6

I don't understand why even if I use ChangeDetectionStrategy.OnPush and changeDetectorRef.detach() the function ngDoCheck keep been called. I have thousands of components in my app, and I'd like to block the changeDetection of child1 if an event (mouse click, etc) has been raised from child2.

Here is a plunker

As you can see I have a father component

@Component({
  selector: 'my-app',
  template: `     
    <app-child1 test="test"></app-child1>
    <app-child2 test="test"></app-child2> `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
  test = 'test';

constructor() { }

  ngDoCheck() {
    console.log("### ngDoCheck app component");
  }
}

and 2 identical children:

@Component({
  selector: 'app-child1',
  template: `<input type="text" [(ngModel)]="test" /><br/> `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class Child1Component implements OnInit {
  @Input()
  test: string;

  constructor(public cd: ChangeDetectorRef) { }

  ngAfterViewInit() {
      console.log("###### DETACH child 1");
      this.cd.detach();

  }

  ngDoCheck() {
    console.log("### ngDoCheck child 1");
  }
}

If I start typing in the input of child1, the ngDoCheck function of child2 is called.

Think about having thousands of children, it get really slow...

Thank you!

Nehal
  • 13,130
  • 4
  • 43
  • 59
user2010955
  • 3,871
  • 7
  • 34
  • 53
  • I'm reading this https://blog.thoughtram.io/angular/2017/02/02/making-your-angular-app-fast.html, it looks like I have to call the detach method on both children and father component, but why? – user2010955 Aug 10 '17 at 15:29

1 Answers1

3

That is the intended behavior, read Everything you need to know about change detection in Angular. Here is the quote:

  1. calls OnInit and ngDoCheck on a child component (OnInit is called only during first check)
  2. runs change detection for a child view (repeats the steps in this list)

So as you can see the ngDoCheck is always triggered on the child component. The OnPush check is performed after when trying to run change detection for the child component. Here is the quote:

Finally, change detection for the current view is responsible for starting change detection for child views (operation 8). This is the place where state of the child component view is checked and if it’s ChecksEnabled, then for this view the change detection is performed. Here is the relevant code:

viewState = view.state;
...
case ViewAction.CheckAndUpdate:
  if ((viewState & ViewState.ChecksEnabled) &&
    (viewState & (ViewState.Errored | ViewState.Destroyed)) === 0) {
    checkAndUpdateView(view);
  }
}

However, it will be called only for the top component on which the OnPush strategy is used. It will not be called on child components:

     normal component
           |
  child OnPush component  <---- ngDoCheck called only for this component
           | 
    child component 1     <---- ngDoCheck is not called for this component
           |
    child component 2     <---- ngDoCheck is not called for this component

Why is it triggered?

It's triggered to give you opportunity to perform your own custom logic inside this hook and choose to run change detection cycle once even if @Inputs haven't changed:

class OnPushComponent {
   constructor(private cd: ChangeDetectorRef) {}

   ngDoCheck() {
      if (some check) {
          this.cd.markForCheck();
      }
   }
}

Also see Angular ngDoCheck() gets called even with ChangeDetectionStrategy.OnPush this answer for real use case example.

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • thank you, your posts should be part of the official angular doc. I can't believe they didn't cover this difficult topic... So when an event is raised on the child 1 component, it starts a change detection cycle from the top-most component down to its children, right? So if I want to block the change detection for child 2 I have to block it a lever upper, on a father component, setting on it the OnPush property – user2010955 Aug 10 '17 at 23:19
  • `ngDoCheck` doesn't meant the change detection is running for your component, it means that the parent component is being checked right now. In your setup you don't need to do anything in particular. The change detection won't run for the `child2` component. You can test it by adding `child2a` as a child of `child2ё and see that `ngDoCheck` won't be triggered for it when you modify `child1`. Read the articles I mentioned – Max Koretskyi Aug 11 '17 at 07:30
  • I don't understand, on your reply you said this: "child component 1 <---- ngDoCheck is not called for this component" but the ngDoCheck is called instead!.... My problem is that I have a lot of children, and everytime I click on an input text I see for all children the ngDoCheck is called, and it takes time! – user2010955 Aug 11 '17 at 09:58
  • The original plunker already show the problem, everytime I just click on an input text ngDoCheck is called for all the children, here is an updated plunker with more than only 2 children: https://plnkr.co/edit/IABey8nkuUoV9H0ZA6DY?p=preview I'm noticing right now that switching from one input to another (or type something) is really slow only if the developer console is open... Anyway, the ngDoCheck function is calling even if my root component has OnPush – user2010955 Aug 11 '17 at 10:08
  • For what I see the plunker is very slow because of all thoso ngDoCheck calls, so I think that I must find a way to skip them. with OnPush or detach of whatever – user2010955 Aug 11 '17 at 10:12
  • I checked the plunker, and it's consistent with what I'm saying. There's no change detection for the `Child1Component` performed. The call to `ngDoCheck` **doesn't mean change detection is happening** and will **only** be called if you define `ngDoCheck` method. The slowness is not due to check (which doesn't happen), but because of sheer quantity of components. Check [this plunker](https://plnkr.co/edit/VYvpCZsBWNK7lwMpJzWe?p=preview). There's no `ngDoCheck child 1 a` log which proves that CD doesn't happen for `Child1Component` – Max Koretskyi Aug 11 '17 at 10:25
  • If you remove "this.cd.detach();" in child1 component, ### ngDoCheck child 1 a start logging – user2010955 Aug 11 '17 at 10:35
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/151710/discussion-between-user2010955-and-maximus). – user2010955 Aug 11 '17 at 10:50
  • @user2010955, yeah, but it's triggered only when you modify input in `n child1 component,`. If you interact with input in the `n child2 component` it's not triggered. It's triggered for the `child1 component` because **every native DOM event** marks entrire branch of components upwards for check. See [this answer](https://stackoverflow.com/a/45396740/2545680) and this – Max Koretskyi Aug 11 '17 at 10:50