parent
d80e9304c6
commit
628303d2cb
@ -19,7 +19,7 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_Vie
|
|||||||
import {Type} from '../type';
|
import {Type} from '../type';
|
||||||
|
|
||||||
import {assertLessThan, assertNotNull} from './assert';
|
import {assertLessThan, assertNotNull} from './assert';
|
||||||
import {addToViewTree, assertPreviousIsParent, createLContainer, createLNodeObject, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate} from './instructions';
|
import {addToViewTree, assertPreviousIsParent, createLContainer, createLNodeObject, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions';
|
||||||
import {ComponentTemplate, DirectiveDef} from './interfaces/definition';
|
import {ComponentTemplate, DirectiveDef} from './interfaces/definition';
|
||||||
import {LInjector} from './interfaces/injector';
|
import {LInjector} from './interfaces/injector';
|
||||||
import {LContainerNode, LElementNode, LNode, LNodeType, LViewNode, TNodeFlags} from './interfaces/node';
|
import {LContainerNode, LElementNode, LNode, LNodeType, LViewNode, TNodeFlags} from './interfaces/node';
|
||||||
@ -404,8 +404,15 @@ export function getOrCreateInjectable<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we *didn't* find the directive for the token from the candidate injector, we had a false
|
// If we *didn't* find the directive for the token and we are searching the current node's
|
||||||
// positive. Traverse up the tree and continue.
|
// injector, it's possible the directive is on this node and hasn't been created yet.
|
||||||
|
let instance: T|null;
|
||||||
|
if (injector === di && (instance = searchMatchesQueuedForCreation<T>(node, token))) {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The def wasn't found anywhere on this node, so it might be a false positive.
|
||||||
|
// Traverse up the tree and continue searching.
|
||||||
injector = injector.parent;
|
injector = injector.parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -415,6 +422,19 @@ export function getOrCreateInjectable<T>(
|
|||||||
throw createInjectionError('Not found', token);
|
throw createInjectionError('Not found', token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function searchMatchesQueuedForCreation<T>(node: LNode, token: any): T|null {
|
||||||
|
const matches = node.view.tView.currentMatches;
|
||||||
|
if (matches) {
|
||||||
|
for (let i = 0; i < matches.length; i += 2) {
|
||||||
|
const def = matches[i] as DirectiveDef<any>;
|
||||||
|
if (def.type === token) {
|
||||||
|
return resolveDirective(def, i + 1, matches, node.view.tView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a directive type, this function returns the bit in an injector's bloom filter
|
* Given a directive type, this function returns the bit in an injector's bloom filter
|
||||||
* that should be used to determine whether or not the directive is present.
|
* that should be used to determine whether or not the directive is present.
|
||||||
|
35
packages/core/src/render3/errors.ts
Normal file
35
packages/core/src/render3/errors.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* @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 {TNode} from './interfaces/node';
|
||||||
|
|
||||||
|
/** Called when directives inject each other (creating a circular dependency) */
|
||||||
|
export function throwCyclicDependencyError(token: any): never {
|
||||||
|
throw new Error(`Cannot instantiate cyclic dependency! ${token}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called when there are multiple component selectors that match a given node */
|
||||||
|
export function throwMultipleComponentError(tNode: TNode): never {
|
||||||
|
throw new Error(`Multiple components match node with tagname ${tNode.tagName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Throws an ExpressionChangedAfterChecked error if checkNoChanges mode is on. */
|
||||||
|
export function throwErrorIfNoChangesMode(
|
||||||
|
creationMode: boolean, checkNoChangesMode: boolean, oldValue: any, currValue: any): never|void {
|
||||||
|
if (checkNoChangesMode) {
|
||||||
|
let msg =
|
||||||
|
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: '${oldValue}'. Current value: '${currValue}'.`;
|
||||||
|
if (creationMode) {
|
||||||
|
msg +=
|
||||||
|
` It seems like the view has been created after its parent and its children have been dirty checked.` +
|
||||||
|
` Has it been created in a change detection hook ?`;
|
||||||
|
}
|
||||||
|
// TODO: include debug context
|
||||||
|
throw new Error(msg);
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,7 @@ import {LContainer, TContainer} from './interfaces/container';
|
|||||||
import {LInjector} from './interfaces/injector';
|
import {LInjector} from './interfaces/injector';
|
||||||
import {CssSelectorList, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
|
import {CssSelectorList, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
|
||||||
import {LQueries} from './interfaces/query';
|
import {LQueries} from './interfaces/query';
|
||||||
import {LView, LViewFlags, LifecycleStage, RootContext, TData, TView} from './interfaces/view';
|
import {CurrentMatchesList, LView, LViewFlags, LifecycleStage, RootContext, TData, TView} from './interfaces/view';
|
||||||
|
|
||||||
import {LContainerNode, LElementNode, LNode, LNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node';
|
import {LContainerNode, LElementNode, LNode, LNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node';
|
||||||
import {assertNodeType} from './node_assert';
|
import {assertNodeType} from './node_assert';
|
||||||
@ -24,6 +24,7 @@ import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, Objec
|
|||||||
import {isDifferent, stringify} from './util';
|
import {isDifferent, stringify} from './util';
|
||||||
import {executeHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks';
|
import {executeHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks';
|
||||||
import {ViewRef} from './view_ref';
|
import {ViewRef} from './view_ref';
|
||||||
|
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directive (D) sets a property on all component instances using this constant as a key and the
|
* Directive (D) sets a property on all component instances using this constant as a key and the
|
||||||
@ -50,6 +51,13 @@ export type Sanitizer = (value: any) => string;
|
|||||||
*/
|
*/
|
||||||
export const _ROOT_DIRECTIVE_INDICES = [0, 0];
|
export const _ROOT_DIRECTIVE_INDICES = [0, 0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token set in currentMatches while dependencies are being resolved.
|
||||||
|
*
|
||||||
|
* If we visit a directive that has a value set to CIRCULAR, we know we've
|
||||||
|
* already seen it, and thus have a circular dependency.
|
||||||
|
*/
|
||||||
|
export const CIRCULAR = '__CIRCULAR__';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This property gets set before entering a template.
|
* This property gets set before entering a template.
|
||||||
@ -528,66 +536,93 @@ export function elementStart(
|
|||||||
|
|
||||||
if (attrs) setUpAttributes(native, attrs);
|
if (attrs) setUpAttributes(native, attrs);
|
||||||
appendChild(node.parent !, native, currentView);
|
appendChild(node.parent !, native, currentView);
|
||||||
|
createDirectivesAndLocals(index, name, attrs, localRefs, null);
|
||||||
if (firstTemplatePass) {
|
|
||||||
const tNode = createTNode(name, attrs || null, null);
|
|
||||||
cacheMatchingDirectivesForNode(tNode);
|
|
||||||
|
|
||||||
ngDevMode && assertDataInRange(index - 1);
|
|
||||||
node.tNode = tData[index] = tNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
hack_declareDirectives(index, localRefs || null);
|
|
||||||
return native;
|
return native;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cacheMatchingDirectivesForNode(tNode: TNode): void {
|
function createDirectivesAndLocals(
|
||||||
const tView = currentView.tView;
|
index: number, name: string | null, attrs: string[] | null | undefined,
|
||||||
const registry = tView.directiveRegistry;
|
localRefs: string[] | null | undefined, containerData: TView[] | null) {
|
||||||
|
const node = previousOrParentNode;
|
||||||
|
if (firstTemplatePass) {
|
||||||
|
ngDevMode && assertDataInRange(index - 1);
|
||||||
|
node.tNode = tData[index] = createTNode(name, attrs || null, containerData);
|
||||||
|
cacheMatchingDirectivesForNode(node.tNode, currentView.tView, localRefs || null);
|
||||||
|
} else {
|
||||||
|
instantiateDirectivesDirectly();
|
||||||
|
}
|
||||||
|
saveResolvedLocalsInData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On first template pass, we match each node against available directive selectors and save
|
||||||
|
* the resulting defs in the correct instantiation order for subsequent change detection runs
|
||||||
|
* (so dependencies are always created before the directives that inject them).
|
||||||
|
*/
|
||||||
|
function cacheMatchingDirectivesForNode(
|
||||||
|
tNode: TNode, tView: TView, localRefs: string[] | null): void {
|
||||||
|
const exportsMap = localRefs ? {'': -1} : null;
|
||||||
|
const matches = tView.currentMatches = findDirectiveMatches(tNode);
|
||||||
|
if (matches) {
|
||||||
|
for (let i = 0; i < matches.length; i += 2) {
|
||||||
|
const def = matches[i] as DirectiveDef<any>;
|
||||||
|
const valueIndex = i + 1;
|
||||||
|
resolveDirective(def, valueIndex, matches, tView);
|
||||||
|
saveNameToExportMap(matches[valueIndex] as number, def, exportsMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Matches the current node against all available selectors. */
|
||||||
|
function findDirectiveMatches(tNode: TNode): CurrentMatchesList|null {
|
||||||
|
const registry = currentView.tView.directiveRegistry;
|
||||||
|
let matches: any[]|null = null;
|
||||||
if (registry) {
|
if (registry) {
|
||||||
let componentFlag = 0;
|
|
||||||
let size = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < registry.length; i++) {
|
for (let i = 0; i < registry.length; i++) {
|
||||||
const def = registry[i];
|
const def = registry[i];
|
||||||
if (isNodeMatchingSelectorList(tNode, def.selectors !)) {
|
if (isNodeMatchingSelectorList(tNode, def.selectors !)) {
|
||||||
if ((def as ComponentDef<any>).template) {
|
if ((def as ComponentDef<any>).template) {
|
||||||
if (componentFlag) throwMultipleComponentError(tNode);
|
if (tNode.flags & TNodeFlags.Component) throwMultipleComponentError(tNode);
|
||||||
componentFlag |= TNodeFlags.Component;
|
tNode.flags = TNodeFlags.Component;
|
||||||
}
|
}
|
||||||
(tView.directives || (tView.directives = [])).push(def);
|
if (def.diPublic) def.diPublic(def);
|
||||||
size++;
|
(matches || (matches = [])).push(def, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (size > 0) {
|
|
||||||
const startIndex = directives ? directives.length : 0;
|
|
||||||
buildTNodeFlags(tNode, startIndex, size, componentFlag);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return matches as CurrentMatchesList;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTNodeFlags(tNode: TNode, index: number, size: number, component: number): void {
|
export function resolveDirective(
|
||||||
tNode.flags = (index << TNodeFlags.INDX_SHIFT) | (size << TNodeFlags.SIZE_SHIFT) | component;
|
def: DirectiveDef<any>, valueIndex: number, matches: CurrentMatchesList, tView: TView): any {
|
||||||
}
|
if (matches[valueIndex] === null) {
|
||||||
|
matches[valueIndex] = CIRCULAR;
|
||||||
function throwMultipleComponentError(tNode: TNode): never {
|
const instance = def.factory();
|
||||||
throw new Error(`Multiple components match node with tagname ${tNode.tagName}`);
|
(tView.directives || (tView.directives = [])).push(def);
|
||||||
|
return directiveCreate(matches[valueIndex] = tView.directives !.length - 1, instance, def);
|
||||||
|
} else if (matches[valueIndex] === CIRCULAR) {
|
||||||
|
// If we revisit this directive before it's resolved, we know it's circular
|
||||||
|
throwCyclicDependencyError(def.type);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stores index of component's host element so it will be queued for view refresh during CD. */
|
/** Stores index of component's host element so it will be queued for view refresh during CD. */
|
||||||
function queueComponentIndexForCheck(dirIndex: number, elIndex: number): void {
|
function queueComponentIndexForCheck(dirIndex: number): void {
|
||||||
if (firstTemplatePass) {
|
if (firstTemplatePass) {
|
||||||
(currentView.tView.components || (currentView.tView.components = [])).push(dirIndex, elIndex);
|
(currentView.tView.components || (currentView.tView.components = [
|
||||||
|
])).push(dirIndex, data.length - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stores index of directive and host element so it will be queued for binding refresh during CD.
|
/** Stores index of directive and host element so it will be queued for binding refresh during CD.
|
||||||
*/
|
*/
|
||||||
function queueHostBindingForCheck(dirIndex: number, elIndex: number): void {
|
function queueHostBindingForCheck(dirIndex: number): void {
|
||||||
ngDevMode &&
|
ngDevMode &&
|
||||||
assertEqual(firstTemplatePass, true, 'Should only be called in first template pass.');
|
assertEqual(firstTemplatePass, true, 'Should only be called in first template pass.');
|
||||||
(currentView.tView.hostBindings || (currentView.tView.hostBindings = [])).push(dirIndex, elIndex);
|
(currentView.tView.hostBindings || (currentView.tView.hostBindings = [
|
||||||
|
])).push(dirIndex, data.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the context for a ChangeDetectorRef to the given instance. */
|
/** Sets the context for a ChangeDetectorRef to the given instance. */
|
||||||
@ -603,32 +638,21 @@ export function isComponent(tNode: TNode): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function instantiates the given directives. It is a hack since it assumes the directives
|
* This function instantiates the given directives.
|
||||||
* come in the correct order for DI.
|
|
||||||
*/
|
*/
|
||||||
function hack_declareDirectives(elementIndex: number, localRefs: string[] | null) {
|
function instantiateDirectivesDirectly() {
|
||||||
const tNode = previousOrParentNode.tNode !;
|
const tNode = previousOrParentNode.tNode !;
|
||||||
const size = (tNode.flags & TNodeFlags.SIZE_MASK) >> TNodeFlags.SIZE_SHIFT;
|
const size = (tNode.flags & TNodeFlags.SIZE_MASK) >> TNodeFlags.SIZE_SHIFT;
|
||||||
|
|
||||||
const exportsMap: {[key: string]: number}|null = firstTemplatePass && localRefs ? {'': -1} : null;
|
|
||||||
|
|
||||||
if (size > 0) {
|
if (size > 0) {
|
||||||
let startIndex = tNode.flags >> TNodeFlags.INDX_SHIFT;
|
const startIndex = tNode.flags >> TNodeFlags.INDX_SHIFT;
|
||||||
const endIndex = startIndex + size;
|
|
||||||
const tDirectives = currentView.tView.directives !;
|
const tDirectives = currentView.tView.directives !;
|
||||||
|
|
||||||
// TODO(mhevery): This assumes that the directives come in correct order, which
|
for (let i = startIndex; i < startIndex + size; i++) {
|
||||||
// is not guaranteed. Must be refactored to take it into account.
|
|
||||||
for (let i = startIndex; i < endIndex; i++) {
|
|
||||||
const def = tDirectives[i] as DirectiveDef<any>;
|
const def = tDirectives[i] as DirectiveDef<any>;
|
||||||
directiveCreate(elementIndex, def.factory(), def);
|
directiveCreate(i, def.factory(), def);
|
||||||
saveNameToExportMap(startIndex, def, exportsMap);
|
|
||||||
startIndex++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (firstTemplatePass) cacheMatchingLocalNames(tNode, localRefs, exportsMap !);
|
|
||||||
saveResolvedLocalsInData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Caches local names and their matching directive indices for query and template lookups. */
|
/** Caches local names and their matching directive indices for query and template lookups. */
|
||||||
@ -710,7 +734,8 @@ export function createTView(
|
|||||||
hostBindings: null,
|
hostBindings: null,
|
||||||
components: null,
|
components: null,
|
||||||
directiveRegistry: typeof defs === 'function' ? defs() : defs,
|
directiveRegistry: typeof defs === 'function' ? defs() : defs,
|
||||||
pipeRegistry: typeof pipes === 'function' ? pipes() : pipes
|
pipeRegistry: typeof pipes === 'function' ? pipes() : pipes,
|
||||||
|
currentMatches: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -777,8 +802,8 @@ export function hostElement(
|
|||||||
|
|
||||||
if (firstTemplatePass) {
|
if (firstTemplatePass) {
|
||||||
node.tNode = createTNode(tag as string, null, null);
|
node.tNode = createTNode(tag as string, null, null);
|
||||||
// Root directive is stored at index 0, size 1
|
node.tNode.flags = TNodeFlags.Component;
|
||||||
buildTNodeFlags(node.tNode, 0, 1, TNodeFlags.Component);
|
if (def.diPublic) def.diPublic(def);
|
||||||
currentView.tView.directives = [def];
|
currentView.tView.directives = [def];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1167,14 +1192,11 @@ 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
|
* 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.
|
* be retrieved before they are created in which case the value will be null.
|
||||||
*
|
*
|
||||||
* @param elementIndex Index of the host element in the data array
|
|
||||||
* @param directive The directive instance.
|
* @param directive The directive instance.
|
||||||
* @param directiveDef DirectiveDef object which contains information about the template.
|
* @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>(
|
export function directiveCreate<T>(
|
||||||
elementIndex: number, directive: T, directiveDef: DirectiveDef<T>| ComponentDef<T>): T {
|
index: number, directive: T, directiveDef: DirectiveDef<T>| ComponentDef<T>): T {
|
||||||
const index = directives ? directives.length : 0;
|
|
||||||
const instance = baseDirectiveCreate(index, directive, directiveDef);
|
const instance = baseDirectiveCreate(index, directive, directiveDef);
|
||||||
|
|
||||||
ngDevMode && assertNotNull(previousOrParentNode.tNode, 'previousOrParentNode.tNode');
|
ngDevMode && assertNotNull(previousOrParentNode.tNode, 'previousOrParentNode.tNode');
|
||||||
@ -1182,7 +1204,7 @@ export function directiveCreate<T>(
|
|||||||
|
|
||||||
const isComponent = (directiveDef as ComponentDef<T>).template;
|
const isComponent = (directiveDef as ComponentDef<T>).template;
|
||||||
if (isComponent) {
|
if (isComponent) {
|
||||||
addComponentLogic(index, elementIndex, directive, directiveDef as ComponentDef<T>);
|
addComponentLogic(index, directive, directiveDef as ComponentDef<T>);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (firstTemplatePass) {
|
if (firstTemplatePass) {
|
||||||
@ -1190,7 +1212,7 @@ export function directiveCreate<T>(
|
|||||||
// any projected components.
|
// any projected components.
|
||||||
queueInitHooks(index, directiveDef.onInit, directiveDef.doCheck, currentView.tView);
|
queueInitHooks(index, directiveDef.onInit, directiveDef.doCheck, currentView.tView);
|
||||||
|
|
||||||
if (directiveDef.hostBindings) queueHostBindingForCheck(index, elementIndex);
|
if (directiveDef.hostBindings) queueHostBindingForCheck(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tNode && tNode.attrs) {
|
if (tNode && tNode.attrs) {
|
||||||
@ -1200,8 +1222,7 @@ export function directiveCreate<T>(
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addComponentLogic<T>(
|
function addComponentLogic<T>(index: number, instance: T, def: ComponentDef<T>): void {
|
||||||
index: number, elementIndex: number, instance: T, def: ComponentDef<T>): void {
|
|
||||||
const tView = getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs);
|
const tView = getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs);
|
||||||
|
|
||||||
// Only component views should be added to the view tree directly. Embedded views are
|
// Only component views should be added to the view tree directly. Embedded views are
|
||||||
@ -1217,7 +1238,7 @@ function addComponentLogic<T>(
|
|||||||
|
|
||||||
initChangeDetectorIfExisting(previousOrParentNode.nodeInjector, instance, hostView);
|
initChangeDetectorIfExisting(previousOrParentNode.nodeInjector, instance, hostView);
|
||||||
|
|
||||||
if (firstTemplatePass) queueComponentIndexForCheck(index, elementIndex);
|
if (firstTemplatePass) queueComponentIndexForCheck(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1240,9 +1261,14 @@ export function baseDirectiveCreate<T>(
|
|||||||
ngDevMode && assertDataNext(index, directives);
|
ngDevMode && assertDataNext(index, directives);
|
||||||
directives[index] = directive;
|
directives[index] = directive;
|
||||||
|
|
||||||
const diPublic = directiveDef !.diPublic;
|
if (firstTemplatePass) {
|
||||||
if (diPublic) {
|
const flags = previousOrParentNode.tNode !.flags;
|
||||||
diPublic(directiveDef !);
|
previousOrParentNode.tNode !.flags = (flags & TNodeFlags.SIZE_MASK) === 0 ?
|
||||||
|
(index << TNodeFlags.INDX_SHIFT) | TNodeFlags.SIZE_SKIP | flags & TNodeFlags.Component :
|
||||||
|
flags + TNodeFlags.SIZE_SKIP;
|
||||||
|
} else {
|
||||||
|
const diPublic = directiveDef !.diPublic;
|
||||||
|
if (diPublic) diPublic(directiveDef !);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (directiveDef !.attributes != null && previousOrParentNode.type == LNodeType.Element) {
|
if (directiveDef !.attributes != null && previousOrParentNode.type == LNodeType.Element) {
|
||||||
@ -1355,18 +1381,10 @@ export function container(
|
|||||||
|
|
||||||
const node = createLNode(index, LNodeType.Container, undefined, lContainer);
|
const node = createLNode(index, LNodeType.Container, undefined, lContainer);
|
||||||
|
|
||||||
if (node.tNode == null) {
|
|
||||||
node.tNode = tData[index] = createTNode(tagName || null, attrs || null, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Containers are added to the current view tree instead of their embedded views
|
// Containers are added to the current view tree instead of their embedded views
|
||||||
// because views can be removed and re-inserted.
|
// because views can be removed and re-inserted.
|
||||||
addToViewTree(currentView, node.data);
|
addToViewTree(currentView, node.data);
|
||||||
|
createDirectivesAndLocals(index, tagName || null, attrs, localRefs, []);
|
||||||
if (firstTemplatePass) cacheMatchingDirectivesForNode(node.tNode);
|
|
||||||
|
|
||||||
// TODO: handle TemplateRef!
|
|
||||||
hack_declareDirectives(index, localRefs || null);
|
|
||||||
|
|
||||||
isParent = false;
|
isParent = false;
|
||||||
ngDevMode && assertNodeType(previousOrParentNode, LNodeType.Container);
|
ngDevMode && assertNodeType(previousOrParentNode, LNodeType.Container);
|
||||||
@ -1892,22 +1910,6 @@ export function checkNoChanges<T>(component: T): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Throws an ExpressionChangedAfterChecked error if checkNoChanges mode is on. */
|
|
||||||
function throwErrorIfNoChangesMode(oldValue: any, currValue: any): never|void {
|
|
||||||
if (checkNoChangesMode) {
|
|
||||||
let msg =
|
|
||||||
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: '${oldValue}'. Current value: '${currValue}'.`;
|
|
||||||
if (creationMode) {
|
|
||||||
msg +=
|
|
||||||
` It seems like the view has been created after its parent and its children have been dirty checked.` +
|
|
||||||
` Has it been created in a change detection hook ?`;
|
|
||||||
}
|
|
||||||
// TODO: include debug context
|
|
||||||
throw new Error(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** Checks the view of the component provided. Does not gate on dirty checks or execute doCheck. */
|
/** Checks the view of the component provided. Does not gate on dirty checks or execute doCheck. */
|
||||||
export function detectChangesInternal<T>(
|
export function detectChangesInternal<T>(
|
||||||
hostView: LView, hostNode: LElementNode, def: ComponentDef<T>, component: T) {
|
hostView: LView, hostNode: LElementNode, def: ComponentDef<T>, component: T) {
|
||||||
@ -1985,7 +1987,7 @@ export function bind<T>(value: T | NO_CHANGE): T|NO_CHANGE {
|
|||||||
|
|
||||||
const changed: boolean = value !== NO_CHANGE && isDifferent(data[bindingIndex], value);
|
const changed: boolean = value !== NO_CHANGE && isDifferent(data[bindingIndex], value);
|
||||||
if (changed) {
|
if (changed) {
|
||||||
throwErrorIfNoChangesMode(data[bindingIndex], value);
|
throwErrorIfNoChangesMode(creationMode, checkNoChangesMode, data[bindingIndex], value);
|
||||||
data[bindingIndex] = value;
|
data[bindingIndex] = value;
|
||||||
}
|
}
|
||||||
bindingIndex++;
|
bindingIndex++;
|
||||||
@ -2165,7 +2167,7 @@ export function bindingUpdated(value: any): boolean {
|
|||||||
if (creationMode) {
|
if (creationMode) {
|
||||||
initBindings();
|
initBindings();
|
||||||
} else if (isDifferent(data[bindingIndex], value)) {
|
} else if (isDifferent(data[bindingIndex], value)) {
|
||||||
throwErrorIfNoChangesMode(data[bindingIndex], value);
|
throwErrorIfNoChangesMode(creationMode, checkNoChangesMode, data[bindingIndex], value);
|
||||||
} else {
|
} else {
|
||||||
bindingIndex++;
|
bindingIndex++;
|
||||||
return false;
|
return false;
|
||||||
|
@ -42,6 +42,9 @@ export const enum TNodeFlags {
|
|||||||
/** How far to shift the flags to get the number of directives on this node */
|
/** How far to shift the flags to get the number of directives on this node */
|
||||||
SIZE_SHIFT = 1,
|
SIZE_SHIFT = 1,
|
||||||
|
|
||||||
|
/** The amount to add to flags to increment size when each directive is added */
|
||||||
|
SIZE_SKIP = 2,
|
||||||
|
|
||||||
/** Mask to get the number of directives on this node */
|
/** Mask to get the number of directives on this node */
|
||||||
SIZE_MASK = 0b00000000000000000001111111111110
|
SIZE_MASK = 0b00000000000000000001111111111110
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {LContainer} from './container';
|
import {LContainer} from './container';
|
||||||
import {ComponentTemplate, DirectiveDefList, PipeDef, PipeDefList} from './definition';
|
import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDef, PipeDefList} from './definition';
|
||||||
import {LElementNode, LViewNode, TNode} from './node';
|
import {LElementNode, LViewNode, TNode} from './node';
|
||||||
import {LQueries} from './query';
|
import {LQueries} from './query';
|
||||||
import {Renderer3} from './renderer';
|
import {Renderer3} from './renderer';
|
||||||
@ -225,6 +225,24 @@ export interface TView {
|
|||||||
/** Static data equivalent of LView.data[]. Contains TNodes. */
|
/** Static data equivalent of LView.data[]. Contains TNodes. */
|
||||||
data: TData;
|
data: TData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selector matches for a node are temporarily cached on the TView so the
|
||||||
|
* DI system can eagerly instantiate directives on the same node if they are
|
||||||
|
* created out of order. They are overwritten after each node.
|
||||||
|
*
|
||||||
|
* <div dirA dirB></div>
|
||||||
|
*
|
||||||
|
* e.g. DirA injects DirB, but DirA is created first. DI should instantiate
|
||||||
|
* DirB when it finds that it's on the same node, but not yet created.
|
||||||
|
*
|
||||||
|
* Even indices: Directive defs
|
||||||
|
* Odd indices:
|
||||||
|
* - Null if the associated directive hasn't been instantiated yet
|
||||||
|
* - Directive index, if associated directive has been created
|
||||||
|
* - String, temporary 'CIRCULAR' token set while dependencies are being resolved
|
||||||
|
*/
|
||||||
|
currentMatches: CurrentMatchesList|null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directive and component defs that have already been matched to nodes on
|
* Directive and component defs that have already been matched to nodes on
|
||||||
* this view.
|
* this view.
|
||||||
@ -397,6 +415,9 @@ export const enum LifecycleStage {
|
|||||||
*/
|
*/
|
||||||
export type TData = (TNode | PipeDef<any>| null)[];
|
export type TData = (TNode | PipeDef<any>| null)[];
|
||||||
|
|
||||||
|
/** Type for TView.currentMatches */
|
||||||
|
export type CurrentMatchesList = [DirectiveDef<any>, (string | number | null)];
|
||||||
|
|
||||||
// Note: This hack is necessary so we don't erroneously get a circular dependency
|
// Note: This hack is necessary so we don't erroneously get a circular dependency
|
||||||
// failure based on types.
|
// failure based on types.
|
||||||
export const unusedValueExportToPlacateAjd = 1;
|
export const unusedValueExportToPlacateAjd = 1;
|
||||||
|
@ -35,9 +35,6 @@
|
|||||||
{
|
{
|
||||||
"name": "baseDirectiveCreate"
|
"name": "baseDirectiveCreate"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "buildTNodeFlags"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "callHooks"
|
"name": "callHooks"
|
||||||
},
|
},
|
||||||
|
@ -11,6 +11,9 @@
|
|||||||
{
|
{
|
||||||
"name": "CIRCULAR$1"
|
"name": "CIRCULAR$1"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "CIRCULAR$2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "CLEAN_PROMISE"
|
"name": "CLEAN_PROMISE"
|
||||||
},
|
},
|
||||||
@ -260,9 +263,6 @@
|
|||||||
{
|
{
|
||||||
"name": "bindingUpdated"
|
"name": "bindingUpdated"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "buildTNodeFlags"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "cacheMatchingDirectivesForNode"
|
"name": "cacheMatchingDirectivesForNode"
|
||||||
},
|
},
|
||||||
@ -296,6 +296,9 @@
|
|||||||
{
|
{
|
||||||
"name": "couldBeInjectableType"
|
"name": "couldBeInjectableType"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "createDirectivesAndLocals"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "createInjector"
|
"name": "createInjector"
|
||||||
},
|
},
|
||||||
@ -404,6 +407,9 @@
|
|||||||
{
|
{
|
||||||
"name": "findAttrIndexInNode"
|
"name": "findAttrIndexInNode"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "findDirectiveMatches"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "findFirstRNode"
|
"name": "findFirstRNode"
|
||||||
},
|
},
|
||||||
@ -485,9 +491,6 @@
|
|||||||
{
|
{
|
||||||
"name": "getTypeNameForDebugging$1"
|
"name": "getTypeNameForDebugging$1"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "hack_declareDirectives"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "hasDeps"
|
"name": "hasDeps"
|
||||||
},
|
},
|
||||||
@ -527,6 +530,9 @@
|
|||||||
{
|
{
|
||||||
"name": "insertView"
|
"name": "insertView"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "instantiateDirectivesDirectly"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "interpolation1"
|
"name": "interpolation1"
|
||||||
},
|
},
|
||||||
@ -680,6 +686,9 @@
|
|||||||
{
|
{
|
||||||
"name": "resetApplicationState"
|
"name": "resetApplicationState"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "resolveDirective"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "resolveForwardRef"
|
"name": "resolveForwardRef"
|
||||||
},
|
},
|
||||||
@ -728,6 +737,9 @@
|
|||||||
{
|
{
|
||||||
"name": "textBinding"
|
"name": "textBinding"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "throwCyclicDependencyError"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "throwErrorIfNoChangesMode"
|
"name": "throwErrorIfNoChangesMode"
|
||||||
},
|
},
|
||||||
@ -761,4 +773,4 @@
|
|||||||
{
|
{
|
||||||
"name": "ɵ0"
|
"name": "ɵ0"
|
||||||
}
|
}
|
||||||
]
|
]
|
@ -11,13 +11,13 @@ import {ChangeDetectorRef, ElementRef, TemplateRef, ViewContainerRef} from '@ang
|
|||||||
import {defineComponent} from '../../src/render3/definition';
|
import {defineComponent} from '../../src/render3/definition';
|
||||||
import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di';
|
import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di';
|
||||||
import {NgOnChangesFeature, PublicFeature, defineDirective, directiveInject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
|
import {NgOnChangesFeature, PublicFeature, defineDirective, directiveInject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
|
||||||
import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
|
import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
|
||||||
import {LInjector} from '../../src/render3/interfaces/injector';
|
import {LInjector} from '../../src/render3/interfaces/injector';
|
||||||
import {LNodeType} from '../../src/render3/interfaces/node';
|
import {LNodeType} from '../../src/render3/interfaces/node';
|
||||||
import {LViewFlags} from '../../src/render3/interfaces/view';
|
import {LViewFlags} from '../../src/render3/interfaces/view';
|
||||||
import {ViewRef} from '../../src/render3/view_ref';
|
import {ViewRef} from '../../src/render3/view_ref';
|
||||||
|
|
||||||
import {createComponent, createDirective, renderComponent, renderToHtml, toHtml} from './render_util';
|
import {ComponentFixture, createComponent, createDirective, renderComponent, renderToHtml, toHtml} from './render_util';
|
||||||
|
|
||||||
describe('di', () => {
|
describe('di', () => {
|
||||||
describe('no dependencies', () => {
|
describe('no dependencies', () => {
|
||||||
@ -47,35 +47,41 @@ describe('di', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('view dependencies', () => {
|
describe('directive injection', () => {
|
||||||
it('should create directive with inter view dependencies', () => {
|
let log: string[] = [];
|
||||||
class DirectiveA {
|
|
||||||
value: string = 'A';
|
class DirB {
|
||||||
|
value = 'DirB';
|
||||||
|
constructor() { log.push(this.value); }
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'dirB', '']],
|
||||||
|
type: DirB,
|
||||||
|
factory: () => new DirB(),
|
||||||
|
features: [PublicFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => log = []);
|
||||||
|
|
||||||
|
it('should create directive with intra view dependencies', () => {
|
||||||
|
class DirA {
|
||||||
|
value: string = 'DirA';
|
||||||
static ngDirectiveDef = defineDirective({
|
static ngDirectiveDef = defineDirective({
|
||||||
type: DirectiveA,
|
type: DirA,
|
||||||
selectors: [['', 'dirA', '']],
|
selectors: [['', 'dirA', '']],
|
||||||
factory: () => new DirectiveA,
|
factory: () => new DirA,
|
||||||
features: [PublicFeature]
|
features: [PublicFeature]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class DirectiveB {
|
class DirC {
|
||||||
value: string = 'B';
|
|
||||||
static ngDirectiveDef = defineDirective({
|
|
||||||
type: DirectiveB,
|
|
||||||
selectors: [['', 'dirB', '']],
|
|
||||||
factory: () => new DirectiveB,
|
|
||||||
features: [PublicFeature]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class DirectiveC {
|
|
||||||
value: string;
|
value: string;
|
||||||
constructor(a: DirectiveA, b: DirectiveB) { this.value = a.value + b.value; }
|
constructor(a: DirA, b: DirB) { this.value = a.value + b.value; }
|
||||||
static ngDirectiveDef = defineDirective({
|
static ngDirectiveDef = defineDirective({
|
||||||
type: DirectiveC,
|
type: DirC,
|
||||||
selectors: [['', 'dirC', '']],
|
selectors: [['', 'dirC', '']],
|
||||||
factory: () => new DirectiveC(directiveInject(DirectiveA), directiveInject(DirectiveB)),
|
factory: () => new DirC(directiveInject(DirA), directiveInject(DirB)),
|
||||||
exportAs: 'dirC'
|
exportAs: 'dirC'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -99,10 +105,381 @@ describe('di', () => {
|
|||||||
textBinding(3, bind(tmp.value));
|
textBinding(3, bind(tmp.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
const defs = [DirectiveA, DirectiveB, DirectiveC];
|
const defs = [DirA, DirB, DirC];
|
||||||
expect(renderToHtml(Template, {}, defs))
|
expect(renderToHtml(Template, {}, defs))
|
||||||
.toEqual('<div dira=""><span dirb="" dirc="">AB</span></div>');
|
.toEqual('<div dira=""><span dirb="" dirc="">DirADirB</span></div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should instantiate injected directives first', () => {
|
||||||
|
class DirA {
|
||||||
|
constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'dirA', '']],
|
||||||
|
type: DirA,
|
||||||
|
factory: () => new DirA(directiveInject(DirB)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** <div dirA dirB></div> */
|
||||||
|
const App = createComponent('app', function(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
elementStart(0, 'div', ['dirA', '', 'dirB', '']);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}, [DirA, DirB]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(App);
|
||||||
|
expect(log).toEqual(['DirB', 'DirA (dep: DirB)']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should instantiate injected directives before components', () => {
|
||||||
|
class Comp {
|
||||||
|
constructor(dir: DirB) { log.push(`Comp (dep: ${dir.value})`); }
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
selectors: [['comp']],
|
||||||
|
type: Comp,
|
||||||
|
factory: () => new Comp(directiveInject(DirB)),
|
||||||
|
template: (ctx: any, fm: boolean) => {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** <comp dirB></comp> */
|
||||||
|
const App = createComponent('app', function(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
elementStart(0, 'comp', ['dirB', '']);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}, [Comp, DirB]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(App);
|
||||||
|
expect(log).toEqual(['DirB', 'Comp (dep: DirB)']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inject directives in the correct order in a for loop', () => {
|
||||||
|
class DirA {
|
||||||
|
constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'dirA', '']],
|
||||||
|
type: DirA,
|
||||||
|
factory: () => new DirA(directiveInject(DirB))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* % for(let i = 0; i < 3; i++) {
|
||||||
|
* <div dirA dirB></div>
|
||||||
|
* % }
|
||||||
|
*/
|
||||||
|
const App = createComponent('app', function(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
container(0);
|
||||||
|
}
|
||||||
|
containerRefreshStart(0);
|
||||||
|
{
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
if (embeddedViewStart(0)) {
|
||||||
|
elementStart(0, 'div', ['dirA', '', 'dirB', '']);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
embeddedViewEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
containerRefreshEnd();
|
||||||
|
}, [DirA, DirB]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(App);
|
||||||
|
expect(log).toEqual(
|
||||||
|
['DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should instantiate directives with multiple out-of-order dependencies', () => {
|
||||||
|
class DirA {
|
||||||
|
value = 'DirA';
|
||||||
|
constructor() { log.push(this.value); }
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'dirA', '']],
|
||||||
|
type: DirA,
|
||||||
|
factory: () => new DirA(),
|
||||||
|
features: [PublicFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class DirB {
|
||||||
|
constructor(dirA: DirA, dirC: DirC) {
|
||||||
|
log.push(`DirB (deps: ${dirA.value} and ${dirC.value})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'dirB', '']],
|
||||||
|
type: DirB,
|
||||||
|
factory: () => new DirB(directiveInject(DirA), directiveInject(DirC))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class DirC {
|
||||||
|
value = 'DirC';
|
||||||
|
constructor() { log.push(this.value); }
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'dirC', '']],
|
||||||
|
type: DirC,
|
||||||
|
factory: () => new DirC(),
|
||||||
|
features: [PublicFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** <div dirA dirB dirC></div> */
|
||||||
|
const App = createComponent('app', function(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
elementStart(0, 'div', ['dirA', '', 'dirB', '', 'dirC', '']);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}, [DirA, DirB, DirC]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(App);
|
||||||
|
expect(log).toEqual(['DirA', 'DirC', 'DirB (deps: DirA and DirC)']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should instantiate in the correct order for complex case', () => {
|
||||||
|
class Comp {
|
||||||
|
constructor(dir: DirD) { log.push(`Comp (dep: ${dir.value})`); }
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
selectors: [['comp']],
|
||||||
|
type: Comp,
|
||||||
|
factory: () => new Comp(directiveInject(DirD)),
|
||||||
|
template: (ctx: any, fm: boolean) => {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class DirA {
|
||||||
|
value = 'DirA';
|
||||||
|
constructor(dir: DirC) { log.push(`DirA (dep: ${dir.value})`); }
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'dirA', '']],
|
||||||
|
type: DirA,
|
||||||
|
factory: () => new DirA(directiveInject(DirC)),
|
||||||
|
features: [PublicFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class DirC {
|
||||||
|
value = 'DirC';
|
||||||
|
constructor(dir: DirB) { log.push(`DirC (dep: ${dir.value})`); }
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'dirC', '']],
|
||||||
|
type: DirC,
|
||||||
|
factory: () => new DirC(directiveInject(DirB)),
|
||||||
|
features: [PublicFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class DirD {
|
||||||
|
value = 'DirD';
|
||||||
|
constructor(dir: DirA) { log.push(`DirD (dep: ${dir.value})`); }
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'dirD', '']],
|
||||||
|
type: DirD,
|
||||||
|
factory: () => new DirD(directiveInject(DirA)),
|
||||||
|
features: [PublicFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** <comp dirA dirB dirC dirD></comp> */
|
||||||
|
const App = createComponent('app', function(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
elementStart(0, 'comp', ['dirA', '', 'dirB', '', 'dirC', '', 'dirD', '']);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}, [Comp, DirA, DirB, DirC, DirD]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(App);
|
||||||
|
expect(log).toEqual(
|
||||||
|
['DirB', 'DirC (dep: DirB)', 'DirA (dep: DirC)', 'DirD (dep: DirA)', 'Comp (dep: DirD)']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should instantiate in correct order with mixed parent and peer dependencies', () => {
|
||||||
|
class DirA {
|
||||||
|
constructor(dirB: DirB, app: App) {
|
||||||
|
log.push(`DirA (deps: ${dirB.value} and ${app.value})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'dirA', '']],
|
||||||
|
type: DirA,
|
||||||
|
factory: () => new DirA(directiveInject(DirB), directiveInject(App)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class App {
|
||||||
|
value = 'App';
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
selectors: [['app']],
|
||||||
|
type: App,
|
||||||
|
factory: () => new App(),
|
||||||
|
/** <div dirA dirB dirC></div> */
|
||||||
|
template: (ctx: any, cm: boolean) => {
|
||||||
|
if (cm) {
|
||||||
|
elementStart(0, 'div', ['dirA', '', 'dirB', '', 'dirC', 'dirC']);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
directives: [DirA, DirB],
|
||||||
|
features: [PublicFeature],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(App);
|
||||||
|
expect(log).toEqual(['DirB', 'DirA (deps: DirB and App)']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not use a parent when peer dep is available', () => {
|
||||||
|
let count = 1;
|
||||||
|
|
||||||
|
class DirA {
|
||||||
|
constructor(dirB: DirB) { log.push(`DirA (dep: DirB - ${dirB.count})`); }
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'dirA', '']],
|
||||||
|
type: DirA,
|
||||||
|
factory: () => new DirA(directiveInject(DirB)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class DirB {
|
||||||
|
count: number;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
log.push(`DirB`);
|
||||||
|
this.count = count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'dirB', '']],
|
||||||
|
type: DirB,
|
||||||
|
factory: () => new DirB(),
|
||||||
|
features: [PublicFeature],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** <div dirA dirB></div> */
|
||||||
|
const Parent = createComponent('parent', function(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
elementStart(0, 'div', ['dirA', '', 'dirB', '']);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}, [DirA, DirB]);
|
||||||
|
|
||||||
|
/** <parent dirB></parent> */
|
||||||
|
const App = createComponent('app', function(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
elementStart(0, 'parent', ['dirB', '']);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}, [Parent, DirB]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(App);
|
||||||
|
expect(log).toEqual(['DirB', 'DirB', 'DirA (dep: DirB - 2)']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if directive is not found', () => {
|
||||||
|
class Dir {
|
||||||
|
constructor(siblingDir: OtherDir) {}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'dir', '']],
|
||||||
|
type: Dir,
|
||||||
|
factory: () => new Dir(directiveInject(OtherDir)),
|
||||||
|
features: [PublicFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class OtherDir {
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'other', '']],
|
||||||
|
type: OtherDir,
|
||||||
|
factory: () => new OtherDir(),
|
||||||
|
features: [PublicFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** <div dir></div> */
|
||||||
|
const App = createComponent('app', function(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
elementStart(0, 'div', ['dir', '']);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}, [Dir, OtherDir]);
|
||||||
|
|
||||||
|
expect(() => new ComponentFixture(App))
|
||||||
|
.toThrowError(/ElementInjector: NotFound \[OtherDir\]/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if directives try to inject each other', () => {
|
||||||
|
class DirA {
|
||||||
|
constructor(dir: DirB) {}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'dirA', '']],
|
||||||
|
type: DirA,
|
||||||
|
factory: () => new DirA(directiveInject(DirB)),
|
||||||
|
features: [PublicFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class DirB {
|
||||||
|
constructor(dir: DirA) {}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'dirB', '']],
|
||||||
|
type: DirB,
|
||||||
|
factory: () => new DirB(directiveInject(DirA)),
|
||||||
|
features: [PublicFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** <div dirA dirB></div> */
|
||||||
|
const App = createComponent('app', function(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
elementStart(0, 'div', ['dirA', '', 'dirB', '']);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}, [DirA, DirB]);
|
||||||
|
|
||||||
|
expect(() => new ComponentFixture(App)).toThrowError(/Cannot instantiate cyclic dependency!/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if directive tries to inject itself', () => {
|
||||||
|
class Dir {
|
||||||
|
constructor(dir: Dir) {}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
selectors: [['', 'dir', '']],
|
||||||
|
type: Dir,
|
||||||
|
factory: () => new Dir(directiveInject(Dir)),
|
||||||
|
features: [PublicFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** <div dir></div> */
|
||||||
|
const App = createComponent('app', function(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
elementStart(0, 'div', ['dir', '']);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}, [Dir]);
|
||||||
|
|
||||||
|
expect(() => new ComponentFixture(App)).toThrowError(/Cannot instantiate cyclic dependency!/);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ElementRef', () => {
|
describe('ElementRef', () => {
|
||||||
@ -283,7 +660,9 @@ describe('di', () => {
|
|||||||
|
|
||||||
class Directive {
|
class Directive {
|
||||||
value: string;
|
value: string;
|
||||||
|
|
||||||
constructor(public cdr: ChangeDetectorRef) { this.value = (cdr.constructor as any).name; }
|
constructor(public cdr: ChangeDetectorRef) { this.value = (cdr.constructor as any).name; }
|
||||||
|
|
||||||
static ngDirectiveDef = defineDirective({
|
static ngDirectiveDef = defineDirective({
|
||||||
type: Directive,
|
type: Directive,
|
||||||
selectors: [['', 'dir', '']],
|
selectors: [['', 'dir', '']],
|
||||||
@ -504,24 +883,23 @@ describe('di', () => {
|
|||||||
expect(dir !.cdr).toBe(app.cdr);
|
expect(dir !.cdr).toBe(app.cdr);
|
||||||
expect(dir !.cdr).toBe(dirSameInstance !.cdr);
|
expect(dir !.cdr).toBe(dirSameInstance !.cdr);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should injectAttribute', () => {
|
it('should injectAttribute', () => {
|
||||||
let exist: string|undefined = 'wrong';
|
let exist: string|undefined = 'wrong';
|
||||||
let nonExist: string|undefined = 'wrong';
|
let nonExist: string|undefined = 'wrong';
|
||||||
|
|
||||||
const MyApp = createComponent('my-app', function(ctx: any, cm: boolean) {
|
const MyApp = createComponent('my-app', function(ctx: any, cm: boolean) {
|
||||||
if (cm) {
|
if (cm) {
|
||||||
elementStart(0, 'div', ['exist', 'existValue', 'other', 'ignore']);
|
elementStart(0, 'div', ['exist', 'existValue', 'other', 'ignore']);
|
||||||
exist = injectAttribute('exist');
|
exist = injectAttribute('exist');
|
||||||
nonExist = injectAttribute('nonExist');
|
nonExist = injectAttribute('nonExist');
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
const app = renderComponent(MyApp);
|
|
||||||
expect(exist).toEqual('existValue');
|
|
||||||
expect(nonExist).toEqual(undefined);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const app = renderComponent(MyApp);
|
||||||
|
expect(exist).toEqual('existValue');
|
||||||
|
expect(nonExist).toEqual(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('inject', () => {
|
describe('inject', () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user