Kara Erickson 9b93bd625f fix(ivy): ensure views created in constructors dont break queries (#29983)
Previous to this change, we assumed embedded views could only be created after
their parent template node had completed processing. As a result, we only set
up query logic for containers after directives on the node were created.
However, this assumption didn't take into account the case where a directive
on a template node could create views in its constructor.

This commit fixes query logic to work with views created in constructors.
In that case, we need to create a query container before the new view is
rendered so query results in the view aren't lost. But since the query container
is created before directives have completed processing, we also have to ensure
that query results gathered later on the template node are inserted before that
query container. Otherwise, query results in embedded views will clobber query
results on template nodes.

This splice mode may be slightly slower than the normal matching for queries on
containers, but we should only fall back to this strategy in the edge case where
views are created in constructors. (We should encourage developers to create
views in ngOnInit instead).

PR Close #29983
2019-04-19 10:01:32 -07:00

87 lines
3.1 KiB
TypeScript

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Type} from '../../interface/type';
import {QueryList} from '../../linker';
import {TContainerNode, TElementContainerNode, TElementNode, TNode} from './node';
/** Used for tracking queries (e.g. ViewChild, ContentChild). */
export interface LQueries {
/**
* The parent LQueries instance.
*
* When there is a content query, a new LQueries instance is created to avoid mutating any
* existing LQueries. After we are done searching content children, the parent property allows
* us to traverse back up to the original LQueries instance to continue to search for matches
* in the main view.
*/
parent: LQueries|null;
/**
* Ask queries to prepare copy of itself. This assures that tracking new queries on content nodes
* doesn't mutate list of queries tracked on a parent node. We will clone LQueries before
* constructing content queries.
*/
clone(): LQueries;
/**
* Notify `LQueries` that a new `TNode` has been created and needs to be added to query results
* if matching query predicate.
*/
addNode(tNode: TElementNode|TContainerNode|TElementContainerNode): void;
/**
* Notify `LQueries` that a new `TNode` has been created and needs to be added to query results
* if matching query predicate. This is a special mode invoked if the query container has to
* be created out of order (e.g. view created in the constructor of a directive).
*/
insertNodeBeforeViews(tNode: TElementNode|TContainerNode|TElementContainerNode): void;
/**
* Notify `LQueries` that a new LContainer was added to ivy data structures. As a result we need
* to prepare room for views that might be inserted into this container.
*/
container(): LQueries|null;
/**
* Notify `LQueries` that a new `LView` has been created. As a result we need to prepare room
* and collect nodes that match query predicate.
*/
createView(): LQueries|null;
/**
* Notify `LQueries` that a new `LView` has been added to `LContainer`. As a result all
* the matching nodes from this view should be added to container's queries.
*/
insertView(newViewIndex: number): void;
/**
* Notify `LQueries` that an `LView` has been removed from `LContainer`. As a result all
* the matching nodes from this view should be removed from container's queries.
*/
removeView(): void;
/**
* Add additional `QueryList` to track.
*
* @param queryList `QueryList` to update with changes.
* @param predicate Either `Type` or selector array of [key, value] predicates.
* @param descend If true the query will recursively apply to the children.
* @param read Indicates which token should be read from DI for this query.
*/
track<T>(
queryList: QueryList<T>, predicate: Type<any>|string[], descend?: boolean,
read?: Type<T>): void;
}
// Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types.
export const unusedValueExportToPlacateAjd = 1;