Angular2 change detection error - with plunker

I am trying to fully understand change detection using Angular2 final.

It includes:

  • Working with change detection strategies
  • Attaching and disconnecting a change detector from a component.

I thought I already had a fairly clear overview of these concepts, but to make sure my assumptions were correct, I wrote a small tablet to test them.

My common understanding is that on a global scale, but in some situations I am a little lost.


Here is the plunker: Angular2 Playground with change detection

Quick explanation of the plunker:

Pretty simple:

  • One parent component in which you can edit one attribute that will be passed to two components:
  • A child with a change detection strategy set to OnPush
  • Child with change detection strategy is set to Default

The parent attribute can be passed to child components:

  • Changing the entire attribute object and creating a new one (" Change obj ") (which triggers detection of changes in the OnPush child)
  • Changing members inside an attribute object ( "Modify Content" ) (which do not cause changes to be detected in the OnPush child)

For each child component, a ChangeDetector can be attached or detached. ( "disconnect ()" and "repeat ()" )

The OnPush child has an additional internal property that can be edited, and change detection can be explicitly applied ( "detectChanges ()" )


Here are the scenarios in which I get behavior that I cannot explain:

Scenario1:

  • Disable the OnPush children and children changes detector by default (click " disconnect () " on both components)
  • Change parent attribute firstname and lastname
  • Click " Change obj " to pass the changed attribute to the children

Expected Behavior: I expect that BOTH children will NOT be updated because they both have a change detector disabled.

Current behavior: By default, the child is not updated, but the OnPush child is updated. WHY? It should not be because its CD is disconnected ...

Scenario2:

  • Detach CD for OnPush component
  • Change its internal value and click change internal . Nothing happens because the CD is disconnected, so no change was found ... OK
  • Click detectChanges () : changes are detected and the view is updated. So far so good.
  • Edit the internal value tab again and click change internal . Once again, nothing happens because the drive is disconnected, so no change was detected .. OK
  • Edit the parent attribute firstname and lastname.
  • Click " Change obj " to pass the changed attribute to the children

Expected behavior: OnPush children should NOT be updated in EVERYTHING, again, because its disk is disconnected ... A CD should not occur at all on this component

Current behavior: Both the value and the internal values are updated; seams, such as a full CD, are applied to this component.

  1. For the last time, edit the input of the internal value and click change internal : the change is detected, and the internal value is updated ...

Expected Behavior: The internal value should NOT be updated because the CD is still disconnected.

Current behavior: An internal value change was detected ... WHY?


Conclusions:

In accordance with these tests, I conclude the following: what stitches are strange to me:

  • The component with the OnPush strategy gets “changed detected” when their input changes, EVEN IF their change detectors are disconnected.
  • The component with the OnPush strategy gets a change detector binding every time its input is changed ...

What do you think of these findings?

Can you better explain this behavior?

Is this a mistake or a desired behavior?

+6
source share
1 answer

Update

The component with the OnPush strategy gets "modified detected" when their change input, EVEN IF their change detector is disconnected.

Since Angular 4.1.1 (2017-05-04) OnPush must respect detach()

https://github.com/angular/angular/commit/acf83b9

Old version

There are many undocumented materials on how change detection works.

We need to know about the three main states of changeDetection ( cdMode ):

1) CheckOnce - 0

CheckedOnce means that after calling detectChanges, the change detector mode will become Checked .

Class appview

 detectChanges(throwOnChange: boolean): void { ... this.detectChangesInternal(throwOnChange); if (this.cdMode === ChangeDetectorStatus.CheckOnce) { this.cdMode = ChangeDetectorStatus.Checked; // <== this line } ... } 

2) Verified - 1

Checked means that the change detector should be skipped until its mode changes to CheckOnce .

3) Separately - 3

Detached means that the auxiliary tree of the change detector is not part of the main tree and should be skipped.

Here are the places where Detached used

Class appview

Skip content check

 detectContentChildrenChanges(throwOnChange: boolean) { for (var i = 0; i < this.contentChildren.length; ++i) { var child = this.contentChildren[i]; if (child.cdMode === ChangeDetectorStatus.Detached) continue; // <== this line child.detectChanges(throwOnChange); } } 

Prospectus Verification

 detectViewChildrenChanges(throwOnChange: boolean) { for (var i = 0; i < this.viewChildren.length; ++i) { var child = this.viewChildren[i]; if (child.cdMode === ChangeDetectorStatus.Detached) continue; // <== this line child.detectChanges(throwOnChange); } } 

Skip cdMode change to CheckOnce

 markPathToRootAsCheckOnce(): void { let c: AppView<any> = this; while (isPresent(c) && c.cdMode !== ChangeDetectorStatus.Detached) { // <== this line if (c.cdMode === ChangeDetectorStatus.Checked) { c.cdMode = ChangeDetectorStatus.CheckOnce; } let parentEl = c.type === ViewType.COMPONENT ? c.declarationAppElement : c.viewContainerElement; c = isPresent(parentEl) ? parentEl.parentView : null; } } 

Note: markPathToRootAsCheckOnce works in all event handlers of your view:

enter image description here

So, if the Detached state is set, your view will not be changed.

Then, how does OnPush strategy work OnPush

OnPush means that the change detector mode will be set to CheckOnce during hydration.

compiler / src / view _compiler / property_binder.ts

 const directiveDetectChangesStmt = isOnPushComp ? new o.IfStmt(directiveDetectChangesExpr, [compileElement.appElement.prop('componentView') .callMethod('markAsCheckOnce', []) .toStmt()]) : directiveDetectChangesExpr.toStmt(); 

https://github.com/angular/angular/blob/2.1.2/modules/%40angular/compiler/src/view_compiler/property_binder.ts#L193-L197

See how it looks in your example:

Parent factory (AppComponent)

Enter a description of the image here.

And again, back to the AppView class :

 markAsCheckOnce(): void { this.cdMode = ChangeDetectorStatus.CheckOnce; } 

Scenario 1

1) Disable the OnPush children and children changes detector by default (click "detach ()" on both components)

 OnPush.cdMode - Detached 

3) Click "Edit Object" to pass the changed attribute to the children

 AppComponent.detectChanges || \/ //if (self._OnPush_35_4.detectChangesInInputProps(self,self._el_35,throwOnChange)) { // self._appEl_35.componentView.markAsCheckOnce(); //} OnPush.markAsCheckOnce || \/ OnPush.cdMode - CheckOnce || \/ OnPush.detectChanges || \/ OnPush.cdMode - Checked 

Therefore, OnPush.dectectChanges triggered.

Here is the conclusion:

The OnPush strategy component gets “modified detected” when their input changes, EVEN IF their change detector is disconnected. Moreover, it changes the viewing status to CheckOnce .

Scenario2

1) Unplug the CD for the OnPush component

 OnPush.cdMode - Detached 

6) Click “Modify Object” to pass the changed attribute to children

 See 3) from scenario 1 => OnPush.cdMode - Checked 

7) For the last time, edit the internal input of the value and click the change button internal: a change is detected, and the internal value is updated ...

As I mentioned above, all event handlers include markPathToRootAsCheckOnce . So:

 markPathToRootAsCheckOnce || \/ OnPush.cdMode - CheckOnce || \/ OnPush.detectChanges || \/ OnPush.cdMode - Checked 

As you can see, the OnPush strategy and ChangeDetector control one property - cdMode

The OnPush strategy component gets its change detector attached every time its input changes ...

In conclusion, I want to say that you are right.

+12
source

Source: https://habr.com/ru/post/1011864/


All Articles