fix(ivy): reset binding index before executing a template in `refreshView` call (#32201)

Prior to this change, the `BINDING_INDEX` of a given lView was reset after processing a template. However change detection can be triggered as a result of View queries processing, thus leading to subsequent `refreshView` call (and executing a template), which in turn operates with the binding index that is not reset after the previous `refreshView` call. This commit updates the logic to reset binding index before we execute a template, so binding index is correct for instructions inside template function.

PR Close #32201
This commit is contained in:
Andrew Kushnir 2019-08-19 17:42:52 -07:00 committed by atscott
parent 4f7c971ee7
commit 6b245a39ee
2 changed files with 43 additions and 5 deletions

View File

@ -377,14 +377,14 @@ export function refreshView<T>(
try {
resetPreOrderHookFlags(lView);
if (templateFn !== null) {
executeTemplate(lView, templateFn, RenderFlags.Update, context);
}
// Resetting the bindingIndex of the current LView as the next steps may trigger change
// detection.
lView[BINDING_INDEX] = tView.bindingStartIndex;
if (templateFn !== null) {
executeTemplate(lView, templateFn, RenderFlags.Update, context);
}
const checkNoChangesMode = getCheckNoChangesMode();
const hooksInitPhaseCompleted =
(flags & LViewFlags.InitPhaseStateMask) === InitPhaseState.InitPhaseCompleted;

View File

@ -8,7 +8,7 @@
import {CommonModule} from '@angular/common';
import {ApplicationRef, ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, Directive, DoCheck, EmbeddedViewRef, ErrorHandler, Input, NgModule, OnInit, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core';
import {ApplicationRef, ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, Directive, DoCheck, EmbeddedViewRef, ErrorHandler, Input, NgModule, OnInit, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -545,6 +545,44 @@ describe('change detection', () => {
expect(fixture.nativeElement.textContent).toEqual('1');
});
it('should support change detection triggered as a result of View queries processing', () => {
@Component({
selector: 'app',
template: `
<div *ngIf="visible" #ref>Visible text</div>
`
})
class App {
@ViewChildren('ref')
ref !: QueryList<any>;
visible = false;
constructor(public changeDetectorRef: ChangeDetectorRef) {}
ngAfterViewInit() {
this.ref.changes.subscribe((refs: QueryList<any>) => {
this.visible = false;
this.changeDetectorRef.detectChanges();
});
}
}
TestBed.configureTestingModule({
declarations: [App],
imports: [CommonModule],
});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toBe('');
// even though we set "visible" to `true`, we do not expect any content to be displayed,
// since the flag is overridden in `ngAfterViewInit` back to `false`
fixture.componentInstance.visible = true;
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toBe('');
});
describe('dynamic views', () => {
@Component({selector: 'structural-comp', template: '{{ value }}'})
class StructuralComp {