fix(ivy): View Queries inheritance fix (#28309)
Prior to this change `viewQuery` functions that represent @ViewQuery list were not composable, which caused problems in case one Component/Directive inherits another one and both of them contain View Queries. Due to the fact that we used indices to reference queries, resulting query set was corrupted (child component queries were overridden by super class ones). In order to avoid that we no longer use indices assigned at compile time and instead maintain current view query index while iterating through them. This allows us to compose `viewQuery` functions and make inheritance feature work with View Queries. PR Close #28309
This commit is contained in:

committed by
Jason Aden

parent
9f9024b7a1
commit
9098225ff0
@ -185,6 +185,8 @@ export class Identifiers {
|
||||
|
||||
static query: o.ExternalReference = {name: 'ɵquery', moduleName: CORE};
|
||||
static queryRefresh: o.ExternalReference = {name: 'ɵqueryRefresh', moduleName: CORE};
|
||||
static viewQuery: o.ExternalReference = {name: 'ɵviewQuery', moduleName: CORE};
|
||||
static loadViewQuery: o.ExternalReference = {name: 'ɵloadViewQuery', moduleName: CORE};
|
||||
static registerContentQuery:
|
||||
o.ExternalReference = {name: 'ɵregisterContentQuery', moduleName: CORE};
|
||||
|
||||
|
@ -254,7 +254,7 @@ export function compileComponentFromMetadata(
|
||||
const template = meta.template;
|
||||
const templateBuilder = new TemplateDefinitionBuilder(
|
||||
constantPool, BindingScope.ROOT_SCOPE, 0, templateTypeName, null, null, templateName,
|
||||
meta.viewQueries, directiveMatcher, directivesUsed, meta.pipes, pipesUsed, R3.namespaceHTML,
|
||||
directiveMatcher, directivesUsed, meta.pipes, pipesUsed, R3.namespaceHTML,
|
||||
meta.relativeContextFilePath, meta.i18nUseExternalIds);
|
||||
|
||||
const templateFunctionExpression = templateBuilder.buildTemplateFunction(template.nodes, []);
|
||||
@ -485,22 +485,19 @@ function selectorsFromGlobalMetadata(
|
||||
return o.NULL_EXPR;
|
||||
}
|
||||
|
||||
function createQueryDefinition(
|
||||
query: R3QueryMetadata, constantPool: ConstantPool, idx: number | null): o.Expression {
|
||||
const predicate = getQueryPredicate(query, constantPool);
|
||||
|
||||
// e.g. r3.query(null, somePredicate, false) or r3.query(0, ['div'], false)
|
||||
function prepareQueryParams(query: R3QueryMetadata, constantPool: ConstantPool): o.Expression[] {
|
||||
const parameters = [
|
||||
o.literal(idx, o.INFERRED_TYPE),
|
||||
predicate,
|
||||
getQueryPredicate(query, constantPool),
|
||||
o.literal(query.descendants),
|
||||
];
|
||||
|
||||
if (query.read) {
|
||||
parameters.push(query.read);
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
return o.importExpr(R3.query).callFn(parameters);
|
||||
function createQueryDefinition(query: R3QueryMetadata, constantPool: ConstantPool): o.Expression {
|
||||
return o.importExpr(R3.query).callFn(prepareQueryParams(query, constantPool));
|
||||
}
|
||||
|
||||
// Turn a directive selector into an R3-compatible selector for directive def
|
||||
@ -522,7 +519,7 @@ function createContentQueriesFunction(
|
||||
meta: R3DirectiveMetadata, constantPool: ConstantPool): o.Expression|null {
|
||||
if (meta.queries.length) {
|
||||
const statements: o.Statement[] = meta.queries.map((query: R3QueryMetadata) => {
|
||||
const queryDefinition = createQueryDefinition(query, constantPool, null);
|
||||
const queryDefinition = createQueryDefinition(query, constantPool);
|
||||
return o.importExpr(R3.registerContentQuery)
|
||||
.callFn([queryDefinition, o.variable('dirIndex')])
|
||||
.toStmt();
|
||||
@ -620,22 +617,21 @@ function createViewQueriesFunction(
|
||||
const updateStatements: o.Statement[] = [];
|
||||
const tempAllocator = temporaryAllocator(updateStatements, TEMPORARY_NAME);
|
||||
|
||||
for (let i = 0; i < meta.viewQueries.length; i++) {
|
||||
const query = meta.viewQueries[i];
|
||||
|
||||
// creation, e.g. r3.Q(0, somePredicate, true);
|
||||
const queryDefinition = createQueryDefinition(query, constantPool, i);
|
||||
meta.viewQueries.forEach((query: R3QueryMetadata) => {
|
||||
// creation, e.g. r3.viewQuery(somePredicate, true);
|
||||
const queryDefinition =
|
||||
o.importExpr(R3.viewQuery).callFn(prepareQueryParams(query, constantPool));
|
||||
createStatements.push(queryDefinition.toStmt());
|
||||
|
||||
// update, e.g. (r3.qR(tmp = r3.ɵload(0)) && (ctx.someDir = tmp));
|
||||
// update, e.g. (r3.queryRefresh(tmp = r3.loadViewQuery()) && (ctx.someDir = tmp));
|
||||
const temporary = tempAllocator();
|
||||
const getQueryList = o.importExpr(R3.load).callFn([o.literal(i)]);
|
||||
const getQueryList = o.importExpr(R3.loadViewQuery).callFn([]);
|
||||
const refresh = o.importExpr(R3.queryRefresh).callFn([temporary.set(getQueryList)]);
|
||||
const updateDirective = o.variable(CONTEXT_NAME)
|
||||
.prop(query.propertyName)
|
||||
.set(query.first ? temporary.prop('first') : temporary);
|
||||
updateStatements.push(refresh.and(updateDirective).toStmt());
|
||||
}
|
||||
});
|
||||
|
||||
const viewQueryFnName = meta.name ? `${meta.name}_Query` : null;
|
||||
return o.fn(
|
||||
|
@ -31,7 +31,6 @@ import {Identifiers as R3} from '../r3_identifiers';
|
||||
import {htmlAstToRender3Ast} from '../r3_template_transform';
|
||||
import {prepareSyntheticListenerFunctionName, prepareSyntheticListenerName, prepareSyntheticPropertyName} from '../util';
|
||||
|
||||
import {R3QueryMetadata} from './api';
|
||||
import {I18nContext} from './i18n/context';
|
||||
import {I18nMetaVisitor} from './i18n/meta';
|
||||
import {getSerializedI18nContent} from './i18n/serializer';
|
||||
@ -161,14 +160,10 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
private constantPool: ConstantPool, parentBindingScope: BindingScope, private level = 0,
|
||||
private contextName: string|null, private i18nContext: I18nContext|null,
|
||||
private templateIndex: number|null, private templateName: string|null,
|
||||
private viewQueries: R3QueryMetadata[], private directiveMatcher: SelectorMatcher|null,
|
||||
private directives: Set<o.Expression>, private pipeTypeByName: Map<string, o.Expression>,
|
||||
private pipes: Set<o.Expression>, private _namespace: o.ExternalReference,
|
||||
private relativeContextFilePath: string, private i18nUseExternalIds: boolean) {
|
||||
// view queries can take up space in data and allocation happens earlier (in the "viewQuery"
|
||||
// function)
|
||||
this._dataIndex = viewQueries.length;
|
||||
|
||||
private directiveMatcher: SelectorMatcher|null, private directives: Set<o.Expression>,
|
||||
private pipeTypeByName: Map<string, o.Expression>, private pipes: Set<o.Expression>,
|
||||
private _namespace: o.ExternalReference, private relativeContextFilePath: string,
|
||||
private i18nUseExternalIds: boolean) {
|
||||
this._bindingScope = parentBindingScope.nestedScope(level);
|
||||
|
||||
// Turn the relative context file path into an identifier by replacing non-alphanumeric
|
||||
@ -821,9 +816,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
// Create the template function
|
||||
const templateVisitor = new TemplateDefinitionBuilder(
|
||||
this.constantPool, this._bindingScope, this.level + 1, contextName, this.i18n,
|
||||
templateIndex, templateName, [], this.directiveMatcher, this.directives,
|
||||
this.pipeTypeByName, this.pipes, this._namespace, this.fileBasedI18nSuffix,
|
||||
this.i18nUseExternalIds);
|
||||
templateIndex, templateName, this.directiveMatcher, this.directives, this.pipeTypeByName,
|
||||
this.pipes, this._namespace, this.fileBasedI18nSuffix, this.i18nUseExternalIds);
|
||||
|
||||
// Nested templates must not be visited until after their parent templates have completed
|
||||
// processing, so they are queued here until after the initial pass. Otherwise, we wouldn't
|
||||
|
Reference in New Issue
Block a user