Why do I need to call detectChanges() with the default change detection strategy?

serlingpa picture serlingpa · Jul 21, 2017 · Viewed 51.2k times · Source

I am working on an Angular 4 app, but I am having a problem inasmuch as I am having to call this.changeDetectorRef.detectChanges(); to update the view when the model changes. For example, I have this code which is for pagination:

changePage(pageNumber) {
  this.currentPage = pageNumber;
  // why do I need to do this?
  this.changeDetectorRef.detectChanges();
}

I have expicitly set the change detection strategy on the component to ChangeDetectionStrategy.Default but this has no effect. It is happening here too when subscribing to an observable:

showResults(preference) {
  this.apiService.get('dining_autocomplete/', `?search=${preference}`)
    .subscribe((results) => {
      this.searchResults = results;
      // why do I need to do this?
      this.changeDetectorRef.detectChanges();
  });
}

If I console.log() this.searchResults in the TypeScript, I get the expected results, but if I use {{ searchResults }} in the HTML, it doesn't update until some other event happens, presumably when it goes through another digest cycle.

What could be going on?

== EDIT ===========================================================

The code for my component looks like this:

import {ChangeDetectorRef, Component, Input, OnChanges} from "@angular/core";

import * as _ from 'lodash';

@Component({
  selector: 'dining-search-results',
  templateUrl: './dining-search-results.template.html',
  styleUrls: ['./dining-search-results.style.scss'],

})
export class DiningSearchResultsComponent {
  @Input() searchResults: any[];
  @Input() hotTablesOnly: boolean = false;
  @Input() memberBenefitsOnly: boolean = false;

  numberOfTagsToDisplay: number = 3;
  resultsPerPage: number = 5;
  currentPage: number = 1;

  constructor(private changeDetectorRef: ChangeDetectorRef) {
  }

  get filteredResults() {
    return this.searchResults ?
      this.searchResults.filter((r) => !((this.hotTablesOnly && !r.has_hot_table)
        || (this.memberBenefitsOnly && !r.has_member_benefit))) : [];
  }

  get pagedResults() {
    return _.chain(this.filteredResults)
      .drop(this.resultsPerPage * (this.currentPage - 1))
      .take(this.resultsPerPage)
      .value();
  }

  get totalPages(): number {
    return Math.ceil(this.filteredResults.length / this.resultsPerPage);
  }

  getInitialTags(tagsArray: any[], count: number): any[] {
    return _.take(tagsArray, count);
  }

  changePage(pageNumber) {
    this.currentPage = pageNumber;
    // why do I need to do this?
    this.changeDetectorRef.detectChanges();
  }
}

When changePage() is called, and this.currentPage is updated, the changes are not reflected in the HTML unless I call detectChanges().

Answer

serlingpa picture serlingpa · Jul 25, 2017

I have solved the problem.

Another component sending notifications to this component was running Observable.fromEvent() outside the Angular zone, so change detection was not happening automatically in response to these events. This post on zones and this StackOverflow post on the issue held the solution!