4

TL;DR: ngOnChanges shows that changes are being detected on an input property, but the view is not being updated

I'm working on an Angular (2+) app trying to make a progress bar for an asynchronous task being fulfilled by a service using an Observable. The Observable pushes numbers as data to indicate what percent of the task has been completed. There are three pieces involved: The Service, the ResultsComponent, and the ProgressBarComponent (child of ResultsComponent) The basic data flow is:

  1. Number pushed from the Observable to ResultsComponent
  2. ResultsComponent.percentLoaded is set to equal this number
  3. ProgressBarComponent uses percentLoaded as an Input and view is updated

The problem is that the view isn't actually being updated.

In ResultsComponent I have this block:

this.service.doTheTask()
    .subscribe(
      data => {
        this.percentLoaded = data;
        this.ref.detectChanges();
      },
      e => console.log('error:', e),
      () =>  {
        this.isLoading = false;
        this.percentLoaded = 0;
      }
    );

Adding ref.detectChanges() successfully makes the OnChanges hook fire in ProgressBarComponent. Component and template are shown below:

Component

export class ProgressBarComponent implements OnChanges {
  @Input() percentLoaded = 0;

  constructor() { }

  ngOnChanges(changes) {
    console.log('changes: ', changes);
  }
}

Template

<div class="meter">
  <div class="percent" [style.width.%]="percentLoaded"></div>
</div>
<p>{{percentLoaded}}% Loaded</p>

As you can see, I'm just logging changes to test. I've verified that ngOnChanges is firing, and that the values are correct. Everything I've read says "Once changes are detected, the view automatically updates" so I don't know what else to do.

Any suggestions?

slander
  • 111
  • 1
  • 7
  • Are none of the bindings to the `percentLoaded` field updating in the view or just some of them? – Teddy Sterne May 02 '17 at 15:17
  • None of the bindings are showing up in the view before the observable is complete. I set this.percentLoaded = 0 in the callback for the completion of the observable. That one is shown in the view. If I remove that line, the view is updated to show 100, the last value that was pushed from the observable. – slander May 02 '17 at 15:24

2 Answers2

0

After your async task is done you overwrite data that you received from subscription with following line this.percentLoaded = 0;. Delete it:

() =>  {
   this.isLoading = false;
   //delete this line this.percentLoaded = 0;
}

Also your change detection is not triggered, because change detection on inputs is triggered only when reference of object is changed. Number is a simple, not a reference variable, so in your case change detection is not triggered. My suggestion is to subscribe to service in both components and trigger change detection only from child component.

export class ProgressBarComponent {
  percentLoaded: number = 0;

  constructor(private ref: ChangeDetectorRef, private service: TheService) { }

  this.service.doTheTask()
   .subscribe(
     data => {
     this.percentLoaded = data;
     this.ref.detectChanges();
   },
}
Yevgen
  • 4,519
  • 3
  • 24
  • 34
  • When I do that, the view still doesn't update during the execution of the observable. It picks up 100 (the last value that is returned) and updates the view to show that, but only after the observable has completed. – slander May 02 '17 at 15:27
  • So you're saying that change detection is still not triggered here? In the code above, I log every time the OnChanges hook was reached. It showed every intermediate value between 0 and 100. If you're saying that change detection is still not being triggered then I am confused as to what the OnChanges hook means. – slander May 02 '17 at 16:03
  • https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html#reducing-the-number-of-checks – Yevgen May 02 '17 at 16:07
-1

I had a similar issue and after spending about an hour trying to figure it out I remembered adding change detection strategy onPush to the parent component.

As soon as I removed that the child component started working again. The ngOnChanges method was firing but my view was not updating in the subscribe closure.

Yehor Androsov
  • 4,885
  • 2
  • 23
  • 40