fix(ivy): process creation mode deeply before running update mode (#27744)
Prior to this commit, we had two different modes for change detection execution for Ivy, depending on whether you called `bootstrap()` or `renderComponent()`. In the former case, we would complete creation mode for all components in the tree before beginning update mode for any component. In the latter case, we would run creation mode and update mode together for each component individually. Maintaining code to support these two different execution orders was unnecessarily complex, so this commit aligns the two bootstrapping mechanisms to execute in the same order. Now creation mode always runs for all components before update mode begins. This change also simplifies our rendering logic so that we use `LView` flags as the source of truth for rendering mode instead of `rf` function arguments. This fixed some related bugs (e.g. calling `ViewRef.detectChanges` synchronously after the view's creation would create view nodes twice, view queries would execute twice, etc). PR Close #27744
This commit is contained in:

committed by
Matias Niemelä

parent
f48a00fb0c
commit
a20b2f72f2
@ -112,23 +112,21 @@ describe('Query API', () => {
|
||||
expect(directive.child.text).toEqual('foo');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-782 - View queries are executed twice in some cases')
|
||||
.it('should contain the first view child', () => {
|
||||
const template = '<needs-view-child #q></needs-view-child>';
|
||||
const view = createTestCmpAndDetectChanges(MyComp0, template);
|
||||
it('should contain the first view child', () => {
|
||||
const template = '<needs-view-child #q></needs-view-child>';
|
||||
const view = createTestCmpAndDetectChanges(MyComp0, template);
|
||||
|
||||
const q: NeedsViewChild = view.debugElement.children[0].references !['q'];
|
||||
expect(q.logs).toEqual([['setter', 'foo'], ['init', 'foo'], ['check', 'foo']]);
|
||||
const q: NeedsViewChild = view.debugElement.children[0].references !['q'];
|
||||
expect(q.logs).toEqual([['setter', 'foo'], ['init', 'foo'], ['check', 'foo']]);
|
||||
|
||||
q.shouldShow = false;
|
||||
view.detectChanges();
|
||||
expect(q.logs).toEqual([
|
||||
['setter', 'foo'], ['init', 'foo'], ['check', 'foo'], ['setter', null],
|
||||
['check', null]
|
||||
]);
|
||||
});
|
||||
q.shouldShow = false;
|
||||
view.detectChanges();
|
||||
expect(q.logs).toEqual([
|
||||
['setter', 'foo'], ['init', 'foo'], ['check', 'foo'], ['setter', null], ['check', null]
|
||||
]);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-782 - View queries are executed twice in some cases')
|
||||
modifiedInIvy('Static ViewChild and ContentChild queries are resolved in update mode')
|
||||
.it('should set static view and content children already after the constructor call', () => {
|
||||
const template =
|
||||
'<needs-static-content-view-child #q><div text="contentFoo"></div></needs-static-content-view-child>';
|
||||
@ -142,34 +140,33 @@ describe('Query API', () => {
|
||||
expect(q.viewChild.text).toEqual('viewFoo');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-782 - View queries are executed twice in some cases')
|
||||
.it('should contain the first view child across embedded views', () => {
|
||||
TestBed.overrideComponent(
|
||||
MyComp0, {set: {template: '<needs-view-child #q></needs-view-child>'}});
|
||||
TestBed.overrideComponent(NeedsViewChild, {
|
||||
set: {
|
||||
template:
|
||||
'<div *ngIf="true"><div *ngIf="shouldShow" text="foo"></div></div><div *ngIf="shouldShow2" text="bar"></div>'
|
||||
}
|
||||
});
|
||||
const view = TestBed.createComponent(MyComp0);
|
||||
it('should contain the first view child across embedded views', () => {
|
||||
TestBed.overrideComponent(
|
||||
MyComp0, {set: {template: '<needs-view-child #q></needs-view-child>'}});
|
||||
TestBed.overrideComponent(NeedsViewChild, {
|
||||
set: {
|
||||
template:
|
||||
'<div *ngIf="true"><div *ngIf="shouldShow" text="foo"></div></div><div *ngIf="shouldShow2" text="bar"></div>'
|
||||
}
|
||||
});
|
||||
const view = TestBed.createComponent(MyComp0);
|
||||
|
||||
view.detectChanges();
|
||||
const q: NeedsViewChild = view.debugElement.children[0].references !['q'];
|
||||
expect(q.logs).toEqual([['setter', 'foo'], ['init', 'foo'], ['check', 'foo']]);
|
||||
view.detectChanges();
|
||||
const q: NeedsViewChild = view.debugElement.children[0].references !['q'];
|
||||
expect(q.logs).toEqual([['setter', 'foo'], ['init', 'foo'], ['check', 'foo']]);
|
||||
|
||||
q.shouldShow = false;
|
||||
q.shouldShow2 = true;
|
||||
q.logs = [];
|
||||
view.detectChanges();
|
||||
expect(q.logs).toEqual([['setter', 'bar'], ['check', 'bar']]);
|
||||
q.shouldShow = false;
|
||||
q.shouldShow2 = true;
|
||||
q.logs = [];
|
||||
view.detectChanges();
|
||||
expect(q.logs).toEqual([['setter', 'bar'], ['check', 'bar']]);
|
||||
|
||||
q.shouldShow = false;
|
||||
q.shouldShow2 = false;
|
||||
q.logs = [];
|
||||
view.detectChanges();
|
||||
expect(q.logs).toEqual([['setter', null], ['check', null]]);
|
||||
});
|
||||
q.shouldShow = false;
|
||||
q.shouldShow2 = false;
|
||||
q.logs = [];
|
||||
view.detectChanges();
|
||||
expect(q.logs).toEqual([['setter', null], ['check', null]]);
|
||||
});
|
||||
|
||||
fixmeIvy(
|
||||
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy')
|
||||
@ -589,31 +586,35 @@ describe('Query API', () => {
|
||||
});
|
||||
|
||||
// Note: this test is just document our current behavior, which we do for performance reasons.
|
||||
fixmeIvy('FW-782 - View queries are executed twice in some cases')
|
||||
.it('should not affected queries for projected templates if views are detached or moved', () => {
|
||||
const template =
|
||||
'<manual-projecting #q><ng-template let-x="x"><div [text]="x"></div></ng-template></manual-projecting>';
|
||||
const view = createTestCmpAndDetectChanges(MyComp0, template);
|
||||
const q = view.debugElement.children[0].references !['q'] as ManualProjecting;
|
||||
expect(q.query.length).toBe(0);
|
||||
fixmeIvy('FW-853: Query results are cleared if embedded views are detached / moved')
|
||||
.it('should not affect queries for projected templates if views are detached or moved',
|
||||
() => {
|
||||
const template = `<manual-projecting #q>
|
||||
<ng-template let-x="x">
|
||||
<div [text]="x"></div>
|
||||
</ng-template>
|
||||
</manual-projecting>`;
|
||||
const view = createTestCmpAndDetectChanges(MyComp0, template);
|
||||
const q = view.debugElement.children[0].references !['q'] as ManualProjecting;
|
||||
expect(q.query.length).toBe(0);
|
||||
|
||||
const view1 = q.vc.createEmbeddedView(q.template, {'x': '1'});
|
||||
const view2 = q.vc.createEmbeddedView(q.template, {'x': '2'});
|
||||
view.detectChanges();
|
||||
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
|
||||
const view1 = q.vc.createEmbeddedView(q.template, {'x': '1'});
|
||||
const view2 = q.vc.createEmbeddedView(q.template, {'x': '2'});
|
||||
view.detectChanges();
|
||||
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
|
||||
|
||||
q.vc.detach(1);
|
||||
q.vc.detach(0);
|
||||
q.vc.detach(1);
|
||||
q.vc.detach(0);
|
||||
|
||||
view.detectChanges();
|
||||
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
|
||||
view.detectChanges();
|
||||
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
|
||||
|
||||
q.vc.insert(view2);
|
||||
q.vc.insert(view1);
|
||||
q.vc.insert(view2);
|
||||
q.vc.insert(view1);
|
||||
|
||||
view.detectChanges();
|
||||
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
|
||||
});
|
||||
view.detectChanges();
|
||||
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
|
||||
});
|
||||
|
||||
fixmeIvy('unknown').it(
|
||||
'should remove manually projected templates if their parent view is destroyed', () => {
|
||||
|
Reference in New Issue
Block a user