refactor(ivy): remove directive references from template (#22986)
PR Close #22986
This commit is contained in:

committed by
Matias Niemelä

parent
2aabbc51fa
commit
910a16a1ff
@ -14,7 +14,7 @@ import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_facto
|
||||
|
||||
import {assertComponentType, assertNotNull} from './assert';
|
||||
import {queueInitHooks, queueLifecycleHooks} from './hooks';
|
||||
import {CLEAN_PROMISE, _getComponentHostLElementNode, baseDirectiveCreate, createLView, createTView, enterView, getRootView, hostElement, initChangeDetectorIfExisting, locateHostElement, renderComponentOrTemplate} from './instructions';
|
||||
import {CLEAN_PROMISE, ROOT_DIRECTIVE_INDICES, _getComponentHostLElementNode, baseDirectiveCreate, createLView, createTView, detectChangesInternal, enterView, executeInitAndContentHooks, getRootView, hostElement, initChangeDetectorIfExisting, leaveView, locateHostElement, setHostBindings} from './instructions';
|
||||
import {ComponentDef, ComponentType} from './interfaces/definition';
|
||||
import {LElementNode, TNodeFlags} from './interfaces/node';
|
||||
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
||||
@ -123,7 +123,9 @@ export function renderComponent<T>(
|
||||
const componentDef = (componentType as ComponentType<T>).ngComponentDef as ComponentDef<T>;
|
||||
if (componentDef.type != componentType) componentDef.type = componentType;
|
||||
let component: T;
|
||||
const hostNode = locateHostElement(rendererFactory, opts.host || componentDef.tag);
|
||||
// TODO: Replace when flattening CssSelector type
|
||||
const componentTag = componentDef.selector ![0] ![0] ![0];
|
||||
const hostNode = locateHostElement(rendererFactory, opts.host || componentTag);
|
||||
const rootContext: RootContext = {
|
||||
// Incomplete initialization due to circular reference.
|
||||
component: null !,
|
||||
@ -131,28 +133,32 @@ export function renderComponent<T>(
|
||||
clean: CLEAN_PROMISE,
|
||||
};
|
||||
const rootView = createLView(
|
||||
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), createTView(), null,
|
||||
rootContext, componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways);
|
||||
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), createTView(null),
|
||||
null, rootContext, componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways);
|
||||
|
||||
const oldView = enterView(rootView, null !);
|
||||
|
||||
let elementNode: LElementNode;
|
||||
try {
|
||||
if (rendererFactory.begin) rendererFactory.begin();
|
||||
|
||||
// Create element node at index 0 in data array
|
||||
elementNode = hostElement(hostNode, componentDef);
|
||||
elementNode = hostElement(componentTag, hostNode, componentDef);
|
||||
|
||||
// Create directive instance with factory() and store at index 0 in directives array
|
||||
component = rootContext.component =
|
||||
baseDirectiveCreate(0, componentDef.factory(), componentDef) as T;
|
||||
initChangeDetectorIfExisting(elementNode.nodeInjector, component);
|
||||
initChangeDetectorIfExisting(elementNode.nodeInjector, component, elementNode.data !);
|
||||
|
||||
opts.hostFeatures && opts.hostFeatures.forEach((feature) => feature(component, componentDef));
|
||||
|
||||
executeInitAndContentHooks();
|
||||
setHostBindings(ROOT_DIRECTIVE_INDICES);
|
||||
detectChangesInternal(elementNode.data as LView, elementNode, componentDef, component);
|
||||
} finally {
|
||||
// We must not use leaveView here because it will set creationMode to false too early,
|
||||
// causing init-only hooks not to run. The detectChanges call below will execute
|
||||
// leaveView at the appropriate time in the lifecycle.
|
||||
enterView(oldView, null);
|
||||
leaveView(oldView);
|
||||
if (rendererFactory.end) rendererFactory.end();
|
||||
}
|
||||
|
||||
opts.hostFeatures && opts.hostFeatures.forEach((feature) => feature(component, componentDef));
|
||||
renderComponentOrTemplate(elementNode, rootView, component);
|
||||
return component;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,8 @@ import {Type} from '../type';
|
||||
import {resolveRendererType2} from '../view/util';
|
||||
|
||||
import {diPublic} from './di';
|
||||
import {ComponentDef, ComponentDefFeature, ComponentTemplate, DirectiveDef, DirectiveDefFeature, PipeDef} from './interfaces/definition';
|
||||
import {ComponentDef, ComponentDefFeature, ComponentTemplate, DirectiveDef, DirectiveDefFeature, DirectiveDefListOrFactory, PipeDef} from './interfaces/definition';
|
||||
import {CssSelector} from './interfaces/projection';
|
||||
|
||||
|
||||
|
||||
@ -41,6 +42,9 @@ export function defineComponent<T>(componentDefinition: {
|
||||
*/
|
||||
type: Type<T>;
|
||||
|
||||
/** The selector that will be used to match nodes to this component. */
|
||||
selector: CssSelector;
|
||||
|
||||
/**
|
||||
* Factory method used to create an instance of directive.
|
||||
*/
|
||||
@ -90,11 +94,6 @@ export function defineComponent<T>(componentDefinition: {
|
||||
*/
|
||||
exportAs?: string;
|
||||
|
||||
/**
|
||||
* HTML tag name to use in place where this component should be instantiated.
|
||||
*/
|
||||
tag: string;
|
||||
|
||||
/**
|
||||
* Template function use for rendering DOM.
|
||||
*
|
||||
@ -147,13 +146,20 @@ export function defineComponent<T>(componentDefinition: {
|
||||
* Defines the set of injectable objects that are visible to its view DOM children.
|
||||
*/
|
||||
viewProviders?: Provider[];
|
||||
|
||||
/**
|
||||
* Registry of directives and components that may be found in this component's view.
|
||||
*
|
||||
* The property is either an array of `DirectiveDef`s or a function which returns the array of
|
||||
* `DirectiveDef`s. The function is necessary to be able to support forward declarations.
|
||||
*/
|
||||
directiveDefs?: DirectiveDefListOrFactory | null;
|
||||
}): ComponentDef<T> {
|
||||
const type = componentDefinition.type;
|
||||
const def = <ComponentDef<any>>{
|
||||
type: type,
|
||||
diPublic: null,
|
||||
factory: componentDefinition.factory,
|
||||
tag: componentDefinition.tag || null !,
|
||||
template: componentDefinition.template || null !,
|
||||
hostBindings: componentDefinition.hostBindings || null,
|
||||
attributes: componentDefinition.attributes || null,
|
||||
@ -168,7 +174,9 @@ export function defineComponent<T>(componentDefinition: {
|
||||
afterViewInit: type.prototype.ngAfterViewInit || null,
|
||||
afterViewChecked: type.prototype.ngAfterViewChecked || null,
|
||||
onDestroy: type.prototype.ngOnDestroy || null,
|
||||
onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush
|
||||
onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush,
|
||||
directiveDefs: componentDefinition.directiveDefs || null,
|
||||
selector: componentDefinition.selector
|
||||
};
|
||||
const feature = componentDefinition.features;
|
||||
feature && feature.forEach((fn) => fn(def));
|
||||
@ -300,6 +308,9 @@ export const defineDirective = defineComponent as any as<T>(directiveDefinition:
|
||||
*/
|
||||
type: Type<T>;
|
||||
|
||||
/** The selector that will be used to match nodes to this directive. */
|
||||
selector: CssSelector;
|
||||
|
||||
/**
|
||||
* Factory method used to create an instance of directive.
|
||||
*/
|
||||
|
@ -19,7 +19,7 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_Vie
|
||||
import {Type} from '../type';
|
||||
|
||||
import {assertLessThan, assertNotNull} from './assert';
|
||||
import {assertPreviousIsParent, getDirectiveInstance, getPreviousOrParentNode, getRenderer, renderEmbeddedTemplate} from './instructions';
|
||||
import {assertPreviousIsParent, enterView, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate} from './instructions';
|
||||
import {ComponentTemplate, DirectiveDef} from './interfaces/definition';
|
||||
import {LInjector} from './interfaces/injector';
|
||||
import {LContainerNode, LElementNode, LNode, LNodeType, LViewNode, TNodeFlags} from './interfaces/node';
|
||||
@ -299,12 +299,10 @@ export function getOrCreateChangeDetectorRef(
|
||||
if (di.changeDetectorRef) return di.changeDetectorRef;
|
||||
|
||||
const currentNode = di.node;
|
||||
if (currentNode.data === null) {
|
||||
// if data is null, this node is a regular element node (not a component)
|
||||
return di.changeDetectorRef = getOrCreateHostChangeDetector(currentNode.view.node);
|
||||
} else if (currentNode.type === LNodeType.Element) {
|
||||
// if it's an element node with data, it's a component and context will be set later
|
||||
if (isComponent(currentNode.tNode !)) {
|
||||
return di.changeDetectorRef = createViewRef(currentNode.data as LView, context);
|
||||
} else if (currentNode.type === LNodeType.Element) {
|
||||
return di.changeDetectorRef = getOrCreateHostChangeDetector(currentNode.view.node);
|
||||
}
|
||||
return null !;
|
||||
}
|
||||
@ -388,7 +386,7 @@ export function getOrCreateInjectable<T>(
|
||||
// The size of the node's directive's list is stored in certain bits of the node's flags,
|
||||
// so exact it with a mask and shift it back such that the bits reflect the real value.
|
||||
const flags = node.tNode !.flags;
|
||||
const size = flags & TNodeFlags.SIZE_MASK;
|
||||
const size = (flags & TNodeFlags.SIZE_MASK) >> TNodeFlags.SIZE_SHIFT;
|
||||
|
||||
if (size !== 0) {
|
||||
// The start index of the directives list is also part of the node's flags, but there is
|
||||
@ -574,6 +572,9 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
|
||||
createEmbeddedView<C>(
|
||||
templateRef: viewEngine_TemplateRef<C>, context?: C|undefined,
|
||||
index?: number|undefined): viewEngine_EmbeddedViewRef<C> {
|
||||
// set current view to container node's view
|
||||
enterView(this._node.view, null);
|
||||
|
||||
const viewRef = templateRef.createEmbeddedView(context !);
|
||||
this.insert(viewRef, index);
|
||||
return viewRef;
|
||||
|
@ -45,7 +45,8 @@ export function queueLifecycleHooks(flags: number, currentView: LView): void {
|
||||
const tView = currentView.tView;
|
||||
if (tView.firstTemplatePass === true) {
|
||||
const start = flags >> TNodeFlags.INDX_SHIFT;
|
||||
const end = start + (flags & TNodeFlags.SIZE_MASK);
|
||||
const size = (flags & TNodeFlags.SIZE_MASK) >> TNodeFlags.SIZE_SHIFT;
|
||||
const end = start + size;
|
||||
|
||||
// It's necessary to loop through the directives at elementEnd() (rather than processing in
|
||||
// directiveCreate) so we can preserve the current hook order. Content, view, and destroy
|
||||
|
@ -18,8 +18,8 @@ import {LView, LViewFlags, LifecycleStage, RootContext, TData, TView} from './in
|
||||
import {LContainerNode, LElementNode, LNode, LNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node';
|
||||
import {assertNodeType} from './node_assert';
|
||||
import {appendChild, insertChild, insertView, appendProjectedNode, removeView, canInsertNativeNode} from './node_manipulation';
|
||||
import {matchingSelectorIndex} from './node_selector_matcher';
|
||||
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType} from './interfaces/definition';
|
||||
import {isNodeMatchingSelector, matchingSelectorIndex} from './node_selector_matcher';
|
||||
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefList, DirectiveDefListOrFactory, DirectiveType} from './interfaces/definition';
|
||||
import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
||||
import {isDifferent, stringify} from './util';
|
||||
import {executeHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks';
|
||||
@ -48,7 +48,7 @@ export type Sanitizer = (value: any) => string;
|
||||
*
|
||||
* Saved here to avoid re-instantiating an array on every change detection run.
|
||||
*/
|
||||
const rootDirectiveIndices = [0, 0];
|
||||
export const _ROOT_DIRECTIVE_INDICES = [0, 0];
|
||||
|
||||
|
||||
/**
|
||||
@ -243,7 +243,7 @@ function refreshDirectives() {
|
||||
}
|
||||
|
||||
/** Sets the host bindings for the current view. */
|
||||
function setHostBindings(bindings: number[] | null): void {
|
||||
export function setHostBindings(bindings: number[] | null): void {
|
||||
if (bindings != null) {
|
||||
const defs = currentView.tView.directives !;
|
||||
for (let i = 0; i < bindings.length; i += 2) {
|
||||
@ -263,7 +263,7 @@ function refreshChildComponents(components: number[] | null): void {
|
||||
}
|
||||
}
|
||||
|
||||
function executeInitAndContentHooks(): void {
|
||||
export function executeInitAndContentHooks(): void {
|
||||
if (!checkNoChangesMode) {
|
||||
const tView = currentView.tView;
|
||||
executeInitHooks(currentView, tView, creationMode);
|
||||
@ -395,21 +395,25 @@ function resetApplicationState() {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param host Existing node to render into.
|
||||
* @param hostNode Existing node to render into.
|
||||
* @param template Template function with the instructions.
|
||||
* @param context to pass into the template.
|
||||
* @param providedRendererFactory renderer factory to use
|
||||
* @param host The host element node to use
|
||||
* @param directiveRegistry Any directive defs that should be used to match nodes to directives
|
||||
*/
|
||||
export function renderTemplate<T>(
|
||||
hostNode: RElement, template: ComponentTemplate<T>, context: T,
|
||||
providedRendererFactory: RendererFactory3, host: LElementNode | null): LElementNode {
|
||||
providedRendererFactory: RendererFactory3, host: LElementNode | null,
|
||||
directiveRegistry: DirectiveDefListOrFactory | null = null): LElementNode {
|
||||
if (host == null) {
|
||||
resetApplicationState();
|
||||
rendererFactory = providedRendererFactory;
|
||||
host = createLNode(
|
||||
null, LNodeType.Element, hostNode,
|
||||
createLView(
|
||||
-1, providedRendererFactory.createRenderer(null, null), getOrCreateTView(template),
|
||||
null, {}, LViewFlags.CheckAlways));
|
||||
-1, providedRendererFactory.createRenderer(null, null),
|
||||
getOrCreateTView(template, directiveRegistry), null, {}, LViewFlags.CheckAlways));
|
||||
}
|
||||
const hostView = host.data !;
|
||||
ngDevMode && assertNotNull(hostView, 'Host node should have an LView defined in host.data.');
|
||||
@ -427,8 +431,9 @@ export function renderEmbeddedTemplate<T>(
|
||||
previousOrParentNode = null !;
|
||||
let cm: boolean = false;
|
||||
if (viewNode == null) {
|
||||
const view =
|
||||
createLView(-1, renderer, createTView(), template, context, LViewFlags.CheckAlways);
|
||||
const view = createLView(
|
||||
-1, renderer, createTView(currentView.tView.directiveRegistry), template, context,
|
||||
LViewFlags.CheckAlways);
|
||||
viewNode = createLNode(null, LNodeType.View, null, view);
|
||||
cm = true;
|
||||
}
|
||||
@ -460,7 +465,7 @@ export function renderComponentOrTemplate<T>(
|
||||
|
||||
// Element was stored at 0 in data and directive was stored at 0 in directives
|
||||
// in renderComponent()
|
||||
setHostBindings(rootDirectiveIndices);
|
||||
setHostBindings(_ROOT_DIRECTIVE_INDICES);
|
||||
componentRefresh(0, 0);
|
||||
}
|
||||
} finally {
|
||||
@ -479,9 +484,8 @@ export function renderComponentOrTemplate<T>(
|
||||
* Create DOM element. The instruction must later be followed by `elementEnd()` call.
|
||||
*
|
||||
* @param index Index of the element in the data array
|
||||
* @param nameOrComponentType Name of the DOM Node or `ComponentType` to create.
|
||||
* @param name Name of the DOM Node
|
||||
* @param attrs Statically bound set of attributes to be written into the DOM element on creation.
|
||||
* @param directiveTypes A set of directives declared on this element.
|
||||
* @param localRefs A set of local reference bindings on the element.
|
||||
*
|
||||
* Attributes and localRefs are passed as an array of strings where elements with an even index
|
||||
@ -489,77 +493,71 @@ export function renderComponentOrTemplate<T>(
|
||||
* ['id', 'warning5', 'class', 'alert']
|
||||
*/
|
||||
export function elementStart(
|
||||
index: number, nameOrComponentType?: string | ComponentType<any>, attrs?: string[] | null,
|
||||
directiveTypes?: DirectiveType<any>[] | null, localRefs?: string[] | null): RElement {
|
||||
index: number, name?: string, attrs?: string[] | null, localRefs?: string[] | null): RElement {
|
||||
let node: LElementNode;
|
||||
let native: RElement;
|
||||
|
||||
if (nameOrComponentType == null) {
|
||||
if (name == null) {
|
||||
// native node retrieval - used for exporting elements as tpl local variables (<div #foo>)
|
||||
const node = data[index] !;
|
||||
native = node && (node as LElementNode).native;
|
||||
} else {
|
||||
ngDevMode &&
|
||||
assertNull(currentView.bindingStartIndex, 'elements should be created before any bindings');
|
||||
const isHostElement = typeof nameOrComponentType !== 'string';
|
||||
|
||||
let hostComponentDef: ComponentDef<any>|null = null;
|
||||
let name = nameOrComponentType as string;
|
||||
native = renderer.createElement(name);
|
||||
node = createLNode(index, LNodeType.Element, native !, null);
|
||||
|
||||
let directiveIndex = getNextDirectiveIndex(index);
|
||||
if (attrs) setUpAttributes(native, attrs);
|
||||
appendChild(node.parent !, native, currentView);
|
||||
|
||||
if (isHostElement) {
|
||||
hostComponentDef = firstTemplatePass ?
|
||||
(nameOrComponentType as ComponentType<any>).ngComponentDef :
|
||||
currentView.tView.directives ![directiveIndex] as ComponentDef<any>;
|
||||
if (firstTemplatePass) {
|
||||
const tNode = createTNode(name, attrs || null, null, null);
|
||||
cacheMatchingDirectivesForNode(tNode);
|
||||
|
||||
name = hostComponentDef !.tag;
|
||||
}
|
||||
|
||||
if (name === null) {
|
||||
// TODO: future support for nameless components.
|
||||
throw 'for now name is required';
|
||||
} else {
|
||||
native = renderer.createElement(name);
|
||||
|
||||
let componentView: LView|null = null;
|
||||
if (isHostElement) {
|
||||
const tView = getOrCreateTView(hostComponentDef !.template);
|
||||
const hostView = createLView(
|
||||
-1, rendererFactory.createRenderer(native, hostComponentDef !.rendererType), tView,
|
||||
null, null, hostComponentDef !.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways);
|
||||
componentView = addToViewTree(hostView);
|
||||
}
|
||||
|
||||
// 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.
|
||||
node = createLNode(index, LNodeType.Element, native, componentView);
|
||||
|
||||
if (node.tNode == null) {
|
||||
const localNames: (string | number)[]|null = findMatchingLocalNames(
|
||||
hostComponentDef, localRefs, isHostElement ? directiveIndex : -1, '');
|
||||
ngDevMode && assertDataInRange(index - 1);
|
||||
node.tNode = tData[index] = createTNode(name, attrs || null, null, localNames);
|
||||
}
|
||||
|
||||
if (attrs) setUpAttributes(native, attrs);
|
||||
appendChild(node.parent !, native, currentView);
|
||||
|
||||
if (hostComponentDef) {
|
||||
// TODO(mhevery): This assumes that the directives come in correct order, which
|
||||
// is not guaranteed. Must be refactored to take it into account.
|
||||
const instance = hostComponentDef.factory();
|
||||
directiveCreate(directiveIndex, index, instance, hostComponentDef, null);
|
||||
initChangeDetectorIfExisting(node.nodeInjector, instance);
|
||||
queueComponentIndexForCheck(directiveIndex, index);
|
||||
directiveIndex++;
|
||||
}
|
||||
hack_declareDirectives(directiveIndex, index, directiveTypes, localRefs);
|
||||
ngDevMode && assertDataInRange(index - 1);
|
||||
node.tNode = tData[index] = tNode;
|
||||
|
||||
if (!isComponent(tNode)) {
|
||||
tNode.localNames = findMatchingLocalNames(null, localRefs, -1, '');
|
||||
}
|
||||
}
|
||||
hack_declareDirectives(index, localRefs);
|
||||
}
|
||||
return native;
|
||||
}
|
||||
|
||||
function cacheMatchingDirectivesForNode(tNode: TNode): void {
|
||||
const registry = currentView.tView.directiveRegistry;
|
||||
const startIndex = directives ? directives.length : 0;
|
||||
|
||||
if (registry) {
|
||||
let componentFlag = 0;
|
||||
let size = 0;
|
||||
|
||||
for (let i = 0; i < registry.length; i++) {
|
||||
const def = registry[i];
|
||||
if (isNodeMatchingSelector(tNode, def.selector !)) {
|
||||
if ((def as ComponentDef<any>).template) {
|
||||
if (componentFlag) throwMultipleComponentError(tNode);
|
||||
componentFlag |= TNodeFlags.Component;
|
||||
}
|
||||
(currentView.tView.directives || (currentView.tView.directives = [])).push(def);
|
||||
size++;
|
||||
}
|
||||
}
|
||||
if (size > 0) buildTNodeFlags(tNode, startIndex, size, componentFlag);
|
||||
}
|
||||
}
|
||||
|
||||
function buildTNodeFlags(tNode: TNode, index: number, size: number, component: number): void {
|
||||
tNode.flags = (index << TNodeFlags.INDX_SHIFT) | (size << TNodeFlags.SIZE_SHIFT) | component;
|
||||
}
|
||||
|
||||
function throwMultipleComponentError(tNode: TNode): never {
|
||||
throw new Error(`Multiple components match node with tagname ${tNode.tagName}`);
|
||||
}
|
||||
|
||||
/** Stores index of component's host element so it will be queued for view refresh during CD. */
|
||||
function queueComponentIndexForCheck(dirIndex: number, elIndex: number): void {
|
||||
if (firstTemplatePass) {
|
||||
@ -576,30 +574,35 @@ function queueHostBindingForCheck(dirIndex: number, elIndex: number): void {
|
||||
}
|
||||
|
||||
/** Sets the context for a ChangeDetectorRef to the given instance. */
|
||||
export function initChangeDetectorIfExisting(injector: LInjector | null, instance: any): void {
|
||||
export function initChangeDetectorIfExisting(
|
||||
injector: LInjector | null, instance: any, view: LView): void {
|
||||
if (injector && injector.changeDetectorRef != null) {
|
||||
(injector.changeDetectorRef as ViewRef<any>)._setComponentContext(instance);
|
||||
(injector.changeDetectorRef as ViewRef<any>)._setComponentContext(view, instance);
|
||||
}
|
||||
}
|
||||
|
||||
export function isComponent(tNode: TNode): boolean {
|
||||
return (tNode.flags & TNodeFlags.Component) === TNodeFlags.Component;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function instantiates the given directives. It is a hack since it assumes the directives
|
||||
* come in the correct order for DI.
|
||||
*/
|
||||
function hack_declareDirectives(
|
||||
index: number, elementIndex: number, directiveTypes: DirectiveType<any>[] | null | undefined,
|
||||
localRefs: string[] | null | undefined, ) {
|
||||
if (directiveTypes) {
|
||||
const defs = currentView.tView.directives !;
|
||||
function hack_declareDirectives(elementIndex: number, localRefs: string[] | null | undefined) {
|
||||
const size = (previousOrParentNode.tNode !.flags & TNodeFlags.SIZE_MASK) >> TNodeFlags.SIZE_SHIFT;
|
||||
|
||||
if (size > 0) {
|
||||
let startIndex = previousOrParentNode.tNode !.flags >> TNodeFlags.INDX_SHIFT;
|
||||
const endIndex = startIndex + size;
|
||||
const tDirectives = currentView.tView.directives !;
|
||||
|
||||
// TODO(mhevery): This assumes that the directives come in correct order, which
|
||||
// is not guaranteed. Must be refactored to take it into account.
|
||||
for (let i = 0; i < directiveTypes.length; i++) {
|
||||
const directiveType = directiveTypes[i];
|
||||
const directiveDef =
|
||||
firstTemplatePass ? directiveType.ngDirectiveDef : defs[index] as DirectiveDef<any>;
|
||||
directiveCreate(index, elementIndex, directiveDef.factory(), directiveDef, localRefs);
|
||||
index++;
|
||||
for (let i = startIndex; i < endIndex; i++) {
|
||||
const def = tDirectives[i] as DirectiveDef<any>;
|
||||
directiveCreate(elementIndex, def.factory(), def, localRefs);
|
||||
startIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -633,12 +636,13 @@ function findMatchingLocalNames(
|
||||
* @param template The template from which to get static data
|
||||
* @returns TView
|
||||
*/
|
||||
function getOrCreateTView(template: ComponentTemplate<any>): TView {
|
||||
return template.ngPrivateData || (template.ngPrivateData = createTView() as never);
|
||||
function getOrCreateTView(
|
||||
template: ComponentTemplate<any>, defs: DirectiveDefListOrFactory | null): TView {
|
||||
return template.ngPrivateData || (template.ngPrivateData = createTView(defs) as never);
|
||||
}
|
||||
|
||||
/** Creates a TView instance */
|
||||
export function createTView(): TView {
|
||||
export function createTView(defs: DirectiveDefListOrFactory | null): TView {
|
||||
return {
|
||||
data: [],
|
||||
directives: null,
|
||||
@ -652,7 +656,8 @@ export function createTView(): TView {
|
||||
destroyHooks: null,
|
||||
pipeDestroyHooks: null,
|
||||
hostBindings: null,
|
||||
components: null
|
||||
components: null,
|
||||
directiveRegistry: typeof defs === 'function' ? defs() : defs
|
||||
};
|
||||
}
|
||||
|
||||
@ -708,13 +713,22 @@ export function locateHostElement(
|
||||
*
|
||||
* @returns LElementNode created
|
||||
*/
|
||||
export function hostElement(rNode: RElement | null, def: ComponentDef<any>): LElementNode {
|
||||
export function hostElement(
|
||||
tag: string, rNode: RElement | null, def: ComponentDef<any>): LElementNode {
|
||||
resetApplicationState();
|
||||
const node = createLNode(
|
||||
0, LNodeType.Element, rNode, createLView(
|
||||
-1, renderer, getOrCreateTView(def.template), null, null,
|
||||
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways));
|
||||
if (firstTemplatePass) node.tNode = createTNode(def.tag, null, null, null);
|
||||
0, LNodeType.Element, rNode,
|
||||
createLView(
|
||||
-1, renderer, getOrCreateTView(def.template, def.directiveDefs), null, null,
|
||||
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways));
|
||||
|
||||
if (firstTemplatePass) {
|
||||
node.tNode = createTNode(tag as string, null, null, null);
|
||||
// Root directive is stored at index 0, size 1
|
||||
buildTNodeFlags(node.tNode, 0, 1, TNodeFlags.Component);
|
||||
currentView.tView.directives = [def];
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@ -898,7 +912,7 @@ function setInputsForProperty(inputs: PropertyAliasValue, value: any): void {
|
||||
*/
|
||||
function generatePropertyAliases(
|
||||
tNodeFlags: TNodeFlags, direction: BindingDirection): PropertyAliases|null {
|
||||
const size = tNodeFlags & TNodeFlags.SIZE_MASK;
|
||||
const size = (tNodeFlags & TNodeFlags.SIZE_MASK) >> TNodeFlags.SIZE_SHIFT;
|
||||
let propStore: PropertyAliases|null = null;
|
||||
|
||||
if (size > 0) {
|
||||
@ -1101,20 +1115,25 @@ export function textBinding<T>(index: number, value: T | NO_CHANGE): void {
|
||||
* NOTE: directives can be created in order other than the index order. They can also
|
||||
* be retrieved before they are created in which case the value will be null.
|
||||
*
|
||||
* @param index Index to save the directive in the directives array
|
||||
* @param elementIndex Index of the host element in the data array
|
||||
* @param directive The directive instance.
|
||||
* @param directiveDef DirectiveDef object which contains information about the template.
|
||||
* @param localRefs Names under which a query can retrieve the directive instance
|
||||
*/
|
||||
export function directiveCreate<T>(
|
||||
index: number, elementIndex: number, directive: T, directiveDef: DirectiveDef<T>,
|
||||
elementIndex: number, directive: T, directiveDef: DirectiveDef<T>| ComponentDef<T>,
|
||||
localRefs?: string[] | null): T {
|
||||
const index = directives ? directives.length : 0;
|
||||
const instance = baseDirectiveCreate(index, directive, directiveDef);
|
||||
|
||||
ngDevMode && assertNotNull(previousOrParentNode.tNode, 'previousOrParentNode.tNode');
|
||||
const tNode: TNode|null = previousOrParentNode.tNode !;
|
||||
|
||||
const isComponent = (directiveDef as ComponentDef<any>).template;
|
||||
if (isComponent) {
|
||||
addComponentLogic(index, elementIndex, directive, directiveDef as ComponentDef<any>);
|
||||
}
|
||||
|
||||
if (firstTemplatePass) {
|
||||
// Init hooks are queued now so ngOnInit is called in host components before
|
||||
// any projected components.
|
||||
@ -1123,7 +1142,8 @@ export function directiveCreate<T>(
|
||||
if (directiveDef.hostBindings) queueHostBindingForCheck(index, elementIndex);
|
||||
|
||||
if (localRefs) {
|
||||
const localNames = findMatchingLocalNames(directiveDef, localRefs, index);
|
||||
const localNames =
|
||||
findMatchingLocalNames(directiveDef, localRefs, index, isComponent ? '' : undefined);
|
||||
tNode.localNames =
|
||||
localNames && tNode.localNames ? tNode.localNames.concat(localNames) : localNames;
|
||||
}
|
||||
@ -1136,6 +1156,26 @@ export function directiveCreate<T>(
|
||||
return instance;
|
||||
}
|
||||
|
||||
function addComponentLogic<T>(
|
||||
index: number, elementIndex: number, instance: T, def: ComponentDef<any>): void {
|
||||
const tView = getOrCreateTView(def.template, def.directiveDefs);
|
||||
|
||||
// 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.
|
||||
const hostView = addToViewTree(createLView(
|
||||
-1, rendererFactory.createRenderer(previousOrParentNode.native as RElement, def.rendererType),
|
||||
tView, null, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways));
|
||||
|
||||
(previousOrParentNode.data as any) = hostView;
|
||||
(hostView.node as any) = previousOrParentNode;
|
||||
|
||||
initChangeDetectorIfExisting(previousOrParentNode.nodeInjector, instance, hostView);
|
||||
|
||||
if (firstTemplatePass) {
|
||||
queueComponentIndexForCheck(index, elementIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A lighter version of directiveCreate() that is used for the root component
|
||||
*
|
||||
@ -1143,7 +1183,7 @@ export function directiveCreate<T>(
|
||||
* current Angular. Example: local refs and inputs on root component.
|
||||
*/
|
||||
export function baseDirectiveCreate<T>(
|
||||
index: number, directive: T, directiveDef: DirectiveDef<T>): T {
|
||||
index: number, directive: T, directiveDef: DirectiveDef<T>| ComponentDef<any>): T {
|
||||
ngDevMode &&
|
||||
assertNull(currentView.bindingStartIndex, 'directives should be created before any bindings');
|
||||
ngDevMode && assertPreviousIsParent();
|
||||
@ -1156,17 +1196,6 @@ export function baseDirectiveCreate<T>(
|
||||
ngDevMode && assertDataNext(index, directives);
|
||||
directives[index] = directive;
|
||||
|
||||
if (firstTemplatePass) {
|
||||
const defs = currentView.tView.directives || (currentView.tView.directives = []);
|
||||
|
||||
ngDevMode && assertDataNext(index, defs);
|
||||
defs[index] = (directiveDef);
|
||||
|
||||
const flags = previousOrParentNode.tNode !.flags;
|
||||
previousOrParentNode.tNode !.flags =
|
||||
(flags & TNodeFlags.SIZE_MASK) === 0 ? (index << TNodeFlags.INDX_SHIFT) | 1 : flags + 1;
|
||||
}
|
||||
|
||||
const diPublic = directiveDef !.diPublic;
|
||||
if (diPublic) {
|
||||
diPublic(directiveDef !);
|
||||
@ -1253,8 +1282,8 @@ function generateInitialInputs(
|
||||
* @param localRefs A set of local reference bindings on the element.
|
||||
*/
|
||||
export function container(
|
||||
index: number, directiveTypes?: DirectiveType<any>[], template?: ComponentTemplate<any>,
|
||||
tagName?: string, attrs?: string[], localRefs?: string[] | null): void {
|
||||
index: number, template?: ComponentTemplate<any>, tagName?: string, attrs?: string[],
|
||||
localRefs?: string[] | null): void {
|
||||
ngDevMode &&
|
||||
assertNull(
|
||||
currentView.bindingStartIndex, 'container nodes should be created before any bindings');
|
||||
@ -1285,7 +1314,9 @@ export function container(
|
||||
// Containers are added to the current view tree instead of their embedded views
|
||||
// because views can be removed and re-inserted.
|
||||
addToViewTree(node.data);
|
||||
hack_declareDirectives(getNextDirectiveIndex(index), index, directiveTypes, localRefs);
|
||||
|
||||
if (firstTemplatePass) cacheMatchingDirectivesForNode(node.tNode);
|
||||
hack_declareDirectives(index, localRefs);
|
||||
|
||||
isParent = false;
|
||||
ngDevMode && assertNodeType(previousOrParentNode, LNodeType.Container);
|
||||
@ -1298,12 +1329,6 @@ export function container(
|
||||
}
|
||||
}
|
||||
|
||||
/** Retrieves the next directive index to write */
|
||||
function getNextDirectiveIndex(index: number): number {
|
||||
return firstTemplatePass ? (directives ? directives.length : 0) :
|
||||
(tData[index] as TNode).flags >> TNodeFlags.INDX_SHIFT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a container up to receive views.
|
||||
*
|
||||
@ -1421,7 +1446,6 @@ export function embeddedViewStart(viewBlockId: number): boolean {
|
||||
|
||||
enterView(newView, createLNode(null, LNodeType.View, null, newView));
|
||||
}
|
||||
|
||||
return !existingViewNode;
|
||||
}
|
||||
|
||||
@ -1441,7 +1465,7 @@ function getOrCreateEmbeddedTView(viewIndex: number, parent: LContainerNode): TV
|
||||
ngDevMode && assertNodeType(parent, LNodeType.Container);
|
||||
const tContainer = (parent !.tNode as TContainerNode).data;
|
||||
if (viewIndex >= tContainer.length || tContainer[viewIndex] == null) {
|
||||
tContainer[viewIndex] = createTView();
|
||||
tContainer[viewIndex] = createTView(currentView.tView.directiveRegistry);
|
||||
}
|
||||
return tContainer[viewIndex];
|
||||
}
|
||||
@ -1487,9 +1511,10 @@ export function componentRefresh<T>(directiveIndex: number, elementIndex: number
|
||||
// Only attached CheckAlways components or attached, dirty OnPush components should be checked
|
||||
if (viewAttached(hostView) && hostView.flags & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
|
||||
ngDevMode && assertDataInRange(directiveIndex, directives !);
|
||||
const template = (currentView.tView.directives ![directiveIndex] as ComponentDef<any>).template;
|
||||
const def = currentView.tView.directives ![directiveIndex] as ComponentDef<any>;
|
||||
|
||||
detectChangesInternal(
|
||||
hostView, element, template, getDirectiveInstance<T>(directives ![directiveIndex]));
|
||||
hostView, element, def, getDirectiveInstance<T>(directives ![directiveIndex]));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1795,8 +1820,8 @@ export function detectChanges<T>(component: T): void {
|
||||
const hostNode = _getComponentHostLElementNode(component);
|
||||
ngDevMode && assertNotNull(hostNode.data, 'Component host node should be attached to an LView');
|
||||
const componentIndex = hostNode.tNode !.flags >> TNodeFlags.INDX_SHIFT;
|
||||
const template = (hostNode.view.tView.directives ![componentIndex] as ComponentDef<T>).template;
|
||||
detectChangesInternal(hostNode.data as LView, hostNode, template, component);
|
||||
const def = hostNode.view.tView.directives ![componentIndex] as ComponentDef<T>;
|
||||
detectChangesInternal(hostNode.data as LView, hostNode, def, component);
|
||||
}
|
||||
|
||||
|
||||
@ -1833,8 +1858,10 @@ function throwErrorIfNoChangesMode(oldValue: any, currValue: any): never|void {
|
||||
|
||||
/** Checks the view of the component provided. Does not gate on dirty checks or execute doCheck. */
|
||||
export function detectChangesInternal<T>(
|
||||
hostView: LView, hostNode: LElementNode, template: ComponentTemplate<any>, component: T) {
|
||||
hostView: LView, hostNode: LElementNode, def: ComponentDef<any>, component: T) {
|
||||
const oldView = enterView(hostView, hostNode);
|
||||
const template = def.template;
|
||||
|
||||
try {
|
||||
template(component, creationMode);
|
||||
refreshDynamicChildren();
|
||||
@ -2150,3 +2177,4 @@ export function _getComponentHostLElementNode<T>(component: T): LElementNode {
|
||||
}
|
||||
|
||||
export const CLEAN_PROMISE = _CLEAN_PROMISE;
|
||||
export const ROOT_DIRECTIVE_INDICES = _ROOT_DIRECTIVE_INDICES;
|
||||
|
@ -12,7 +12,7 @@ import {Provider} from '../../core';
|
||||
import {RendererType2} from '../../render/api';
|
||||
import {Type} from '../../type';
|
||||
import {resolveRendererType2} from '../../view/util';
|
||||
|
||||
import {CssSelector} from './projection';
|
||||
|
||||
|
||||
/**
|
||||
@ -61,6 +61,9 @@ export interface DirectiveDef<T> {
|
||||
/** Function that makes a directive public to the DI system. */
|
||||
diPublic: ((def: DirectiveDef<any>) => void)|null;
|
||||
|
||||
/** The selector that will be used to match nodes to this directive. */
|
||||
selector: CssSelector;
|
||||
|
||||
/**
|
||||
* A dictionary mapping the inputs' minified property names to their public API names, which
|
||||
* are their aliases if any, or their original unminified property names
|
||||
@ -122,13 +125,6 @@ export interface DirectiveDef<T> {
|
||||
* See: {@link defineComponent}
|
||||
*/
|
||||
export interface ComponentDef<T> extends DirectiveDef<T> {
|
||||
/**
|
||||
* The tag name which should be used by the component.
|
||||
*
|
||||
* NOTE: only used with component directives.
|
||||
*/
|
||||
readonly tag: string;
|
||||
|
||||
/**
|
||||
* The View template of the component.
|
||||
*
|
||||
@ -157,6 +153,14 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
|
||||
* children only.
|
||||
*/
|
||||
readonly viewProviders?: Provider[];
|
||||
|
||||
/**
|
||||
* Registry of directives and components that may be found in this view.
|
||||
*
|
||||
* The property is either an array of `DirectiveDef`s or a function which returns the array of
|
||||
* `DirectiveDef`s. The function is necessary to be able to support forward declarations.
|
||||
*/
|
||||
directiveDefs: DirectiveDefListOrFactory|null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -195,6 +199,15 @@ export interface PipeDef<T> {
|
||||
export type DirectiveDefFeature = <T>(directiveDef: DirectiveDef<T>) => void;
|
||||
export type ComponentDefFeature = <T>(componentDef: ComponentDef<T>) => void;
|
||||
|
||||
/**
|
||||
* Type used for directiveDefs on component definition.
|
||||
*
|
||||
* The function is necessary to be able to support forward declarations.
|
||||
*/
|
||||
export type DirectiveDefListOrFactory = (() => DirectiveDefList) | DirectiveDefList;
|
||||
|
||||
export type DirectiveDefList = (DirectiveDef<any>| ComponentDef<any>)[];
|
||||
|
||||
// Note: This hack is necessary so we don't erroneously get a circular dependency
|
||||
// failure based on types.
|
||||
export const unusedValueExportToPlacateAjd = 1;
|
||||
|
@ -32,7 +32,19 @@ export const enum LNodeType {
|
||||
* on how to map a particular set of bits to the node's first directive index
|
||||
* (with INDX_SHIFT) or the node's directive count (with SIZE_MASK)
|
||||
*/
|
||||
export const enum TNodeFlags {INDX_SHIFT = 12, SIZE_MASK = 0b00000000000000000000111111111111}
|
||||
export const enum TNodeFlags {
|
||||
/** Whether or not this node is a component */
|
||||
Component = 0b001,
|
||||
|
||||
/** How far to shift the flags to get the first directive index on this node */
|
||||
INDX_SHIFT = 13,
|
||||
|
||||
/** How far to shift the flags to get the number of directives on this node */
|
||||
SIZE_SHIFT = 1,
|
||||
|
||||
/** Mask to get the number of directives on this node */
|
||||
SIZE_MASK = 0b00000000000000000001111111111110
|
||||
}
|
||||
|
||||
/**
|
||||
* LNode is an internal data structure which is used for the incremental DOM algorithm.
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {LContainer} from './container';
|
||||
import {ComponentDef, ComponentTemplate, DirectiveDef, PipeDef} from './definition';
|
||||
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDef} from './definition';
|
||||
import {LElementNode, LViewNode, TNode} from './node';
|
||||
import {LQueries} from './query';
|
||||
import {Renderer3} from './renderer';
|
||||
@ -219,19 +219,31 @@ export interface LViewOrLContainer {
|
||||
* Stored on the template function as ngPrivateData.
|
||||
*/
|
||||
export interface TView {
|
||||
/** Whether or not this template has been processed. */
|
||||
firstTemplatePass: boolean;
|
||||
|
||||
/** Static data equivalent of LView.data[]. Contains TNodes. */
|
||||
data: TData;
|
||||
|
||||
/**
|
||||
* Directive and component defs for this view
|
||||
* Directive and component defs that have already been matched to nodes on
|
||||
* this view.
|
||||
*
|
||||
* Defs are stored at the same index in TView.directives[] as their instances
|
||||
* are stored in LView.directives[]. This simplifies lookup in DI.
|
||||
*/
|
||||
directives: (ComponentDef<any>|DirectiveDef<any>)[]|null;
|
||||
directives: DirectiveDefList|null;
|
||||
|
||||
/** Whether or not this template has been processed. */
|
||||
firstTemplatePass: boolean;
|
||||
/**
|
||||
* Full registry of directives and components that may be found in this view.
|
||||
*
|
||||
* The property is either an array of `DirectiveDef`s or a function which returns the array of
|
||||
* `DirectiveDef`s. The function is necessary to be able to support forward declarations.
|
||||
*
|
||||
* It's necessary to keep a copy of the full def list on the TView so it's possible
|
||||
* to render template functions without a host component.
|
||||
*/
|
||||
directiveRegistry: DirectiveDefList|null;
|
||||
|
||||
/**
|
||||
* Array of ngOnInit and ngDoCheck hooks that should be executed for this view in
|
||||
|
@ -196,8 +196,8 @@ function getIdxOfMatchingSelector(tNode: TNode, selector: string): number|null {
|
||||
function geIdxOfMatchingDirective(node: LNode, type: Type<any>): number|null {
|
||||
const defs = node.view.tView.directives !;
|
||||
const flags = node.tNode !.flags;
|
||||
for (let i = flags >> TNodeFlags.INDX_SHIFT, ii = i + (flags & TNodeFlags.SIZE_MASK); i < ii;
|
||||
i++) {
|
||||
const size = (flags & TNodeFlags.SIZE_MASK) >> TNodeFlags.SIZE_SHIFT;
|
||||
for (let i = flags >> TNodeFlags.INDX_SHIFT, ii = i + size; i < ii; i++) {
|
||||
const def = defs[i] as DirectiveDef<any>;
|
||||
if (def.diPublic && def.type === type) {
|
||||
return i;
|
||||
|
@ -21,7 +21,10 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T> {
|
||||
constructor(private _view: LView, context: T|null, ) { this.context = context !; }
|
||||
|
||||
/** @internal */
|
||||
_setComponentContext(context: T) { this.context = context; }
|
||||
_setComponentContext(view: LView, context: T) {
|
||||
this._view = view;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
destroy(): void { notImplemented(); }
|
||||
destroyed: boolean;
|
||||
@ -223,9 +226,9 @@ export class EmbeddedViewRef<T> extends ViewRef<T> {
|
||||
* @param context The context for this view
|
||||
* @returns The ViewRef
|
||||
*/
|
||||
export function createViewRef<T>(view: LView, context: T): ViewRef<T> {
|
||||
export function createViewRef<T>(view: LView | null, context: T): ViewRef<T> {
|
||||
// TODO: add detectChanges back in when implementing ChangeDetectorRef.detectChanges
|
||||
return addDestroyable(new ViewRef(view, context));
|
||||
return addDestroyable(new ViewRef(view !, context));
|
||||
}
|
||||
|
||||
/** Interface for destroy logic. Implemented by addDestroyable. */
|
||||
|
Reference in New Issue
Block a user