1

I'm having problems in this situation:

@Component({
  selector: 'my-app',
  template: `
    {{items | async| json}}

    <div *ngFor="let item of items | async">
      <input type=checkbox [(ngModel)]="item.b"/>
    </div>
  `
})
export class AppComponent  {
  items = of([{
    name: '1',
  },
  {
    name: '2',
  },
  {
    name: '3',
  }])
  .pipe(map(i=>{
    return i.map(i=>{
      return {
        i: i,
        b: false
      }
    })
  }))
}

Stackblitz app

The problem is that the ngModel is not working and I can't see the b property change. If I remove the map pipe and I put the boolean property in the first array, all is working. Am I missing something? What's the problem?

Thanks

Davide C
  • 830
  • 2
  • 11
  • 21
  • 1
    https://stackblitz.com/edit/ngmodel-problem-k9p24r?file=src/app/app.component.ts – Pavankumar Shukla Apr 25 '20 at 15:50
  • It’s unclear what you are trying to achieve here..? Why the observable? – MikeOne Apr 25 '20 at 15:51
  • I'm using the observable because this is only a simple example. In my real application I have an observable as a response from an http call and I need to add some properties (a selected boolean property) to the response before to show on the template – Davide C Apr 25 '20 at 15:54
  • 1
    @PavanShukla, thanks for your solution – Davide C Apr 25 '20 at 15:57

2 Answers2

3

You're not doing anything wrong. If you render out {{item.b}} in the ngFor you will see the value is changing between true and false correctly. As mentioned in the other answer this is because of references and change detection. You can also simply save the observable data a property on your class using ngOnInit and subscribe:

import { Component } from "@angular/core";
import { of } from "rxjs";
import { map } from "rxjs/operators";

@Component({
  selector: "my-app",
  template: `
    {{ items | json }}

    <form #myForm="ngForm">
      <div *ngFor="let item of items">
        <input [name]="item.i.name" type="checkbox" [(ngModel)]="item.b" />
      </div>
    </form>
  `
})
export class AppComponent {
  items: any[] = [];

  ngOnInit() {
    this.getData().subscribe(data => (this.items = data));
  }

  private getData() {
    return of([
      {
        name: "1"
      },
      {
        name: "2"
      },
      {
        name: "3"
      }
    ]).pipe(
      map(i => {
        return i.map(i => {
          return {
            i: i,
            b: false
          };
        });
      })
    );
  }
}

Here is an example in action. Don't forget to clean up any observables if needed to avoid memory leaks.

Alexander Staroselsky
  • 37,209
  • 15
  • 79
  • 91
  • Thanks for all the answer. Just to understand better, is it possible to maintain only the observable items? I mean, is there a way to trigger the change detection? – Davide C Apr 25 '20 at 19:08
  • 1
    @DavideC There are a few ways to approach this. I'd recommend to look at this question/answer: https://stackoverflow.com/questions/36919399/angular-2-view-not-updating-after-model-changes – Alexander Staroselsky Apr 25 '20 at 20:30
2

Actually what you are doing is correct. To check what I mean change your code to this:

<input type=checkbox (change)="change(item)" [(ngModel)]="item.b"/>

change(item) {
 console.log(item);
}

This is not reflecting on the dom because the items array is mapped to the same memory location and changing an element inside it won't cause a change detection in angular to trigger a display change.

Arun Mohan
  • 1,207
  • 1
  • 5
  • 11