Angular 4 detach change detection between children
Asked Answered
C

1

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!

Childers answered 10/8, 2017 at 13:59 Comment(1)
I'm reading this blog.thoughtram.io/angular/2017/02/02/…, it looks like I have to call the detach method on both children and father component, but why?Childers
J
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.

Judkins answered 10/8, 2017 at 15:44 Comment(9)
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 propertyChilders
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 mentionedJudkins
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!Childers
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: 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 OnPushChilders
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 whateverChilders
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. There's no ngDoCheck child 1 a log which proves that CD doesn't happen for Child1ComponentJudkins
If you remove "this.cd.detach();" in child1 component, ### ngDoCheck child 1 a start loggingChilders
Let us continue this discussion in chat.Childers
@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 and thisJudkins

© 2022 - 2024 — McMap. All rights reserved.