fix(ivy): support queries for views inserted in lifecycle hooks (#24587)
Closes #23707 PR Close #24587
This commit is contained in:
parent
1e6a226703
commit
3e1a3b2e32
|
@ -192,7 +192,7 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S
|
|||
| `@ContentChildren` | ✅ | ✅ | ❌ |
|
||||
| `@ContentChild` | ✅ | ✅ | ✅ |
|
||||
| `@ViewChildren` | ✅ | ✅ | ❌ |
|
||||
| `@ViewChild` | ✅ | ✅ | ✅ |
|
||||
| `@ViewChild` | ✅ | ✅ | ❌ |
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ export function renderComponent<T>(
|
|||
|
||||
const rootView: LViewData = createLViewData(
|
||||
rendererFactory.createRenderer(hostNode, componentDef.rendererType),
|
||||
createTView(-1, null, null, null), rootContext,
|
||||
createTView(-1, null, null, null, null), rootContext,
|
||||
componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways);
|
||||
rootView[INJECTOR] = opts.injector || null;
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
|||
// Create the root view. Uses empty TView and ContentTemplate.
|
||||
const rootView: LViewData = createLViewData(
|
||||
rendererFactory.createRenderer(hostNode, this.componentDef.rendererType),
|
||||
createTView(-1, null, null, null), null,
|
||||
createTView(-1, null, null, null, null), null,
|
||||
this.componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways);
|
||||
rootView[INJECTOR] = ngModule && ngModule.injector || null;
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import {SimpleChange} from '../change_detection/change_detection_util';
|
||||
import {ChangeDetectionStrategy} from '../change_detection/constants';
|
||||
import {PipeTransform} from '../change_detection/pipe_transform';
|
||||
import {Provider} from '../core';
|
||||
import {OnChanges, SimpleChanges} from '../metadata/lifecycle_hooks';
|
||||
import {NgModuleDef, NgModuleDefInternal} from '../metadata/ng_module';
|
||||
|
@ -17,7 +16,7 @@ import {Type} from '../type';
|
|||
import {resolveRendererType2} from '../view/util';
|
||||
|
||||
import {diPublic} from './di';
|
||||
import {ComponentDefFeature, ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDefFeature, DirectiveDefInternal, DirectiveDefListOrFactory, DirectiveType, DirectiveTypesOrFactory, PipeDef, PipeType, PipeTypesOrFactory} from './interfaces/definition';
|
||||
import {ComponentDefFeature, ComponentDefInternal, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDefFeature, DirectiveDefInternal, DirectiveDefListOrFactory, DirectiveType, DirectiveTypesOrFactory, PipeDef, PipeType, PipeTypesOrFactory} from './interfaces/definition';
|
||||
import {CssSelectorList, SelectorFlags} from './interfaces/projection';
|
||||
|
||||
|
||||
|
@ -126,6 +125,16 @@ export function defineComponent<T>(componentDefinition: {
|
|||
*/
|
||||
template: ComponentTemplate<T>;
|
||||
|
||||
/**
|
||||
* Additional set of instructions specific to view query processing. This could be seen as a
|
||||
* set of instruction to be inserted into the template function.
|
||||
*
|
||||
* Query-related instructions need to be pulled out to a specific function as a timing of
|
||||
* execution is different as compared to all other instructions (after change detection hooks but
|
||||
* before view hooks).
|
||||
*/
|
||||
viewQuery?: ComponentQuery<T>| null;
|
||||
|
||||
/**
|
||||
* A list of optional features to apply.
|
||||
*
|
||||
|
@ -193,7 +202,8 @@ export function defineComponent<T>(componentDefinition: {
|
|||
pipeDefs: pipeTypes ?
|
||||
() => (typeof pipeTypes === 'function' ? pipeTypes() : pipeTypes).map(extractPipeDef) :
|
||||
null,
|
||||
selectors: componentDefinition.selectors
|
||||
selectors: componentDefinition.selectors,
|
||||
viewQuery: componentDefinition.viewQuery || null,
|
||||
};
|
||||
const feature = componentDefinition.features;
|
||||
feature && feature.forEach((fn) => fn(def));
|
||||
|
|
|
@ -23,7 +23,7 @@ import {AttributeMarker, TAttributes, LContainerNode, LElementNode, LNode, TNode
|
|||
import {assertNodeType} from './node_assert';
|
||||
import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode, getParentLNode, getLViewChild} from './node_manipulation';
|
||||
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
||||
import {ComponentDefInternal, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
|
||||
import {ComponentDefInternal, ComponentTemplate, ComponentQuery, DirectiveDefInternal, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
|
||||
import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
||||
import {isDifferent, stringify} from './util';
|
||||
import {ViewRef} from './view_ref';
|
||||
|
@ -446,7 +446,7 @@ export function renderTemplate<T>(
|
|||
if (host == null) {
|
||||
resetApplicationState();
|
||||
rendererFactory = providedRendererFactory;
|
||||
const tView = getOrCreateTView(template, directives || null, pipes || null);
|
||||
const tView = getOrCreateTView(template, directives || null, pipes || null, null);
|
||||
host = createLNode(
|
||||
-1, TNodeType.Element, hostNode, null, null,
|
||||
createLViewData(
|
||||
|
@ -809,7 +809,7 @@ function saveResolvedLocalsInData(): void {
|
|||
*/
|
||||
function getOrCreateTView(
|
||||
template: ComponentTemplate<any>, directives: DirectiveDefListOrFactory | null,
|
||||
pipes: PipeDefListOrFactory | null): TView {
|
||||
pipes: PipeDefListOrFactory | null, viewQuery: ComponentQuery<any>| null): TView {
|
||||
// TODO(misko): reading `ngPrivateData` here is problematic for two reasons
|
||||
// 1. It is a megamorphic call on each invocation.
|
||||
// 2. For nested embedded views (ngFor inside ngFor) the template instance is per
|
||||
|
@ -818,7 +818,7 @@ function getOrCreateTView(
|
|||
// and not on embedded templates.
|
||||
|
||||
return template.ngPrivateData ||
|
||||
(template.ngPrivateData = createTView(-1, template, directives, pipes) as never);
|
||||
(template.ngPrivateData = createTView(-1, template, directives, pipes, viewQuery) as never);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -830,11 +830,13 @@ function getOrCreateTView(
|
|||
*/
|
||||
export function createTView(
|
||||
viewIndex: number, template: ComponentTemplate<any>| null,
|
||||
directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null): TView {
|
||||
directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null,
|
||||
viewQuery: ComponentQuery<any>| null): TView {
|
||||
ngDevMode && ngDevMode.tView++;
|
||||
return {
|
||||
id: viewIndex,
|
||||
template: template,
|
||||
viewQuery: viewQuery,
|
||||
node: null !,
|
||||
data: HEADER_FILLER.slice(), // Fill in to match HEADER_OFFSET in LViewData
|
||||
childIndex: -1, // Children set in addToViewTree(), if any
|
||||
|
@ -937,8 +939,8 @@ export function hostElement(
|
|||
const node = createLNode(
|
||||
0, TNodeType.Element, rNode, null, null,
|
||||
createLViewData(
|
||||
renderer, getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs), null,
|
||||
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, sanitizer));
|
||||
renderer, getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs, def.viewQuery),
|
||||
null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, sanitizer));
|
||||
|
||||
if (firstTemplatePass) {
|
||||
node.tNode.flags = TNodeFlags.isComponent;
|
||||
|
@ -1418,7 +1420,7 @@ export function directiveCreate<T>(
|
|||
|
||||
function addComponentLogic<T>(
|
||||
directiveIndex: number, instance: T, def: ComponentDefInternal<T>): void {
|
||||
const tView = getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs);
|
||||
const tView = getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs, def.viewQuery);
|
||||
|
||||
// Only component views should be added to the view tree directly. Embedded views are
|
||||
// accessed through their containers because they may be removed / re-added later.
|
||||
|
@ -1608,8 +1610,9 @@ export function container(
|
|||
appendChild(getParentLNode(node), comment, viewData);
|
||||
|
||||
if (firstTemplatePass) {
|
||||
node.tNode.tViews =
|
||||
template ? createTView(-1, template, tView.directiveRegistry, tView.pipeRegistry) : [];
|
||||
node.tNode.tViews = template ?
|
||||
createTView(-1, template, tView.directiveRegistry, tView.pipeRegistry, null) :
|
||||
[];
|
||||
}
|
||||
|
||||
// Containers are added to the current view tree instead of their embedded views
|
||||
|
@ -1775,7 +1778,7 @@ function getOrCreateEmbeddedTView(viewIndex: number, parent: LContainerNode): TV
|
|||
ngDevMode && assertEqual(Array.isArray(containerTViews), true, 'TViews should be in an array');
|
||||
if (viewIndex >= containerTViews.length || containerTViews[viewIndex] == null) {
|
||||
containerTViews[viewIndex] =
|
||||
createTView(viewIndex, null, tView.directiveRegistry, tView.pipeRegistry);
|
||||
createTView(viewIndex, null, tView.directiveRegistry, tView.pipeRegistry, null);
|
||||
}
|
||||
return containerTViews[viewIndex];
|
||||
}
|
||||
|
@ -2198,17 +2201,34 @@ export function checkNoChanges<T>(component: T): void {
|
|||
export function detectChangesInternal<T>(
|
||||
hostView: LViewData, hostNode: LElementNode, component: T) {
|
||||
const oldView = enterView(hostView, hostNode);
|
||||
const template = hostView[TVIEW].template !;
|
||||
const hostTView = hostView[TVIEW];
|
||||
const template = hostTView.template !;
|
||||
const viewQuery = hostTView.viewQuery;
|
||||
|
||||
try {
|
||||
namespaceHTML();
|
||||
createViewQuery(viewQuery, hostView[FLAGS], component);
|
||||
template(getRenderFlags(hostView), component);
|
||||
refreshView();
|
||||
updateViewQuery(viewQuery, component);
|
||||
} finally {
|
||||
leaveView(oldView);
|
||||
}
|
||||
}
|
||||
|
||||
function createViewQuery<T>(
|
||||
viewQuery: ComponentQuery<{}>| null, flags: LViewFlags, component: T): void {
|
||||
if (viewQuery && (flags & LViewFlags.CreationMode)) {
|
||||
viewQuery(RenderFlags.Create, component);
|
||||
}
|
||||
}
|
||||
|
||||
function updateViewQuery<T>(viewQuery: ComponentQuery<{}>| null, component: T): void {
|
||||
if (viewQuery) {
|
||||
viewQuery(RenderFlags.Update, component);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mark the component as dirty (needing change detection).
|
||||
|
|
|
@ -18,6 +18,11 @@ export type ComponentTemplate<T> = {
|
|||
(rf: RenderFlags, ctx: T): void; ngPrivateData?: never;
|
||||
};
|
||||
|
||||
/**
|
||||
* Definition of what a query function should look like.
|
||||
*/
|
||||
export type ComponentQuery<T> = ComponentTemplate<T>;
|
||||
|
||||
/**
|
||||
* Flags passed into template functions to determine which blocks (i.e. creation, update)
|
||||
* should be executed.
|
||||
|
@ -153,15 +158,16 @@ export type ComponentDefInternal<T> = ComponentDef<T, string>;
|
|||
export interface ComponentDef<T, Selector extends string> extends DirectiveDef<T, Selector> {
|
||||
/**
|
||||
* The View template of the component.
|
||||
*
|
||||
* NOTE: only used with component directives.
|
||||
*/
|
||||
readonly template: ComponentTemplate<T>;
|
||||
|
||||
/**
|
||||
* Query-related instructions for a component.
|
||||
*/
|
||||
readonly viewQuery: ComponentQuery<T>|null;
|
||||
|
||||
/**
|
||||
* Renderer type data of the component.
|
||||
*
|
||||
* NOTE: only used with component directives.
|
||||
*/
|
||||
readonly rendererType: RendererType2|null;
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import {Injector} from '../../di/injector';
|
|||
import {Sanitizer} from '../../sanitization/security';
|
||||
|
||||
import {LContainer} from './container';
|
||||
import {ComponentTemplate, DirectiveDefInternal, DirectiveDefList, PipeDef, PipeDefList} from './definition';
|
||||
import {ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefList, PipeDef, PipeDefList} from './definition';
|
||||
import {LElementNode, LViewNode, TNode} from './node';
|
||||
import {LQueries} from './query';
|
||||
import {Renderer3} from './renderer';
|
||||
|
@ -200,6 +200,11 @@ export interface TView {
|
|||
*/
|
||||
template: ComponentTemplate<{}>|null;
|
||||
|
||||
/**
|
||||
* A function containing query-related instructions.
|
||||
*/
|
||||
viewQuery: ComponentQuery<{}>|null;
|
||||
|
||||
/**
|
||||
* Pointer to the `TNode` that represents the root of the view.
|
||||
*
|
||||
|
|
|
@ -119,6 +119,9 @@
|
|||
{
|
||||
"name": "createTextNode"
|
||||
},
|
||||
{
|
||||
"name": "createViewQuery"
|
||||
},
|
||||
{
|
||||
"name": "defineComponent"
|
||||
},
|
||||
|
@ -221,6 +224,9 @@
|
|||
{
|
||||
"name": "text"
|
||||
},
|
||||
{
|
||||
"name": "updateViewQuery"
|
||||
},
|
||||
{
|
||||
"name": "viewAttached"
|
||||
}
|
||||
|
|
|
@ -266,6 +266,9 @@
|
|||
{
|
||||
"name": "createTextNode"
|
||||
},
|
||||
{
|
||||
"name": "createViewQuery"
|
||||
},
|
||||
{
|
||||
"name": "defineComponent"
|
||||
},
|
||||
|
@ -548,6 +551,9 @@
|
|||
{
|
||||
"name": "tickRootContext"
|
||||
},
|
||||
{
|
||||
"name": "updateViewQuery"
|
||||
},
|
||||
{
|
||||
"name": "viewAttached"
|
||||
},
|
||||
|
|
|
@ -54,13 +54,17 @@ describe('queries', () => {
|
|||
factory: function ViewQueryComponent_Factory() { return new ViewQueryComponent(); },
|
||||
template: function ViewQueryComponent_Template(
|
||||
rf: $RenderFlags$, ctx: $ViewQueryComponent$) {
|
||||
let $tmp$: any;
|
||||
if (rf & 1) {
|
||||
$r3$.ɵEe(2, 'div', $e1_attrs$);
|
||||
}
|
||||
},
|
||||
viewQuery: function ViewQueryComponent_Query(rf: $RenderFlags$, ctx: $ViewQueryComponent$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵQ(0, SomeDirective, false);
|
||||
$r3$.ɵQ(1, SomeDirective, false);
|
||||
$r3$.ɵEe(2, 'div', $e1_attrs$);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$: any;
|
||||
$r3$.ɵqR($tmp$ = $r3$.ɵld<QueryList<any>>(0)) && (ctx.someDir = $tmp$.first);
|
||||
$r3$.ɵqR($tmp$ = $r3$.ɵld<QueryList<any>>(1)) &&
|
||||
(ctx.someDirList = $tmp$ as QueryList<any>);
|
||||
|
|
|
@ -1403,8 +1403,8 @@ describe('di', () => {
|
|||
|
||||
describe('getOrCreateNodeInjector', () => {
|
||||
it('should handle initial undefined state', () => {
|
||||
const contentView =
|
||||
createLViewData(null !, createTView(-1, null, null, null), null, LViewFlags.CheckAlways);
|
||||
const contentView = createLViewData(
|
||||
null !, createTView(-1, null, null, null, null), null, LViewFlags.CheckAlways);
|
||||
const oldView = enterView(contentView, null !);
|
||||
try {
|
||||
const parent = createLNode(0, TNodeType.Element, null, null, null, null);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -228,7 +228,8 @@ export function toHtml<T>(componentOrElement: T | RElement): string {
|
|||
|
||||
export function createComponent(
|
||||
name: string, template: ComponentTemplate<any>, directives: DirectiveTypesOrFactory = [],
|
||||
pipes: PipeTypesOrFactory = []): ComponentType<any> {
|
||||
pipes: PipeTypesOrFactory = [],
|
||||
viewQuery: ComponentTemplate<any>| null = null): ComponentType<any> {
|
||||
return class Component {
|
||||
value: any;
|
||||
static ngComponentDef = defineComponent({
|
||||
|
@ -236,6 +237,7 @@ export function createComponent(
|
|||
selectors: [[name]],
|
||||
factory: () => new Component,
|
||||
template: template,
|
||||
viewQuery: viewQuery,
|
||||
features: [PublicFeature],
|
||||
directives: directives,
|
||||
pipes: pipes
|
||||
|
|
Loading…
Reference in New Issue