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:
Andrew Kushnir
2019-01-18 18:02:32 -08:00
committed by Jason Aden
parent 9f9024b7a1
commit 9098225ff0
21 changed files with 550 additions and 384 deletions

View File

@ -81,6 +81,8 @@ export {
containerRefreshStart as ɵcontainerRefreshStart,
containerRefreshEnd as ɵcontainerRefreshEnd,
queryRefresh as ɵqueryRefresh,
viewQuery as ɵviewQuery,
loadViewQuery as ɵloadViewQuery,
loadQueryList as ɵloadQueryList,
elementEnd as ɵelementEnd,
elementProperty as ɵelementProperty,

View File

@ -124,6 +124,8 @@ export {
export {
query,
queryRefresh,
viewQuery,
loadViewQuery,
} from './query';
export {
registerContentQuery,

View File

@ -36,7 +36,7 @@ import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, DECLA
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, getFirstTemplatePass, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setFirstTemplatePass, setIsParent, setPreviousOrParentTNode} from './state';
import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCurrentDirectiveDef, getCurrentViewQueryIndex, getElementDepthCount, getFirstTemplatePass, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentViewQueryIndex, setFirstTemplatePass, setIsParent, setPreviousOrParentTNode} from './state';
import {getInitialClassNameValue, initializeStaticContext as initializeStaticStylingContext, patchContextWithStaticAttrs, renderInitialStylesAndClasses, renderStyling, updateClassProp as updateElementClassProp, updateContextWithBindings, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
import {BoundPlayerFactory} from './styling/player_factory';
import {createEmptyStylingContext, getStylingContext, hasClassInput, hasStyling, isAnimationProp} from './styling/util';
@ -722,6 +722,7 @@ export function createTView(
data: blueprint.slice(), // Fill in to match HEADER_OFFSET in LView
childIndex: -1, // Children set in addToViewTree(), if any
bindingStartIndex: bindingStartIndex,
viewQueryStartIndex: initialViewLength,
expandoStartIndex: initialViewLength,
expandoInstructions: null,
firstTemplatePass: true,
@ -2690,28 +2691,24 @@ export function checkView<T>(hostView: LView, component: T) {
const hostTView = hostView[TVIEW];
const oldView = enterView(hostView, hostView[HOST_NODE]);
const templateFn = hostTView.template !;
const viewQuery = hostTView.viewQuery;
const creationMode = isCreationMode(hostView);
try {
namespaceHTML();
createViewQuery(viewQuery, hostView, component);
creationMode && executeViewQueryFn(hostView, hostTView, component);
templateFn(getRenderFlags(hostView), component);
refreshDescendantViews(hostView);
updateViewQuery(viewQuery, hostView, component);
!creationMode && executeViewQueryFn(hostView, hostTView, component);
} finally {
leaveView(oldView);
}
}
function createViewQuery<T>(viewQuery: ComponentQuery<{}>| null, view: LView, component: T): void {
if (viewQuery && isCreationMode(view)) {
viewQuery(RenderFlags.Create, component);
}
}
function updateViewQuery<T>(viewQuery: ComponentQuery<{}>| null, view: LView, component: T): void {
if (viewQuery && !isCreationMode(view)) {
viewQuery(RenderFlags.Update, component);
function executeViewQueryFn<T>(lView: LView, tView: TView, component: T): void {
const viewQuery = tView.viewQuery;
if (viewQuery) {
setCurrentViewQueryIndex(tView.viewQueryStartIndex);
viewQuery(getRenderFlags(lView), component);
}
}
@ -3072,4 +3069,4 @@ function getTViewCleanup(view: LView): any[] {
function loadComponentRenderer(tNode: TNode, lView: LView): Renderer3 {
const componentLView = lView[tNode.index] as LView;
return componentLView[RENDERER];
}
}

View File

@ -335,6 +335,17 @@ export interface TView {
*/
expandoStartIndex: number;
/**
* The index where the viewQueries section of `LView` begins. This section contains
* view queries defined for a component/directive.
*
* We store this start index so we know where the list of view queries starts.
* This is required when we invoke view queries at runtime. We invoke queries one by one and
* increment query index after each iteration. This information helps us to reset index back to
* the beginning of view query list before we invoke view queries again.
*/
viewQueryStartIndex: number;
/**
* Index of the host node of the first LView or LContainer beneath this LView in
* the hierarchy.

View File

@ -89,6 +89,8 @@ export const angularCoreEnv: {[name: string]: Function} = {
'ɵpipe': r3.pipe,
'ɵquery': r3.query,
'ɵqueryRefresh': r3.queryRefresh,
'ɵviewQuery': r3.viewQuery,
'ɵloadViewQuery': r3.loadViewQuery,
'ɵregisterContentQuery': r3.registerContentQuery,
'ɵreference': r3.reference,
'ɵelementStyling': r3.elementStyling,

View File

@ -18,13 +18,13 @@ import {assertDefined, assertEqual} from '../util/assert';
import {assertPreviousIsParent} from './assert';
import {getNodeInjectable, locateDirectiveOrProvider} from './di';
import {NG_ELEMENT_ID} from './fields';
import {store, storeCleanupWithContext} from './instructions';
import {load, store, storeCleanupWithContext} from './instructions';
import {unusedValueExportToPlacateAjd as unused1} from './interfaces/definition';
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 {LView, TVIEW} from './interfaces/view';
import {getIsParent, getLView, getOrCreateCurrentQueries} from './state';
import {getCurrentViewQueryIndex, getIsParent, getLView, getOrCreateCurrentQueries, setCurrentViewQueryIndex} from './state';
import {isContentQueryHost} from './util';
import {createElementRef, createTemplateRef} from './view_engine_compatibility';
@ -357,26 +357,20 @@ type QueryList_<T> = QueryList<T>& {_valuesTree: any[]};
/**
* Creates and returns a QueryList.
*
* @param memoryIndex The index in memory where the QueryList should be saved. If null,
* this is is a content query and the QueryList will be saved later through directiveCreate.
* @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
* @returns QueryList<T>
*/
export function query<T>(
memoryIndex: number | null, predicate: Type<any>| string[], descend?: boolean,
// TODO: "read" should be an AbstractType (FW-486)
read?: any): QueryList<T> {
predicate: Type<any>| string[], descend?: boolean, read?: any): QueryList<T> {
ngDevMode && assertPreviousIsParent(getIsParent());
const queryList = new QueryList<T>();
const queries = getOrCreateCurrentQueries(LQueries_);
(queryList as QueryList_<T>)._valuesTree = [];
queries.track(queryList, predicate, descend, read);
storeCleanupWithContext(getLView(), queryList, queryList.destroy);
if (memoryIndex != null) {
store(memoryIndex, queryList);
}
return queryList;
}
@ -394,3 +388,35 @@ export function queryRefresh(queryList: QueryList<any>): boolean {
}
return false;
}
/**
* Creates new QueryList, stores the reference in LView and returns QueryList.
*
* @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
* @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> {
const lView = getLView();
const tView = lView[TVIEW];
if (tView.firstTemplatePass) {
tView.expandoStartIndex++;
}
const index = getCurrentViewQueryIndex();
const viewQuery: QueryList<T> = query<T>(predicate, descend, read);
store(index, viewQuery);
setCurrentViewQueryIndex(index + 1);
return viewQuery;
}
/**
* Loads current View Query and moves the pointer/index to the next View Query in LView.
*/
export function loadViewQuery<T>(): T {
const index = getCurrentViewQueryIndex();
setCurrentViewQueryIndex(index + 1);
return load<T>(index);
}

View File

@ -256,6 +256,21 @@ export function setBindingRoot(value: number) {
bindingRootIndex = value;
}
/**
* Current index of a View Query which needs to be processed next.
* We iterate over the list of View Queries stored in LView and increment current query index.
*/
let viewQueryIndex: number = 0;
export function getCurrentViewQueryIndex(): number {
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
return viewQueryIndex;
}
export function setCurrentViewQueryIndex(value: number): void {
viewQueryIndex = value;
}
/**
* Swap the current state with a new state.
*

View File

@ -14,7 +14,7 @@ import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
import {ComponentDef, DirectiveDef} from './interfaces/definition';
import {NO_PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFlags} from './interfaces/injector';
import {TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
import {GlobalTargetName, GlobalTargetResolver, RComment, RElement, RText} from './interfaces/renderer';
import {RComment, RElement, RText} from './interfaces/renderer';
import {StylingContext} from './interfaces/styling';
import {CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, LView, LViewFlags, PARENT, RootContext, TData, TVIEW, TView} from './interfaces/view';