Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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().

like image 884
serlingpa Avatar asked Jul 21 '17 11:07

serlingpa


People also ask

What does ChangeDetectorRef detectChanges () do?

detectChanges()linkChecks this view and its children. Use in combination with detach to implement local change detection checks.

Where should we call the detectChanges method?

detectChanges — When you call this method on changeDetectorRef provider, it will run change detection from the current component and all it's descendants. While running change detection it keeps the change detection strategy in mind.

What is default change detection in Angular?

By default, Angular 2+ performs change detection on all components (from top to bottom) every time something changes in your app. A change can occur from a user event or data received from a network request.

What is the difference between OnPush and default change detection?

OnPush means that the change detector's mode will be set to CheckOnce during hydration. Default means that the change detector's mode will be set to CheckAlways during hydration.


2 Answers

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!

like image 89
serlingpa Avatar answered Oct 27 '22 13:10

serlingpa


You can Input your searchResults into the child Components

@Input() searchResults;

after that you pass it trough the parent template

// parent template
<app-child [searchResults]="searchResults"></app-child>

you can use it in the child template

// child template 
<my-date-picker [ngModel]="searchResults"></my-date-picker>

after that you can 'listen' for changes in this property in the child Component

export class ChildComponent implements OnChanges {

  @Input() searchResults;

  constructor() { }

  ngOnChanges() {
    // some code
  }

Every time searchResults is changed the changes will be populated in the child component, the values in the child template will get the new value.

like image 2
onetwo12 Avatar answered Oct 27 '22 11:10

onetwo12