fix(ivy): support static ViewChild queries (#28811)
This commit adds support for the `static: true` flag in `ViewChild` queries. Prior to this commit, all `ViewChild` queries were resolved after change detection ran. This is a problem for backwards compatibility because View Engine also supported "static" queries which would resolve before change detection. Now if users add a `static: true` option, the query will be resolved in creation mode (before change detection runs). For example: ```ts @ViewChild(TemplateRef, {static: true}) template !: TemplateRef; ``` This feature will come in handy for components that need to create components dynamically. PR Close #28811
This commit is contained in:

committed by
Igor Minar

parent
ae16378ee7
commit
a4638d5a81
@ -150,6 +150,7 @@ export interface R3QueryMetadataFacade {
|
||||
predicate: any|string[];
|
||||
descendants: boolean;
|
||||
read: any|null;
|
||||
static: boolean;
|
||||
}
|
||||
|
||||
export interface ParseSourceSpan {
|
||||
|
@ -81,6 +81,7 @@ export {
|
||||
containerRefreshEnd as ɵcontainerRefreshEnd,
|
||||
queryRefresh as ɵqueryRefresh,
|
||||
viewQuery as ɵviewQuery,
|
||||
staticViewQuery as ɵstaticViewQuery,
|
||||
loadViewQuery as ɵloadViewQuery,
|
||||
contentQuery as ɵcontentQuery,
|
||||
loadContentQuery as ɵloadContentQuery,
|
||||
|
@ -124,6 +124,7 @@ export {
|
||||
export {
|
||||
queryRefresh,
|
||||
viewQuery,
|
||||
staticViewQuery,
|
||||
loadViewQuery,
|
||||
contentQuery,
|
||||
loadContentQuery,
|
||||
|
@ -37,7 +37,7 @@ import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTEXT, DECLARATION_VIEW, Expa
|
||||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||
import {appendChild, appendProjectedNode, createTextNode, getLViewChild, insertView, removeView} from './node_manipulation';
|
||||
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
||||
import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCurrentDirectiveDef, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode} from './state';
|
||||
import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCurrentDirectiveDef, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode,} from './state';
|
||||
import {getInitialClassNameValue, getInitialStyleStringValue, initializeStaticContext as initializeStaticStylingContext, patchContextWithStaticAttrs, renderInitialClasses, renderInitialStyles, renderStyling, updateClassProp as updateElementClassProp, updateContextWithBindings, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
|
||||
import {BoundPlayerFactory} from './styling/player_factory';
|
||||
import {ANIMATION_PROP_PREFIX, allocateDirectiveIntoContext, createEmptyStylingContext, forceClassesAsString, forceStylesAsString, getStylingContext, hasClassInput, hasStyleInput, hasStyling, isAnimationProp} from './styling/util';
|
||||
@ -784,6 +784,7 @@ export function createTView(
|
||||
expandoStartIndex: initialViewLength,
|
||||
expandoInstructions: null,
|
||||
firstTemplatePass: true,
|
||||
staticViewQueries: false,
|
||||
initHooks: null,
|
||||
checkHooks: null,
|
||||
contentHooks: null,
|
||||
@ -2922,20 +2923,23 @@ export function checkView<T>(hostView: LView, component: T) {
|
||||
|
||||
try {
|
||||
namespaceHTML();
|
||||
creationMode && executeViewQueryFn(hostView, hostTView, component);
|
||||
creationMode && executeViewQueryFn(RenderFlags.Create, hostTView, component);
|
||||
templateFn(getRenderFlags(hostView), component);
|
||||
refreshDescendantViews(hostView);
|
||||
!creationMode && executeViewQueryFn(hostView, hostTView, component);
|
||||
// Only check view queries again in creation mode if there are static view queries
|
||||
if (!creationMode || hostTView.staticViewQueries) {
|
||||
executeViewQueryFn(RenderFlags.Update, hostTView, component);
|
||||
}
|
||||
} finally {
|
||||
leaveView(oldView);
|
||||
}
|
||||
}
|
||||
|
||||
function executeViewQueryFn<T>(lView: LView, tView: TView, component: T): void {
|
||||
function executeViewQueryFn<T>(flags: RenderFlags, tView: TView, component: T): void {
|
||||
const viewQuery = tView.viewQuery;
|
||||
if (viewQuery) {
|
||||
setCurrentQueryIndex(tView.viewQueryStartIndex);
|
||||
viewQuery(getRenderFlags(lView), component);
|
||||
viewQuery(flags, component);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -376,6 +376,14 @@ export interface TView {
|
||||
*/
|
||||
expandoStartIndex: number;
|
||||
|
||||
/**
|
||||
* Whether or not there are any static view queries tracked on this view.
|
||||
*
|
||||
* We store this so we know whether or not we should do a view query
|
||||
* refresh after creation mode to collect static query results.
|
||||
*/
|
||||
staticViewQueries: boolean;
|
||||
|
||||
/**
|
||||
* The index where the viewQueries section of `LView` begins. This section contains
|
||||
* view queries defined for a component/directive.
|
||||
|
@ -169,7 +169,8 @@ export function convertToR3QueryMetadata(propertyName: string, ann: Query): R3Qu
|
||||
predicate: convertToR3QueryPredicate(ann.selector),
|
||||
descendants: ann.descendants,
|
||||
first: ann.first,
|
||||
read: ann.read ? ann.read : null
|
||||
read: ann.read ? ann.read : null,
|
||||
static: !!ann.static
|
||||
};
|
||||
}
|
||||
function extractQueriesMetadata(
|
||||
|
@ -88,6 +88,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
|
||||
'ɵpipe': r3.pipe,
|
||||
'ɵqueryRefresh': r3.queryRefresh,
|
||||
'ɵviewQuery': r3.viewQuery,
|
||||
'ɵstaticViewQuery': r3.staticViewQuery,
|
||||
'ɵloadViewQuery': r3.loadViewQuery,
|
||||
'ɵcontentQuery': r3.contentQuery,
|
||||
'ɵloadContentQuery': r3.loadContentQuery,
|
||||
|
@ -24,7 +24,7 @@ import {unusedValueExportToPlacateAjd as unused2} from './interfaces/injector';
|
||||
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node';
|
||||
import {LQueries, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
|
||||
import {CONTENT_QUERIES, HEADER_OFFSET, LView, QUERIES, TVIEW} from './interfaces/view';
|
||||
import {getCurrentQueryIndex, getIsParent, getLView, setCurrentQueryIndex} from './state';
|
||||
import {getCurrentQueryIndex, getIsParent, getLView, isCreationMode, setCurrentQueryIndex} from './state';
|
||||
import {createElementRef, createTemplateRef} from './view_engine_compatibility';
|
||||
|
||||
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4;
|
||||
@ -338,7 +338,7 @@ function createQuery<T>(
|
||||
};
|
||||
}
|
||||
|
||||
type QueryList_<T> = QueryList<T>& {_valuesTree: any[]};
|
||||
type QueryList_<T> = QueryList<T>& {_valuesTree: any[], _static: boolean};
|
||||
|
||||
/**
|
||||
* Creates and returns a QueryList.
|
||||
@ -350,12 +350,13 @@ type QueryList_<T> = QueryList<T>& {_valuesTree: any[]};
|
||||
*/
|
||||
export function query<T>(
|
||||
// TODO: "read" should be an AbstractType (FW-486)
|
||||
predicate: Type<any>| string[], descend?: boolean, read?: any): QueryList<T> {
|
||||
predicate: Type<any>| string[], descend: boolean, read: any): QueryList<T> {
|
||||
ngDevMode && assertPreviousIsParent(getIsParent());
|
||||
const lView = getLView();
|
||||
const queryList = new QueryList<T>();
|
||||
const queryList = new QueryList<T>() as QueryList_<T>;
|
||||
const queries = lView[QUERIES] || (lView[QUERIES] = new LQueries_(null, null, null));
|
||||
(queryList as QueryList_<T>)._valuesTree = [];
|
||||
queryList._valuesTree = [];
|
||||
queryList._static = false;
|
||||
queries.track(queryList, predicate, descend, read);
|
||||
storeCleanupWithContext(lView, queryList, queryList.destroy);
|
||||
return queryList;
|
||||
@ -368,7 +369,10 @@ export function query<T>(
|
||||
*/
|
||||
export function queryRefresh(queryList: QueryList<any>): boolean {
|
||||
const queryListImpl = (queryList as any as QueryList_<any>);
|
||||
if (queryList.dirty) {
|
||||
const creationMode = isCreationMode();
|
||||
|
||||
// if creation mode and static or update mode and not static
|
||||
if (queryList.dirty && creationMode === queryListImpl._static) {
|
||||
queryList.reset(queryListImpl._valuesTree || []);
|
||||
queryList.notifyOnChanges();
|
||||
return true;
|
||||
@ -376,6 +380,24 @@ export function queryRefresh(queryList: QueryList<any>): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new QueryList for a static view query.
|
||||
*
|
||||
* @param predicate The type for which the query will search
|
||||
* @param descend Whether or not to descend into children
|
||||
* @param read What to save in the query
|
||||
*/
|
||||
export function staticViewQuery<T>(
|
||||
// TODO(FW-486): "read" should be an AbstractType
|
||||
predicate: Type<any>| string[], descend: boolean, read: any): void {
|
||||
const queryList = viewQuery(predicate, descend, read) as QueryList_<T>;
|
||||
const tView = getLView()[TVIEW];
|
||||
queryList._static = true;
|
||||
if (!tView.staticViewQueries) {
|
||||
tView.staticViewQueries = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new QueryList, stores the reference in LView and returns QueryList.
|
||||
*
|
||||
@ -385,8 +407,8 @@ export function queryRefresh(queryList: QueryList<any>): boolean {
|
||||
* @returns QueryList<T>
|
||||
*/
|
||||
export function viewQuery<T>(
|
||||
// TODO: "read" should be an AbstractType (FW-486)
|
||||
predicate: Type<any>| string[], descend?: boolean, read?: any): QueryList<T> {
|
||||
// TODO(FW-486): "read" should be an AbstractType
|
||||
predicate: Type<any>| string[], descend: boolean, read: any): QueryList<T> {
|
||||
const lView = getLView();
|
||||
const tView = lView[TVIEW];
|
||||
if (tView.firstTemplatePass) {
|
||||
@ -419,9 +441,9 @@ export function loadViewQuery<T>(): T {
|
||||
* @returns QueryList<T>
|
||||
*/
|
||||
export function contentQuery<T>(
|
||||
directiveIndex: number, predicate: Type<any>| string[], descend?: boolean,
|
||||
directiveIndex: number, predicate: Type<any>| string[], descend: boolean,
|
||||
// TODO: "read" should be an AbstractType (FW-486)
|
||||
read?: any): QueryList<T> {
|
||||
read: any): QueryList<T> {
|
||||
const lView = getLView();
|
||||
const tView = lView[TVIEW];
|
||||
const contentQuery: QueryList<T> = query<T>(predicate, descend, read);
|
||||
@ -448,4 +470,4 @@ export function loadContentQuery<T>(): QueryList<T> {
|
||||
|
||||
setCurrentQueryIndex(index + 1);
|
||||
return lView[CONTENT_QUERIES] ![index];
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user