Binding ngModel to Dynamic Checkbox List : Angular 2 / Typescript
Asked Answered
A

2

9

I'm not sure the proper way to bind and update a model where the checkboxes are dynamically generated. (This is a ASP.NET Core project with Angular 2 initially created using the Yeoman generator) Or a simple checkbox for that matter I have just found out.

Below is stripped down code, which has a facility and timeslots. Each facility can have multiple timeslots. The timeslot checkbox list displays fine. The initial data indicates only one timeslot should be checked. But when I load up a facility, all the timeslots are checked. They aren't binding with ngModel as I expected.

  1. How do I fix it so only the timeslots included in the facility data are checked rather than all?
  2. How do I bind what is checked to the ngModel timeslotids property? Do I need to create an array property on the component and manipulate that instead of relying on ngModel? (tried this but obviously didn't do it right so reverted back to the code below)
  3. I also seem to be having an issue binding to a simple checkbox (haselectric) because this too is not checking off when it should be. Am I missing an import that would fix all of this??

CODE EXPLANATION I have two objects (facility and timeslot) coming from an api which I bind to interfaces. They have been significantly reduced in properties for simplicity's sake:

export interface IFacility {   
   id: number,
   name: string,
   haselectric: boolean, 
   timeslotids: number[],    
   timeslots: ITimeslot[]

}
export interface ITimeslot {
    id: number,
    timeslotname: string    
}

This is the JSON data for Timeslots:

[{"id":1,"timeslotname":"Daily"},{"id":2,"timeslotname":"Hourly"},{"id":4,"timeslotname":"Market"}]

This is the JSON data for a single Facility prior to being updated:

{"id":2,"name":"Clements Building","haselectric":true,"timeslotids":[1],"timeslots":[{"id":1,"timeslotname":"Daily"}]}

Component for editing a facility (facility.component.html):

<form #form="ngForm" (ngSubmit)="submitForm()" *ngIf="formactive">
   <input type="hidden" [(ngModel)]="updatedfacility.id" #id="ngModel" name="id" />
    <div class="form-group">
         <label for="name">Facility Name *</label>
         <input type="text" class="form-control input-lg" [(ngModel)]="updatedfacility.name" #name="ngModel" name="name" placeholder="Facility Name *">
   </div>
   <div class="form-group">
                    <label for="exhibitspaces">Has Electric *</label>
                    <input type="checkbox" class="form-control input-lg" [(ngModel)]="updatedfacility.haselecric" #haselectric="ngModel" name="haselectric">
    </div>
    <div class="form-group">
        <label for="timeslots">Select Timeslots Available for Rent *</label>
        <div *ngIf="dbtimeslots" >
            <span *ngFor="let timeslot of dbtimeslots" class="checkbox">
                <label for="timeslot">
                    <input type="checkbox" [(ngModel)]="updatedfacility.timeslotids" value="{{timeslot.id}}" name="{{timeslot.id}}" [checked]="updatedfacility.timeslotids.indexOf(timeslot.id)" />{{timeslot.timeslotname}}
                 </label>
             </span>
        </div>
    </div>
    <button type="submit" class="btn btn-lg btn-default" [disabled]="form.invalid">Update</button> 
</form>

Code for the component (facility.component.ts)

import { Component, OnInit, Input, Output, OnChanges, EventEmitter  } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { ActivatedRoute, Router } from '@angular/router'; 
import { FormsModule }  from '@angular/forms';
import { FacilityService }  from '../../../services/facility.service';
import { TimeslotService } from '../../../services/timeslot.service';
import { IFacility } from '../../../services/facility.interface';
import { ITimeslot } from '../../../services/timeslot.interface';

@Component({
   template: require('./facility.component.html')
})
export class FacilityDetailsComponent {
  facility: IFacility;
  httpresponse: string;
  successMessage: string;
  errormessage: string;
  showSuccess: boolean = true;
  showError: boolean = true;
  formactive: boolean = true;
  dbtimeslots: ITimeslot[];    


  constructor(
    private _route: ActivatedRoute,
    private _router: Router,
    private _facilityservice: FacilityService,
    private _timeslotservice: TimeslotService
  ) {}

  ngOnInit(): void {
    this._timeslotservice.getTimeslots().subscribe(timeslots => this.dbtimeslots = timeslots, error => this.errormessage = <any>error);
    let id = +this._route.snapshot.params['id'];

    this._facilityservice.getFacility(id)
        .subscribe(facility => this.facility = facility,
        error => this.errormessage = <any>error);

  }

  submitForm() {
    //update the facility through the service call
    this._facilityservice.updateFacility(this.facility)
        .subscribe(response => {
            this.httpresponse = response;
            console.log(this.httpresponse);
            this.successMessage = "Facility updated!";
            this.formactive = false;
            setTimeout(() => this.formactive = true, 3);
            this.showSuccess = false;
        },
        error => {
            this.errormessage = <any>error;
            this.showError = false;
        });
  }     

}

P.S. The reason I have a two properties in the facility object for timeslots is because a list of id's is much easier to pass through to the API for updating. Rather than passing full models (timeslots is larger than what I have here and not needed to update the database). Please reserve comments on that as it's unrelated to what needs to be accomplished.

I found this question...but sadly, no one answered this poor fellow: ngmodel binding with dynamic array of checkbox in angular2 Tried this but didn't work: Get values from a dynamic checkbox list I also tried version of updating an local property on (change) of the checkbox but that didn't seem to work either: Angular 2: Get Values of Multiple Checked Checkboxes

Actinozoan answered 24/4, 2017 at 19:46 Comment(0)
A
14

Based on НЛО answer above I was able to figure out the solution to my question. Thank you НЛО.

<div class="form-group">
    <label for="timeslots">Select Timeslots Available for Rent *</label>
    <div *ngIf="dbtimeslots">
        <span *ngFor="let timeslot of dbtimeslots" class="checkbox">
            <label>
                <input type="checkbox" value="{{timeslot.id}}" name="{{timeslot.id}}"  [checked]="(facility.timeslotids && (-1 !== facility.timeslotids.indexOf(timeslot.id)) ? 'checked' : '')" (change) ="updateSelectedTimeslots($event)" />{{timeslot.timeslotname}}
            </label>
         </span>
    </div>
</div>

Then the function on the component:

updateSelectedTimeslots(event) {
    if (event.target.checked) {
          if (this.facility.timeslotids.indexOf(parseInt(event.target.name)) < 0) { 
                this.facility.timeslotids.push(parseInt(event.target.name));

          }
     } else {
           if (this.facility.timeslotids.indexOf(parseInt(event.target.name)) > -1) 
            {
                this.facility.timeslotids.splice(this.facility.timeslotids.indexOf(parseInt(event.target.name)), 1);              
            }
    }
     //console.log("TimeslotIDs: ", this.facility.timeslotids);    
}
Actinozoan answered 23/5, 2017 at 12:52 Comment(1)
i have a simillar problem to this and i dont know im getting it wrong. if you got time take a look at #51868355Void
T
11

I have a code similar to yours (ngFor over all of the possible checkboxes, some of them should be checked, changes are to be saved in some data structure we are not iterating over), and here's what I come up with:

<md-checkbox 
    *ngFor="let some of availableSomes"
    name="{{ some }}"
    checked="{{ data.somes && -1 !== this.data.somes.indexOf(some) ? 'checked' : '' }}"
    (change)="updateSome($event)">
    {{ some }}
</md-checkbox>

Here's updateSome:

updateSome(event) {
  if (-1 !== this.availableSkills.indexOf(event.source.name)) {
    if (event.checked) {
      this.data.somes.push(event.source.name);
    } else {
      this.data.somes.splice(this.data.somes.indexOf(event.source.name), 1);
    }
  }
}

Also, I believe it should be event.target, but it's event.source due to material. Kind of clumsy solution, but I think it will help you figure out how can you achieve what you want

Tempietempla answered 24/4, 2017 at 20:9 Comment(5)
Thanks much. Going to try this out and post back.Actinozoan
This pointed me in the direction I needed. I updated my original post above with the solution code. Thank you very much!Actinozoan
@Actinozoan glad you figured it out. You should've posted working solution as an answer and accept it thoughTempietempla
oh, sorry. Don't post on here much. I will do that.Actinozoan
Thanks a lot,It helped mePhebe

© 2022 - 2024 — McMap. All rights reserved.