fix(ivy): save queries at the correct indices (#28327)

Previous to this change, we were storing view queries at the
wrong index. This is because we were passing a raw index to the
store() instruction instead of an adjusted index (i.e. an
index that does not include the HEADER_OFFSET). We had an
additional issue where TView.blueprint was not backfilled
when TView.data was backfilled, so new component instances
created from the blueprint would end up having a shorter LView.
Both of these problems together led to the Material demo app
failing with Ivy. This commit fixes those discrepancies.

PR Close #28327
This commit is contained in:
Kara Erickson 2019-01-23 21:11:13 -08:00 committed by Jason Aden
parent d4ecffe475
commit c1fb9c265c
6 changed files with 107 additions and 67 deletions

View File

@ -2930,6 +2930,7 @@ export function store<T>(index: number, value: T): void {
const adjustedIndex = index + HEADER_OFFSET; const adjustedIndex = index + HEADER_OFFSET;
if (adjustedIndex >= tView.data.length) { if (adjustedIndex >= tView.data.length) {
tView.data[adjustedIndex] = null; tView.data[adjustedIndex] = null;
tView.blueprint[adjustedIndex] = null;
} }
lView[adjustedIndex] = value; lView[adjustedIndex] = value;
} }

View File

@ -23,7 +23,7 @@ import {unusedValueExportToPlacateAjd as unused1} from './interfaces/definition'
import {unusedValueExportToPlacateAjd as unused2} from './interfaces/injector'; import {unusedValueExportToPlacateAjd as unused2} from './interfaces/injector';
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node'; import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node';
import {LQueries, unusedValueExportToPlacateAjd as unused4} from './interfaces/query'; import {LQueries, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
import {LView, TVIEW} from './interfaces/view'; import {HEADER_OFFSET, LView, TVIEW} from './interfaces/view';
import {getCurrentViewQueryIndex, getIsParent, getLView, getOrCreateCurrentQueries, setCurrentViewQueryIndex} from './state'; import {getCurrentViewQueryIndex, getIsParent, getLView, getOrCreateCurrentQueries, setCurrentViewQueryIndex} from './state';
import {isContentQueryHost} from './util'; import {isContentQueryHost} from './util';
import {createElementRef, createTemplateRef} from './view_engine_compatibility'; import {createElementRef, createTemplateRef} from './view_engine_compatibility';
@ -407,7 +407,7 @@ export function viewQuery<T>(
} }
const index = getCurrentViewQueryIndex(); const index = getCurrentViewQueryIndex();
const viewQuery: QueryList<T> = query<T>(predicate, descend, read); const viewQuery: QueryList<T> = query<T>(predicate, descend, read);
store(index, viewQuery); store(index - HEADER_OFFSET, viewQuery);
setCurrentViewQueryIndex(index + 1); setCurrentViewQueryIndex(index + 1);
return viewQuery; return viewQuery;
} }
@ -418,5 +418,5 @@ export function viewQuery<T>(
export function loadViewQuery<T>(): T { export function loadViewQuery<T>(): T {
const index = getCurrentViewQueryIndex(); const index = getCurrentViewQueryIndex();
setCurrentViewQueryIndex(index + 1); setCurrentViewQueryIndex(index + 1);
return load<T>(index); return load<T>(index - HEADER_OFFSET);
} }

View File

@ -9,7 +9,7 @@
import {Inject, InjectionToken, QueryList} from '../../src/core'; import {Inject, InjectionToken, QueryList} from '../../src/core';
import {ComponentDef, DirectiveDef, InheritDefinitionFeature, NgOnChangesFeature, ProvidersFeature, RenderFlags, allocHostVars, bind, defineBase, defineComponent, defineDirective, directiveInject, element, elementProperty, loadViewQuery, queryRefresh, viewQuery} from '../../src/render3/index'; import {ComponentDef, DirectiveDef, InheritDefinitionFeature, NgOnChangesFeature, ProvidersFeature, RenderFlags, allocHostVars, bind, defineBase, defineComponent, defineDirective, directiveInject, element, elementProperty, loadViewQuery, queryRefresh, viewQuery} from '../../src/render3/index';
import {ComponentFixture, createComponent} from './render_util'; import {ComponentFixture, createComponent, getDirectiveOnNode} from './render_util';
describe('InheritDefinitionFeature', () => { describe('InheritDefinitionFeature', () => {
it('should inherit lifecycle hooks', () => { it('should inherit lifecycle hooks', () => {
@ -363,6 +363,76 @@ describe('InheritDefinitionFeature', () => {
expect(divEl.title).toEqual('new-title'); expect(divEl.title).toEqual('new-title');
}); });
describe('view query', () => {
const SomeComp = createComponent('some-comp', (rf: RenderFlags, ctx: any) => {});
/*
* class SuperComponent {
* @ViewChildren('super') superQuery;
* }
*/
class SuperComponent {
superQuery?: QueryList<any>;
static ngComponentDef = defineComponent({
type: SuperComponent,
template: () => {},
consts: 0,
vars: 0,
selectors: [['super-comp']],
viewQuery: <T>(rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
viewQuery(['super'], false);
}
if (rf & RenderFlags.Update) {
let tmp: any;
queryRefresh(tmp = loadViewQuery<QueryList<any>>()) &&
(ctx.superQuery = tmp as QueryList<any>);
}
},
factory: () => new SuperComponent(),
});
}
/**
* <div id="sub" #sub></div>
* <div id="super" #super></div>
* <some-comp></some-comp>
* class SubComponent extends SuperComponent {
* @ViewChildren('sub') subQuery;
* }
*/
class SubComponent extends SuperComponent {
subQuery?: QueryList<any>;
static ngComponentDef = defineComponent({
type: SubComponent,
template: (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
element(0, 'div', ['id', 'sub'], ['sub', '']);
element(2, 'div', ['id', 'super'], ['super', '']);
element(4, 'some-comp');
}
},
consts: 5,
vars: 0,
selectors: [['sub-comp']],
viewQuery: (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
viewQuery(['sub'], false);
}
if (rf & RenderFlags.Update) {
let tmp: any;
queryRefresh(tmp = loadViewQuery<QueryList<any>>()) &&
(ctx.subQuery = tmp as QueryList<any>);
}
},
factory: () => new SubComponent(),
features: [InheritDefinitionFeature],
directives: [SomeComp]
});
}
it('should compose viewQuery (basic mechanics check)', () => { it('should compose viewQuery (basic mechanics check)', () => {
const log: Array<[string, RenderFlags, any]> = []; const log: Array<[string, RenderFlags, any]> = [];
@ -404,69 +474,9 @@ describe('InheritDefinitionFeature', () => {
expect(log).toEqual([['super', 1, context], ['sub', 1, context]]); expect(log).toEqual([['super', 1, context], ['sub', 1, context]]);
}); });
it('should compose viewQuery (query logic check)', () => { it('should compose viewQuery (query logic check)', () => {
/*
* class SuperComponent {
* @ViewChildren('super') superQuery;
* }
*/
class SuperComponent {
superQuery?: QueryList<any>;
static ngComponentDef = defineComponent({
type: SuperComponent,
template: () => {},
consts: 0,
vars: 0,
selectors: [['', 'superDir', '']],
viewQuery: <T>(rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
viewQuery(['super'], false);
}
if (rf & RenderFlags.Update) {
let tmp: any;
queryRefresh(tmp = loadViewQuery<QueryList<any>>()) &&
(ctx.superQuery = tmp as QueryList<any>);
}
},
factory: () => new SuperComponent(),
});
}
/**
* <div id="sub" #sub></div>
* <div id="super" #super></div>
* class SubComponent extends SuperComponent {
* @ViewChildren('sub') subQuery;
* }
*/
class SubComponent extends SuperComponent {
subQuery?: QueryList<any>;
static ngComponentDef = defineComponent({
type: SubComponent,
template: (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
element(0, 'div', ['id', 'sub'], ['sub', '']);
element(2, 'div', ['id', 'super'], ['super', '']);
}
},
consts: 4,
vars: 0,
selectors: [['', 'subDir', '']],
viewQuery: (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
viewQuery(['sub'], false);
}
if (rf & RenderFlags.Update) {
let tmp: any;
queryRefresh(tmp = loadViewQuery<QueryList<any>>()) &&
(ctx.subQuery = tmp as QueryList<any>);
}
},
factory: () => new SubComponent(),
features: [InheritDefinitionFeature]
});
}
const fixture = new ComponentFixture(SubComponent); const fixture = new ComponentFixture(SubComponent);
const check = (key: string): void => { const check = (key: string): void => {
@ -480,6 +490,35 @@ describe('InheritDefinitionFeature', () => {
check('super'); check('super');
}); });
it('should work with multiple viewQuery comps', () => {
let subCompOne !: SubComponent;
let subCompTwo !: SubComponent;
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
element(0, 'sub-comp');
element(1, 'sub-comp');
}
subCompOne = getDirectiveOnNode(0);
subCompTwo = getDirectiveOnNode(1);
}, 2, 0, [SubComponent, SuperComponent]);
const fixture = new ComponentFixture(App);
const check = (comp: SubComponent): void => {
const qList = comp.subQuery as QueryList<any>;
expect(qList.length).toBe(1);
expect(qList.first.nativeElement).toEqual(fixture.hostElement.querySelector('#sub'));
expect(qList.first.nativeElement.id).toEqual('sub');
};
check(subCompOne);
check(subCompTwo);
});
});
it('should compose contentQueries', () => { it('should compose contentQueries', () => {
const log: string[] = []; const log: string[] = [];

View File

@ -1305,14 +1305,14 @@ describe('query', () => {
0, Cmpt_Template_1, 2, 0, 'ng-template', null, ['foo', ''], 0, Cmpt_Template_1, 2, 0, 'ng-template', null, ['foo', ''],
templateRefExtractor); templateRefExtractor);
template( template(
1, Cmpt_Template_1, 2, 0, 'ng-template', null, ['bar', ''], 2, Cmpt_Template_1, 2, 0, 'ng-template', null, ['bar', ''],
templateRefExtractor); templateRefExtractor);
template( template(
2, Cmpt_Template_1, 2, 0, 'ng-template', null, ['baz', ''], 4, Cmpt_Template_1, 2, 0, 'ng-template', null, ['baz', ''],
templateRefExtractor); templateRefExtractor);
} }
}, },
3, 0, [], [], 6, 0, [], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(TemplateRef as any, false); viewQuery(TemplateRef as any, false);
@ -2160,7 +2160,7 @@ describe('query', () => {
element(0, 'div', null, ['foo', '']); element(0, 'div', null, ['foo', '']);
} }
}, },
1, 0, [], [], 2, 0, [], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], false); viewQuery(['foo'], false);

View File

@ -279,8 +279,8 @@ class SuperComp {
vars: 0, vars: 0,
template: (rf: RenderFlags, ctx: SuperComp) => { template: (rf: RenderFlags, ctx: SuperComp) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(1, 'div'); elementStart(0, 'div');
element(2, 'child-comp', ['child', ''], ['child', 'child']); element(1, 'child-comp', ['child', ''], ['child', 'child']);
elementEnd(); elementEnd();
} }
}, },

View File

@ -2060,10 +2060,10 @@ describe('ViewContainerRef', () => {
vars: 0, vars: 0,
template: (rf: RenderFlags, ctx: DynamicCompWithViewQueries) => { template: (rf: RenderFlags, ctx: DynamicCompWithViewQueries) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
element(1, 'div', ['bar', ''], ['foo', '']); element(0, 'div', ['bar', ''], ['foo', '']);
} }
// testing only // testing only
fooEl = getNativeByIndex(1, getLView()); fooEl = getNativeByIndex(0, getLView());
}, },
viewQuery: function(rf: RenderFlags, ctx: any) { viewQuery: function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {