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:
Kara Erickson
2019-02-18 17:33:59 -08:00
committed by Igor Minar
parent ae16378ee7
commit a4638d5a81
26 changed files with 340 additions and 163 deletions

View File

@ -150,6 +150,7 @@ export interface R3QueryMetadataFacade {
predicate: any|string[];
descendants: boolean;
read: any|null;
static: boolean;
}
export interface ParseSourceSpan {

View File

@ -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,

View File

@ -124,6 +124,7 @@ export {
export {
queryRefresh,
viewQuery,
staticViewQuery,
loadViewQuery,
contentQuery,
loadContentQuery,

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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(

View File

@ -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,

View File

@ -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];
}
}