feat(ivy): support providers and viewProviders (#25803)
PR Close #25803
This commit is contained in:

committed by
Matias Niemelä

parent
9dc52d9d04
commit
b0476f308b
@ -25,7 +25,7 @@ export {
|
||||
getFactoryOf as ɵgetFactoryOf,
|
||||
getInheritedFactory as ɵgetInheritedFactory,
|
||||
templateRefExtractor as ɵtemplateRefExtractor,
|
||||
PublicFeature as ɵPublicFeature,
|
||||
ProvidersFeature as ɵProvidersResolver,
|
||||
InheritDefinitionFeature as ɵInheritDefinitionFeature,
|
||||
NgOnChangesFeature as ɵNgOnChangesFeature,
|
||||
NgModuleType as ɵNgModuleType,
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
import {Type} from '../type';
|
||||
import {stringify} from '../util';
|
||||
import {getClosureSafeProperty} from '../util/property';
|
||||
|
||||
|
||||
|
||||
@ -22,6 +23,8 @@ import {stringify} from '../util';
|
||||
*/
|
||||
export interface ForwardRefFn { (): any; }
|
||||
|
||||
const __forward_ref__ = getClosureSafeProperty({__forward_ref__: getClosureSafeProperty});
|
||||
|
||||
/**
|
||||
* Allows to refer to references which are not yet defined.
|
||||
*
|
||||
@ -53,10 +56,11 @@ export function forwardRef(forwardRefFn: ForwardRefFn): Type<any> {
|
||||
* @see `forwardRef`
|
||||
* @publicApi
|
||||
*/
|
||||
export function resolveForwardRef(type: any): any {
|
||||
if (typeof type === 'function' && type.hasOwnProperty('__forward_ref__') &&
|
||||
type.__forward_ref__ === forwardRef) {
|
||||
return (<ForwardRefFn>type)();
|
||||
export function resolveForwardRef<T>(type: T): T {
|
||||
const fn: any = type;
|
||||
if (typeof fn === 'function' && fn.hasOwnProperty(__forward_ref__) &&
|
||||
fn.__forward_ref__ === forwardRef) {
|
||||
return fn();
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
|
@ -431,6 +431,17 @@ export function setCurrentInjector(injector: Injector | null | undefined): Injec
|
||||
_currentInjector = injector;
|
||||
return former;
|
||||
}
|
||||
/**
|
||||
* Current implementation of inject.
|
||||
*
|
||||
* By default, it is `injectInjectorOnly`, which makes it `Injector`-only aware. It can be changed
|
||||
* to `directiveInject`, which brings in the `NodeInjector` system of ivy. It is designed this
|
||||
* way for two reasons:
|
||||
* 1. `Injector` should not depend on ivy logic.
|
||||
* 2. To maintain tree shake-ability we don't want to bring in unnecessary code.
|
||||
*/
|
||||
let _injectImplementation: (<T>(token: Type<T>| InjectionToken<T>, flags: InjectFlags) => T | null)|
|
||||
undefined;
|
||||
|
||||
/**
|
||||
* Injects a token from the currently active injector.
|
||||
@ -452,21 +463,53 @@ export function setCurrentInjector(injector: Injector | null | undefined): Injec
|
||||
export function inject<T>(token: Type<T>| InjectionToken<T>): T;
|
||||
export function inject<T>(token: Type<T>| InjectionToken<T>, flags?: InjectFlags): T|null;
|
||||
export function inject<T>(token: Type<T>| InjectionToken<T>, flags = InjectFlags.Default): T|null {
|
||||
return (_injectImplementation || injectInjectorOnly)(token, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current inject implementation.
|
||||
*/
|
||||
export function setInjectImplementation(
|
||||
impl: (<T>(token: Type<T>| InjectionToken<T>, flags?: InjectFlags) => T | null) | undefined):
|
||||
(<T>(token: Type<T>| InjectionToken<T>, flags?: InjectFlags) => T | null)|undefined {
|
||||
const previous = _injectImplementation;
|
||||
_injectImplementation = impl;
|
||||
return previous;
|
||||
}
|
||||
|
||||
export function injectInjectorOnly<T>(token: Type<T>| InjectionToken<T>): T;
|
||||
export function injectInjectorOnly<T>(token: Type<T>| InjectionToken<T>, flags?: InjectFlags): T|
|
||||
null;
|
||||
export function injectInjectorOnly<T>(
|
||||
token: Type<T>| InjectionToken<T>, flags = InjectFlags.Default): T|null {
|
||||
if (_currentInjector === undefined) {
|
||||
throw new Error(`inject() must be called from an injection context`);
|
||||
} else if (_currentInjector === null) {
|
||||
const injectableDef: InjectableDef<T>|null = getInjectableDef(token);
|
||||
if (injectableDef && injectableDef.providedIn == 'root') {
|
||||
return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() :
|
||||
injectableDef.value;
|
||||
}
|
||||
if (flags & InjectFlags.Optional) return null;
|
||||
throw new Error(`Injector: NOT_FOUND [${stringify(token)}]`);
|
||||
return injectRootLimpMode(token, undefined, flags);
|
||||
} else {
|
||||
return _currentInjector.get(token, flags & InjectFlags.Optional ? null : undefined, flags);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects `root` tokens in limp mode.
|
||||
*
|
||||
* If no injector exists, we can still inject tree-shakable providers which have `providedIn` set to
|
||||
* `"root"`. This is known as the limp mode injection. In such case the value is stored in the
|
||||
* `InjectableDef`.
|
||||
*/
|
||||
export function injectRootLimpMode<T>(
|
||||
token: Type<T>| InjectionToken<T>, notFoundValue: T | undefined, flags: InjectFlags): T|null {
|
||||
const injectableDef: InjectableDef<T>|null = getInjectableDef(token);
|
||||
if (injectableDef && injectableDef.providedIn == 'root') {
|
||||
return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() :
|
||||
injectableDef.value;
|
||||
}
|
||||
if (flags & InjectFlags.Optional) return null;
|
||||
if (notFoundValue !== undefined) return notFoundValue;
|
||||
throw new Error(`Injector: NOT_FOUND [${stringify(token)}]`);
|
||||
}
|
||||
|
||||
export function injectArgs(types: (Type<any>| InjectionToken<any>| any[])[]): any[] {
|
||||
const args: any[] = [];
|
||||
for (let i = 0; i < types.length; i++) {
|
||||
|
@ -165,7 +165,7 @@ export class R3Injector {
|
||||
if (def && this.injectableDefInScope(def)) {
|
||||
// Found an ngInjectableDef and it's scoped to this injector. Pretend as if it was here
|
||||
// all along.
|
||||
record = injectableDefRecord(token);
|
||||
record = makeRecord(injectableDefFactory(token), NOT_YET);
|
||||
this.records.set(token, record);
|
||||
}
|
||||
}
|
||||
@ -328,7 +328,7 @@ export class R3Injector {
|
||||
}
|
||||
}
|
||||
|
||||
function injectableDefRecord(token: Type<any>| InjectionToken<any>): Record<any> {
|
||||
function injectableDefFactory(token: Type<any>| InjectionToken<any>): () => any {
|
||||
const injectableDef = getInjectableDef(token as InjectableType<any>);
|
||||
if (injectableDef === null) {
|
||||
if (token instanceof InjectionToken) {
|
||||
@ -336,21 +336,34 @@ function injectableDefRecord(token: Type<any>| InjectionToken<any>): Record<any>
|
||||
}
|
||||
// TODO(alxhub): there should probably be a strict mode which throws here instead of assuming a
|
||||
// no-args constructor.
|
||||
return makeRecord(() => new (token as Type<any>)());
|
||||
return () => new (token as Type<any>)();
|
||||
}
|
||||
return makeRecord(injectableDef.factory);
|
||||
return injectableDef.factory;
|
||||
}
|
||||
|
||||
function providerToRecord(provider: SingleProvider): Record<any> {
|
||||
let factory: (() => any)|undefined = providerToFactory(provider);
|
||||
if (isValueProvider(provider)) {
|
||||
return makeRecord(undefined, provider.useValue);
|
||||
} else {
|
||||
return makeRecord(factory, NOT_YET);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a `SingleProvider` into a factory function.
|
||||
*
|
||||
* @param provider provider to convert to factory
|
||||
*/
|
||||
export function providerToFactory(provider: SingleProvider): () => any {
|
||||
let token = resolveForwardRef(provider);
|
||||
let value: any = NOT_YET;
|
||||
let factory: (() => any)|undefined = undefined;
|
||||
if (isTypeProvider(provider)) {
|
||||
return injectableDefRecord(provider);
|
||||
return injectableDefFactory(provider);
|
||||
} else {
|
||||
token = resolveForwardRef(provider.provide);
|
||||
if (isValueProvider(provider)) {
|
||||
value = provider.useValue;
|
||||
factory = () => provider.useValue;
|
||||
} else if (isExistingProvider(provider)) {
|
||||
factory = () => inject(provider.useExisting);
|
||||
} else if (isFactoryProvider(provider)) {
|
||||
@ -360,11 +373,11 @@ function providerToRecord(provider: SingleProvider): Record<any> {
|
||||
if (hasDeps(provider)) {
|
||||
factory = () => new (classRef)(...injectArgs(provider.deps));
|
||||
} else {
|
||||
return injectableDefRecord(classRef);
|
||||
return injectableDefFactory(classRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
return makeRecord(factory, value);
|
||||
return factory;
|
||||
}
|
||||
|
||||
function makeRecord<T>(
|
||||
@ -392,7 +405,7 @@ function isFactoryProvider(value: SingleProvider): value is FactoryProvider {
|
||||
return !!(value as FactoryProvider).useFactory;
|
||||
}
|
||||
|
||||
function isTypeProvider(value: SingleProvider): value is TypeProvider {
|
||||
export function isTypeProvider(value: SingleProvider): value is TypeProvider {
|
||||
return typeof value === 'function';
|
||||
}
|
||||
|
||||
|
@ -15,14 +15,16 @@ import {Sanitizer} from '../sanitization/security';
|
||||
import {assertComponentType, assertDefined} from './assert';
|
||||
import {getComponentViewByInstance} from './context_discovery';
|
||||
import {getComponentDef} from './definition';
|
||||
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
|
||||
import {queueInitHooks, queueLifecycleHooks} from './hooks';
|
||||
import {CLEAN_PROMISE, baseDirectiveCreate, createLViewData, createNodeAtIndex, createTView, detectChangesInternal, enterView, executeInitAndContentHooks, getOrCreateTView, leaveView, locateHostElement, prefillHostVars, resetComponentState, setHostBindings} from './instructions';
|
||||
import {CLEAN_PROMISE, createLViewData, createNodeAtIndex, createTView, detectChangesInternal, executeInitAndContentHooks, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, prefillHostVars, setHostBindings} from './instructions';
|
||||
import {ComponentDef, ComponentType} from './interfaces/definition';
|
||||
import {TElementNode, TNodeFlags, TNodeType} from './interfaces/node';
|
||||
import {PlayerHandler} from './interfaces/player';
|
||||
import {RElement, RNode, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
||||
import {CONTEXT, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, LViewData, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
|
||||
import {publishDefaultGlobalUtils} from './publish_global_util';
|
||||
import {enterView, leaveView, resetComponentState} from './state';
|
||||
import {getRootView, readElementValue, readPatchedLViewData, stringify} from './util';
|
||||
|
||||
|
||||
@ -130,13 +132,12 @@ export function renderComponent<T>(
|
||||
let component: T;
|
||||
try {
|
||||
if (rendererFactory.begin) rendererFactory.begin();
|
||||
|
||||
const componentView =
|
||||
createRootComponentView(hostRNode, componentDef, rootView, renderer, sanitizer);
|
||||
component = createRootComponent(
|
||||
hostRNode, componentView, componentDef, rootView, rootContext, opts.hostFeatures || null);
|
||||
|
||||
executeInitAndContentHooks();
|
||||
executeInitAndContentHooks(rootView);
|
||||
detectChangesInternal(componentView, component);
|
||||
} finally {
|
||||
leaveView(oldView);
|
||||
@ -171,9 +172,9 @@ export function createRootComponentView(
|
||||
|
||||
if (tView.firstTemplatePass) {
|
||||
tView.expandoInstructions = ROOT_EXPANDO_INSTRUCTIONS.slice();
|
||||
if (def.diPublic) def.diPublic(def);
|
||||
tNode.flags =
|
||||
rootView.length << TNodeFlags.DirectiveStartingIndexShift | TNodeFlags.isComponent;
|
||||
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, rootView), rootView, def.type);
|
||||
tNode.flags = TNodeFlags.isComponent;
|
||||
initNodeFlags(tNode, rootView.length, 1);
|
||||
}
|
||||
|
||||
// Store component view at node index, with node as the HOST
|
||||
@ -189,16 +190,17 @@ export function createRootComponentView(
|
||||
export function createRootComponent<T>(
|
||||
hostRNode: RNode | null, componentView: LViewData, componentDef: ComponentDef<T>,
|
||||
rootView: LViewData, rootContext: RootContext, hostFeatures: HostFeature[] | null): any {
|
||||
const tView = rootView[TVIEW];
|
||||
// Create directive instance with factory() and store at next index in viewData
|
||||
const component =
|
||||
baseDirectiveCreate(rootView.length, componentDef.factory() as T, componentDef, hostRNode);
|
||||
const component = instantiateRootComponent(tView, rootView, componentDef);
|
||||
|
||||
rootContext.components.push(component);
|
||||
componentView[CONTEXT] = component;
|
||||
|
||||
hostFeatures && hostFeatures.forEach((feature) => feature(component, componentDef));
|
||||
if (rootView[TVIEW].firstTemplatePass) prefillHostVars(componentDef.hostVars);
|
||||
setHostBindings();
|
||||
|
||||
if (tView.firstTemplatePass) prefillHostVars(tView, rootView, componentDef.hostVars);
|
||||
setHostBindings(tView, rootView);
|
||||
return component;
|
||||
}
|
||||
|
||||
|
@ -19,11 +19,12 @@ import {Type} from '../type';
|
||||
import {assertComponentType, assertDefined} from './assert';
|
||||
import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component';
|
||||
import {getComponentDef} from './definition';
|
||||
import {adjustBlueprintForNewNode, createLViewData, createNodeAtIndex, createTView, createViewNode, elementCreate, enterView, locateHostElement, renderEmbeddedTemplate} from './instructions';
|
||||
import {createLViewData, createNodeAtIndex, createTView, createViewNode, elementCreate, locateHostElement, renderEmbeddedTemplate} from './instructions';
|
||||
import {ComponentDef, RenderFlags} from './interfaces/definition';
|
||||
import {TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
|
||||
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
||||
import {FLAGS, INJECTOR, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view';
|
||||
import {FLAGS, HEADER_OFFSET, INJECTOR, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view';
|
||||
import {enterView} from './state';
|
||||
import {getTNode} from './util';
|
||||
import {createElementRef} from './view_engine_compatibility';
|
||||
import {RootViewRef, ViewRef} from './view_ref';
|
||||
@ -114,9 +115,6 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
||||
elementCreate(this.selector, rendererFactory.createRenderer(null, this.componentDef)) :
|
||||
locateHostElement(rendererFactory, rootSelectorOrNode);
|
||||
|
||||
// The first index of the first selector is the tag name.
|
||||
const componentTag = this.componentDef.selectors ![0] ![0] as string;
|
||||
|
||||
const rootFlags = this.componentDef.onPush ? LViewFlags.Dirty | LViewFlags.IsRoot :
|
||||
LViewFlags.CheckAlways | LViewFlags.IsRoot;
|
||||
const rootContext: RootContext = ngModule && !isInternalRootView ?
|
||||
@ -145,15 +143,25 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
||||
// projection instruction. This is needed to support the reprojection of these nodes.
|
||||
if (projectableNodes) {
|
||||
let index = 0;
|
||||
const tView = rootView[TVIEW];
|
||||
const projection: TNode[] = tElementNode.projection = [];
|
||||
for (let i = 0; i < projectableNodes.length; i++) {
|
||||
const nodeList = projectableNodes[i];
|
||||
let firstTNode: TNode|null = null;
|
||||
let previousTNode: TNode|null = null;
|
||||
for (let j = 0; j < nodeList.length; j++) {
|
||||
adjustBlueprintForNewNode(rootView);
|
||||
if (tView.firstTemplatePass) {
|
||||
// For dynamically created components such as ComponentRef, we create a new TView for
|
||||
// each insert. This is not ideal since we should be sharing the TViews.
|
||||
// Also the logic here should be shared with `component.ts`'s `renderComponent`
|
||||
// method.
|
||||
tView.expandoStartIndex++;
|
||||
tView.blueprint.splice(++index + HEADER_OFFSET, 0, null);
|
||||
tView.data.splice(index + HEADER_OFFSET, 0, null);
|
||||
rootView.splice(index + HEADER_OFFSET, 0, null);
|
||||
}
|
||||
const tNode =
|
||||
createNodeAtIndex(++index, TNodeType.Element, nodeList[j] as RElement, null, null);
|
||||
createNodeAtIndex(index, TNodeType.Element, nodeList[j] as RElement, null, null);
|
||||
previousTNode ? (previousTNode.next = tNode) : (firstTNode = tNode);
|
||||
previousTNode = tNode;
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ export function defineComponent<T>(componentDefinition: {
|
||||
/**
|
||||
* Factory method used to create an instance of directive.
|
||||
*/
|
||||
factory: () => T;
|
||||
factory: (t: Type<T>| null) => T;
|
||||
|
||||
/**
|
||||
* The number of nodes, local refs, and pipes in this component template.
|
||||
@ -154,7 +154,7 @@ export function defineComponent<T>(componentDefinition: {
|
||||
/**
|
||||
* Function to create instances of content queries associated with a given directive.
|
||||
*/
|
||||
contentQueries?: (() => void);
|
||||
contentQueries?: ((dirIndex: number) => void);
|
||||
|
||||
/** Refreshes content queries associated with directives in a given view */
|
||||
contentQueriesRefresh?: ((directiveIndex: number, queryIndex: number) => void);
|
||||
@ -210,7 +210,7 @@ export function defineComponent<T>(componentDefinition: {
|
||||
/**
|
||||
* A list of optional features to apply.
|
||||
*
|
||||
* See: {@link NgOnChangesFeature}, {@link PublicFeature}
|
||||
* See: {@link NgOnChangesFeature}, {@link ProvidersFeature}
|
||||
*/
|
||||
features?: ComponentDefFeature[];
|
||||
|
||||
@ -238,17 +238,6 @@ export function defineComponent<T>(componentDefinition: {
|
||||
*/
|
||||
changeDetection?: ChangeDetectionStrategy;
|
||||
|
||||
/**
|
||||
* Defines the set of injectable objects that are visible to a Directive and its light DOM
|
||||
* children.
|
||||
*/
|
||||
providers?: Provider[];
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@ -270,7 +259,7 @@ export function defineComponent<T>(componentDefinition: {
|
||||
const declaredInputs: {[key: string]: string} = {} as any;
|
||||
const def: Mutable<ComponentDef<any>, keyof ComponentDef<any>> = {
|
||||
type: type,
|
||||
diPublic: null,
|
||||
providersResolver: null,
|
||||
consts: componentDefinition.consts,
|
||||
vars: componentDefinition.vars,
|
||||
hostVars: componentDefinition.hostVars || 0,
|
||||
@ -301,8 +290,6 @@ export function defineComponent<T>(componentDefinition: {
|
||||
// TODO(misko): convert ViewEncapsulation into const enum so that it can be used directly in the
|
||||
// next line. Also `None` should be 0 not 2.
|
||||
encapsulation: componentDefinition.encapsulation || ViewEncapsulation.Emulated,
|
||||
providers: EMPTY_ARRAY,
|
||||
viewProviders: EMPTY_ARRAY,
|
||||
id: 'c',
|
||||
styles: componentDefinition.styles || EMPTY_ARRAY,
|
||||
_: null as never,
|
||||
@ -525,7 +512,7 @@ export const defineDirective = defineComponent as any as<T>(directiveDefinition:
|
||||
/**
|
||||
* Factory method used to create an instance of directive.
|
||||
*/
|
||||
factory: () => T;
|
||||
factory: (t: Type<T>| null) => T;
|
||||
|
||||
/**
|
||||
* Static attributes to set on host element.
|
||||
@ -595,7 +582,7 @@ export const defineDirective = defineComponent as any as<T>(directiveDefinition:
|
||||
/**
|
||||
* A list of optional features to apply.
|
||||
*
|
||||
* See: {@link NgOnChangesFeature}, {@link PublicFeature}, {@link InheritDefinitionFeature}
|
||||
* See: {@link NgOnChangesFeature}, {@link ProvidersFeature}, {@link InheritDefinitionFeature}
|
||||
*/
|
||||
features?: DirectiveDefFeature[];
|
||||
|
||||
@ -615,7 +602,7 @@ export const defineDirective = defineComponent as any as<T>(directiveDefinition:
|
||||
/**
|
||||
* Function to create instances of content queries associated with a given directive.
|
||||
*/
|
||||
contentQueries?: (() => void);
|
||||
contentQueries?: ((directiveIndex: number) => void);
|
||||
|
||||
/** Refreshes content queries associated with directives in a given view */
|
||||
contentQueriesRefresh?: ((directiveIndex: number, queryIndex: number) => void);
|
||||
@ -650,7 +637,7 @@ export function definePipe<T>(pipeDef: {
|
||||
type: Type<T>,
|
||||
|
||||
/** A factory for creating a pipe instance. */
|
||||
factory: () => T,
|
||||
factory: (t: Type<T>| null) => T,
|
||||
|
||||
/** Whether the pipe is pure. */
|
||||
pure?: boolean
|
||||
|
@ -6,23 +6,67 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
// We are temporarily importing the existing viewEngine_from core so we can be sure we are
|
||||
// correctly implementing its interfaces for backwards compatibility.
|
||||
|
||||
import {getInjectableDef, getInjectorDef} from '../di/defs';
|
||||
import {InjectionToken} from '../di/injection_token';
|
||||
import {InjectFlags, Injector, inject, setCurrentInjector} from '../di/injector';
|
||||
import {InjectFlags, Injector, NullInjector, injectRootLimpMode, setInjectImplementation} from '../di/injector';
|
||||
import {Type} from '../type';
|
||||
|
||||
import {assertDefined} from './assert';
|
||||
import {assertDefined, assertEqual} from './assert';
|
||||
import {getComponentDef, getDirectiveDef, getPipeDef} from './definition';
|
||||
import {NG_ELEMENT_ID} from './fields';
|
||||
import {_getViewData, getPreviousOrParentTNode, resolveDirective, setEnvironment} from './instructions';
|
||||
import {DirectiveDef} from './interfaces/definition';
|
||||
import {InjectorLocationFlags, PARENT_INJECTOR, TNODE,} from './interfaces/injector';
|
||||
import {AttributeMarker, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
|
||||
import {NO_PARENT_INJECTOR, NodeInjectorFactory, PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFlags, TNODE, isFactory} from './interfaces/injector';
|
||||
import {AttributeMarker, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType} from './interfaces/node';
|
||||
import {DECLARATION_VIEW, HOST_NODE, INJECTOR, LViewData, TData, TVIEW, TView} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes} from './node_assert';
|
||||
import {getPreviousOrParentTNode, getViewData, setTNodeAndViewData} from './state';
|
||||
import {getParentInjectorIndex, getParentInjectorView, getParentInjectorViewOffset, hasParentInjector, isComponent, stringify} from './util';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Defines if the call to `inject` should include `viewProviders` in its resolution.
|
||||
*
|
||||
* This is set to true when we try to instantiate a component. This value is reset in
|
||||
* `getNodeInjectable` to a value which matches the declaration location of the token about to be
|
||||
* instantiated. This is done so that if we are injecting a token which was declared outside of
|
||||
* `viewProviders` we don't accidentally pull `viewProviders` in.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @Injectable()
|
||||
* class MyService {
|
||||
* constructor(public value: String) {}
|
||||
* }
|
||||
*
|
||||
* @Component({
|
||||
* providers: [
|
||||
* MyService,
|
||||
* {provide: String, value: 'providers' }
|
||||
* ]
|
||||
* viewProviders: [
|
||||
* {provide: String, value: 'viewProviders'}
|
||||
* ]
|
||||
* })
|
||||
* class MyComponent {
|
||||
* constructor(myService: MyService, value: String) {
|
||||
* // We expect that Component can see into `viewProviders`.
|
||||
* expect(value).toEqual('viewProviders');
|
||||
* // `MyService` was not declared in `viewProviders` hence it can't see it.
|
||||
* expect(myService.value).toEqual('providers');
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
let includeViewProviders = false;
|
||||
|
||||
function setIncludeViewProviders(v: boolean): boolean {
|
||||
const oldValue = includeViewProviders;
|
||||
includeViewProviders = v;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of slots in each bloom filter (used by DI). The larger this number, the fewer
|
||||
@ -43,46 +87,40 @@ let nextNgElementId = 0;
|
||||
* @param tView The TView for the injector's bloom filters
|
||||
* @param type The directive token to register
|
||||
*/
|
||||
export function bloomAdd(injectorIndex: number, tView: TView, type: Type<any>): void {
|
||||
if (tView.firstTemplatePass) {
|
||||
let id: number|undefined = (type as any)[NG_ELEMENT_ID];
|
||||
export function bloomAdd(
|
||||
injectorIndex: number, tView: TView, type: Type<any>| InjectionToken<any>): void {
|
||||
ngDevMode && assertEqual(tView.firstTemplatePass, true, 'expected firstTemplatePass to be true');
|
||||
let id: number|undefined = (type as any)[NG_ELEMENT_ID];
|
||||
|
||||
// Set a unique ID on the directive type, so if something tries to inject the directive,
|
||||
// we can easily retrieve the ID and hash it into the bloom bit that should be checked.
|
||||
if (id == null) {
|
||||
id = (type as any)[NG_ELEMENT_ID] = nextNgElementId++;
|
||||
}
|
||||
|
||||
// We only have BLOOM_SIZE (256) slots in our bloom filter (8 buckets * 32 bits each),
|
||||
// so all unique IDs must be modulo-ed into a number from 0 - 255 to fit into the filter.
|
||||
const bloomBit = id & BLOOM_MASK;
|
||||
|
||||
// Create a mask that targets the specific bit associated with the directive.
|
||||
// JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding
|
||||
// to bit positions 0 - 31 in a 32 bit integer.
|
||||
const mask = 1 << bloomBit;
|
||||
|
||||
// Use the raw bloomBit number to determine which bloom filter bucket we should check
|
||||
// e.g: bf0 = [0 - 31], bf1 = [32 - 63], bf2 = [64 - 95], bf3 = [96 - 127], etc
|
||||
const b7 = bloomBit & 0x80;
|
||||
const b6 = bloomBit & 0x40;
|
||||
const b5 = bloomBit & 0x20;
|
||||
const tData = tView.data as number[];
|
||||
|
||||
if (b7) {
|
||||
b6 ? (b5 ? (tData[injectorIndex + 7] |= mask) : (tData[injectorIndex + 6] |= mask)) :
|
||||
(b5 ? (tData[injectorIndex + 5] |= mask) : (tData[injectorIndex + 4] |= mask));
|
||||
} else {
|
||||
b6 ? (b5 ? (tData[injectorIndex + 3] |= mask) : (tData[injectorIndex + 2] |= mask)) :
|
||||
(b5 ? (tData[injectorIndex + 1] |= mask) : (tData[injectorIndex] |= mask));
|
||||
}
|
||||
// Set a unique ID on the directive type, so if something tries to inject the directive,
|
||||
// we can easily retrieve the ID and hash it into the bloom bit that should be checked.
|
||||
if (id == null) {
|
||||
id = (type as any)[NG_ELEMENT_ID] = nextNgElementId++;
|
||||
}
|
||||
}
|
||||
|
||||
export function getOrCreateNodeInjector(): number {
|
||||
return getOrCreateNodeInjectorForNode(
|
||||
getPreviousOrParentTNode() as TElementNode | TElementContainerNode | TContainerNode,
|
||||
_getViewData());
|
||||
// We only have BLOOM_SIZE (256) slots in our bloom filter (8 buckets * 32 bits each),
|
||||
// so all unique IDs must be modulo-ed into a number from 0 - 255 to fit into the filter.
|
||||
const bloomBit = id & BLOOM_MASK;
|
||||
|
||||
// Create a mask that targets the specific bit associated with the directive.
|
||||
// JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding
|
||||
// to bit positions 0 - 31 in a 32 bit integer.
|
||||
const mask = 1 << bloomBit;
|
||||
|
||||
// Use the raw bloomBit number to determine which bloom filter bucket we should check
|
||||
// e.g: bf0 = [0 - 31], bf1 = [32 - 63], bf2 = [64 - 95], bf3 = [96 - 127], etc
|
||||
const b7 = bloomBit & 0x80;
|
||||
const b6 = bloomBit & 0x40;
|
||||
const b5 = bloomBit & 0x20;
|
||||
const tData = tView.data as number[];
|
||||
|
||||
if (b7) {
|
||||
b6 ? (b5 ? (tData[injectorIndex + 7] |= mask) : (tData[injectorIndex + 6] |= mask)) :
|
||||
(b5 ? (tData[injectorIndex + 5] |= mask) : (tData[injectorIndex + 4] |= mask));
|
||||
} else {
|
||||
b6 ? (b5 ? (tData[injectorIndex + 3] |= mask) : (tData[injectorIndex + 2] |= mask)) :
|
||||
(b5 ? (tData[injectorIndex + 1] |= mask) : (tData[injectorIndex] |= mask));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,26 +140,29 @@ export function getOrCreateNodeInjectorForNode(
|
||||
const tView = hostView[TVIEW];
|
||||
if (tView.firstTemplatePass) {
|
||||
tNode.injectorIndex = hostView.length;
|
||||
setUpBloom(tView.data, tNode); // foundation for node bloom
|
||||
setUpBloom(hostView, null); // foundation for cumulative bloom
|
||||
setUpBloom(tView.blueprint, null);
|
||||
insertBloom(tView.data, tNode); // foundation for node bloom
|
||||
insertBloom(hostView, null); // foundation for cumulative bloom
|
||||
insertBloom(tView.blueprint, null);
|
||||
|
||||
ngDevMode && assertEqual(
|
||||
tNode.flags === 0 || tNode.flags === TNodeFlags.isComponent, true,
|
||||
'expected tNode.flags to not be initialized');
|
||||
}
|
||||
|
||||
const parentLoc = getParentInjectorLocation(tNode, hostView);
|
||||
const parentIndex = parentLoc & InjectorLocationFlags.InjectorIndexMask;
|
||||
const parentIndex = getParentInjectorIndex(parentLoc);
|
||||
const parentView: LViewData = getParentInjectorView(parentLoc, hostView);
|
||||
|
||||
const parentData = parentView[TVIEW].data as any;
|
||||
const injectorIndex = tNode.injectorIndex;
|
||||
|
||||
// If a parent injector can't be found, its location is set to -1.
|
||||
// In that case, we don't need to set up a cumulative bloom
|
||||
if (parentLoc !== -1) {
|
||||
for (let i = 0; i < PARENT_INJECTOR; i++) {
|
||||
const bloomIndex = parentIndex + i;
|
||||
// Creates a cumulative bloom filter that merges the parent's bloom filter
|
||||
// and its own cumulative bloom (which contains tokens for all ancestors)
|
||||
hostView[injectorIndex + i] = parentView[bloomIndex] | parentData[bloomIndex];
|
||||
if (hasParentInjector(parentLoc)) {
|
||||
const parentData = parentView[TVIEW].data as any;
|
||||
// Creates a cumulative bloom filter that merges the parent's bloom filter
|
||||
// and its own cumulative bloom (which contains tokens for all ancestors)
|
||||
for (let i = 0; i < 8; i++) {
|
||||
hostView[injectorIndex + i] = parentView[parentIndex + i] | parentData[parentIndex + i];
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,10 +170,11 @@ export function getOrCreateNodeInjectorForNode(
|
||||
return injectorIndex;
|
||||
}
|
||||
|
||||
function setUpBloom(arr: any[], footer: TNode | null) {
|
||||
function insertBloom(arr: any[], footer: TNode | null): void {
|
||||
arr.push(0, 0, 0, 0, 0, 0, 0, 0, footer);
|
||||
}
|
||||
|
||||
|
||||
export function getInjectorIndex(tNode: TNode, hostView: LViewData): number {
|
||||
if (tNode.injectorIndex === -1 ||
|
||||
// If the injector index is the same as its parent's injector index, then the index has been
|
||||
@ -150,10 +192,12 @@ export function getInjectorIndex(tNode: TNode, hostView: LViewData): number {
|
||||
/**
|
||||
* Finds the index of the parent injector, with a view offset if applicable. Used to set the
|
||||
* parent injector initially.
|
||||
*
|
||||
* Returns a combination of number of `ViewData` we have to go up and index in that `Viewdata`
|
||||
*/
|
||||
export function getParentInjectorLocation(tNode: TNode, view: LViewData): number {
|
||||
export function getParentInjectorLocation(tNode: TNode, view: LViewData): RelativeInjectorLocation {
|
||||
if (tNode.parent && tNode.parent.injectorIndex !== -1) {
|
||||
return tNode.parent.injectorIndex; // view offset is 0
|
||||
return tNode.parent.injectorIndex as any; // view offset is 0
|
||||
}
|
||||
|
||||
// For most cases, the parent injector index can be found on the host node (e.g. for component
|
||||
@ -167,81 +211,20 @@ export function getParentInjectorLocation(tNode: TNode, view: LViewData): number
|
||||
viewOffset++;
|
||||
}
|
||||
return hostTNode ?
|
||||
hostTNode.injectorIndex | (viewOffset << InjectorLocationFlags.ViewOffsetShift) :
|
||||
-1;
|
||||
hostTNode.injectorIndex | (viewOffset << RelativeInjectorLocationFlags.ViewOffsetShift) :
|
||||
-1 as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwraps a parent injector location number to find the view offset from the current injector,
|
||||
* then walks up the declaration view tree until the view is found that contains the parent
|
||||
* injector.
|
||||
*
|
||||
* @param location The location of the parent injector, which contains the view offset
|
||||
* @param startView The LViewData instance from which to start walking up the view tree
|
||||
* @returns The LViewData instance that contains the parent injector
|
||||
*/
|
||||
export function getParentInjectorView(location: number, startView: LViewData): LViewData {
|
||||
let viewOffset = location >> InjectorLocationFlags.ViewOffsetShift;
|
||||
let parentView = startView;
|
||||
// For most cases, the parent injector can be found on the host node (e.g. for component
|
||||
// or container), but we must keep the loop here to support the rarer case of deeply nested
|
||||
// <ng-template> tags or inline views, where the parent injector might live many views
|
||||
// above the child injector.
|
||||
while (viewOffset > 0) {
|
||||
parentView = parentView[DECLARATION_VIEW] !;
|
||||
viewOffset--;
|
||||
}
|
||||
return parentView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a directive public to the DI system by adding it to an injector's bloom filter.
|
||||
* Makes a type or an injection token public to the DI system by adding it to an
|
||||
* injector's bloom filter.
|
||||
*
|
||||
* @param di The node injector in which a directive will be added
|
||||
* @param def The definition of the directive to be made public
|
||||
* @param token The type or the injection token to be made public
|
||||
*/
|
||||
export function diPublicInInjector(
|
||||
injectorIndex: number, view: LViewData, def: DirectiveDef<any>): void {
|
||||
bloomAdd(injectorIndex, view[TVIEW], def.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a directive public to the DI system by adding it to an injector's bloom filter.
|
||||
*
|
||||
* @param def The definition of the directive to be made public
|
||||
*/
|
||||
export function diPublic(def: DirectiveDef<any>): void {
|
||||
diPublicInInjector(getOrCreateNodeInjector(), _getViewData(), def);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value associated to the given token from the injectors.
|
||||
*
|
||||
* `directiveInject` is intended to be used for directive, component and pipe factories.
|
||||
* All other injection use `inject` which does not walk the node injector tree.
|
||||
*
|
||||
* Usage example (in factory function):
|
||||
*
|
||||
* class SomeDirective {
|
||||
* constructor(directive: DirectiveA) {}
|
||||
*
|
||||
* static ngDirectiveDef = defineDirective({
|
||||
* type: SomeDirective,
|
||||
* factory: () => new SomeDirective(directiveInject(DirectiveA))
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* @param token the type or token to inject
|
||||
* @param flags Injection flags
|
||||
* @returns the value from the injector or `null` when not found
|
||||
*/
|
||||
export function directiveInject<T>(token: Type<T>| InjectionToken<T>): T;
|
||||
export function directiveInject<T>(token: Type<T>| InjectionToken<T>, flags: InjectFlags): T;
|
||||
export function directiveInject<T>(
|
||||
token: Type<T>| InjectionToken<T>, flags = InjectFlags.Default): T|null {
|
||||
const hostTNode =
|
||||
getPreviousOrParentTNode() as TElementNode | TContainerNode | TElementContainerNode;
|
||||
return getOrCreateInjectable<T>(hostTNode, _getViewData(), token, flags);
|
||||
injectorIndex: number, view: LViewData, token: InjectionToken<any>| Type<any>): void {
|
||||
bloomAdd(injectorIndex, view[TVIEW], token);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -275,8 +258,7 @@ export function directiveInject<T>(
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function injectAttribute(attrNameToInject: string): string|undefined {
|
||||
const tNode = getPreviousOrParentTNode();
|
||||
export function injectAttributeImpl(tNode: TNode, attrNameToInject: string): string|undefined {
|
||||
ngDevMode && assertNodeOfPossibleTypes(
|
||||
tNode, TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer);
|
||||
ngDevMode && assertDefined(tNode, 'expecting tNode');
|
||||
@ -295,7 +277,7 @@ export function injectAttribute(attrNameToInject: string): string|undefined {
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value associated to the given token from the injectors.
|
||||
* Returns the value associated to the given token from the NodeInjectors => ModuleInjector.
|
||||
*
|
||||
* Look for the injector providing the token by walking up the node injector tree and then
|
||||
* the module injector tree.
|
||||
@ -306,133 +288,169 @@ export function injectAttribute(attrNameToInject: string): string|undefined {
|
||||
* @returns the value from the injector or `null` when not found
|
||||
*/
|
||||
export function getOrCreateInjectable<T>(
|
||||
hostTNode: TElementNode | TContainerNode | TElementContainerNode, hostView: LViewData,
|
||||
token: Type<T>| InjectionToken<T>, flags: InjectFlags = InjectFlags.Default): T|null {
|
||||
tNode: TElementNode | TContainerNode | TElementContainerNode, lViewData: LViewData,
|
||||
token: Type<T>| InjectionToken<T>, flags: InjectFlags = InjectFlags.Default,
|
||||
notFoundValue?: any): T|null {
|
||||
const bloomHash = bloomHashBitOrFactory(token);
|
||||
// If the ID stored here is a function, this is a special object like ElementRef or TemplateRef
|
||||
// so just call the factory function to create it.
|
||||
if (typeof bloomHash === 'function') return bloomHash();
|
||||
if (typeof bloomHash === 'function') {
|
||||
const savePreviousOrParentTNode = getPreviousOrParentTNode();
|
||||
const saveViewData = getViewData();
|
||||
setTNodeAndViewData(tNode, lViewData);
|
||||
try {
|
||||
return bloomHash();
|
||||
} finally {
|
||||
setTNodeAndViewData(savePreviousOrParentTNode, saveViewData);
|
||||
}
|
||||
} else if (typeof bloomHash == 'number') {
|
||||
// If the token has a bloom hash, then it is a token which could be in NodeInjector.
|
||||
|
||||
// If the token has a bloom hash, then it is a directive that is public to the injection system
|
||||
// (diPublic) otherwise fall back to the module injector.
|
||||
if (bloomHash != null) {
|
||||
const startInjectorIndex = getInjectorIndex(hostTNode, hostView);
|
||||
// A reference to the previous injector TView that was found while climbing the element injector
|
||||
// tree. This is used to know if viewProviders can be accessed on the current injector.
|
||||
let previousTView: TView|null = null;
|
||||
let injectorIndex = getInjectorIndex(tNode, lViewData);
|
||||
let parentLocation: RelativeInjectorLocation = NO_PARENT_INJECTOR;
|
||||
|
||||
let injectorIndex = startInjectorIndex;
|
||||
let injectorView = hostView;
|
||||
let parentLocation: number = -1;
|
||||
// If we should skip this injector, start by searching the parent injector.
|
||||
if (flags & InjectFlags.SkipSelf) {
|
||||
parentLocation = injectorIndex === -1 ? getParentInjectorLocation(tNode, lViewData) :
|
||||
lViewData[injectorIndex + PARENT_INJECTOR];
|
||||
|
||||
// If we should skip this injector or if an injector doesn't exist on this node (e.g. all
|
||||
// directives on this node are private), start by searching the parent injector.
|
||||
if (flags & InjectFlags.SkipSelf || injectorIndex === -1) {
|
||||
parentLocation = injectorIndex === -1 ? getParentInjectorLocation(hostTNode, hostView) :
|
||||
injectorView[injectorIndex + PARENT_INJECTOR];
|
||||
|
||||
if (shouldNotSearchParent(flags, parentLocation)) {
|
||||
if (!shouldSearchParent(flags, parentLocation)) {
|
||||
injectorIndex = -1;
|
||||
} else {
|
||||
injectorIndex = parentLocation & InjectorLocationFlags.InjectorIndexMask;
|
||||
injectorView = getParentInjectorView(parentLocation, injectorView);
|
||||
previousTView = lViewData[TVIEW];
|
||||
injectorIndex = getParentInjectorIndex(parentLocation);
|
||||
lViewData = getParentInjectorView(parentLocation, lViewData);
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse up the injector tree until we find a potential match or until we know there
|
||||
// *isn't* a match.
|
||||
while (injectorIndex !== -1) {
|
||||
// Traverse up the injector tree until we find a potential match or until we know there
|
||||
// *isn't* a match. Outer loop is necessary in case we get a false positive injector.
|
||||
while (injectorIndex !== -1) {
|
||||
// Check the current injector. If it matches, stop searching for an injector.
|
||||
if (injectorHasToken(bloomHash, injectorIndex, injectorView[TVIEW].data)) {
|
||||
break;
|
||||
}
|
||||
parentLocation = lViewData[injectorIndex + PARENT_INJECTOR];
|
||||
|
||||
parentLocation = injectorView[injectorIndex + PARENT_INJECTOR];
|
||||
if (shouldNotSearchParent(flags, parentLocation)) {
|
||||
injectorIndex = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the ancestor bloom filter value has the bit corresponding to the directive, traverse
|
||||
// up to find the specific injector. If the ancestor bloom filter does not have the bit, we
|
||||
// can abort.
|
||||
if (injectorHasToken(bloomHash, injectorIndex, injectorView)) {
|
||||
injectorIndex = parentLocation & InjectorLocationFlags.InjectorIndexMask;
|
||||
injectorView = getParentInjectorView(parentLocation, injectorView);
|
||||
} else {
|
||||
injectorIndex = -1;
|
||||
break;
|
||||
// Check the current injector. If it matches, see if it contains token.
|
||||
const tView = lViewData[TVIEW];
|
||||
if (bloomHasToken(bloomHash, injectorIndex, tView.data)) {
|
||||
// At this point, we have an injector which *may* contain the token, so we step through
|
||||
// the providers and directives associated with the injector's corresponding node to get
|
||||
// the instance.
|
||||
const instance: T|null =
|
||||
searchTokensOnInjector<T>(injectorIndex, lViewData, token, previousTView);
|
||||
if (instance !== NOT_FOUND) {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
// If no injector is found, we *know* that there is no ancestor injector that contains the
|
||||
// token, so we abort.
|
||||
if (injectorIndex === -1) {
|
||||
break;
|
||||
if (shouldSearchParent(flags, parentLocation) &&
|
||||
bloomHasToken(bloomHash, injectorIndex, lViewData)) {
|
||||
// The def wasn't found anywhere on this node, so it was a false positive.
|
||||
// Traverse up the tree and continue searching.
|
||||
previousTView = tView;
|
||||
injectorIndex = getParentInjectorIndex(parentLocation);
|
||||
lViewData = getParentInjectorView(parentLocation, lViewData);
|
||||
} else {
|
||||
// If we should not search parent OR If the ancestor bloom filter value does not have the
|
||||
// bit corresponding to the directive we can give up on traversing up to find the specific
|
||||
// injector.
|
||||
injectorIndex = -1;
|
||||
}
|
||||
|
||||
// At this point, we have an injector which *may* contain the token, so we step through the
|
||||
// directives associated with the injector's corresponding node to get the directive instance.
|
||||
let instance: T|null;
|
||||
if (instance = searchDirectivesOnInjector<T>(injectorIndex, injectorView, token)) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
// If we *didn't* find the directive for the token and we are searching the current node's
|
||||
// injector, it's possible the directive is on this node and hasn't been created yet.
|
||||
if (injectorIndex === startInjectorIndex && hostView === injectorView &&
|
||||
(instance = searchMatchesQueuedForCreation<T>(token, injectorView[TVIEW]))) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
// The def wasn't found anywhere on this node, so it was a false positive.
|
||||
// Traverse up the tree and continue searching.
|
||||
injectorIndex = parentLocation & InjectorLocationFlags.InjectorIndexMask;
|
||||
injectorView = getParentInjectorView(parentLocation, injectorView);
|
||||
}
|
||||
}
|
||||
|
||||
const moduleInjector = hostView[INJECTOR];
|
||||
const formerInjector = setCurrentInjector(moduleInjector);
|
||||
try {
|
||||
return inject(token, flags);
|
||||
} finally {
|
||||
setCurrentInjector(formerInjector);
|
||||
}
|
||||
}
|
||||
|
||||
function searchMatchesQueuedForCreation<T>(token: any, hostTView: TView): T|null {
|
||||
const matches = hostTView.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);
|
||||
}
|
||||
if ((flags & (InjectFlags.Self | InjectFlags.Host)) === 0) {
|
||||
const moduleInjector = lViewData[INJECTOR];
|
||||
if (moduleInjector) {
|
||||
return moduleInjector.get(token, notFoundValue, flags & InjectFlags.Optional);
|
||||
} else {
|
||||
return injectRootLimpMode(token, notFoundValue, flags & InjectFlags.Optional);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
if (flags & InjectFlags.Optional) {
|
||||
return notFoundValue;
|
||||
} else {
|
||||
throw new Error(`NodeInjector: NOT_FOUND [${stringify(token)}]`);
|
||||
}
|
||||
}
|
||||
|
||||
function searchDirectivesOnInjector<T>(
|
||||
injectorIndex: number, injectorView: LViewData, token: Type<T>| InjectionToken<T>) {
|
||||
const tNode = injectorView[TVIEW].data[injectorIndex + TNODE] as TNode;
|
||||
const NOT_FOUND = {};
|
||||
|
||||
function searchTokensOnInjector<T>(
|
||||
injectorIndex: number, injectorView: LViewData, token: Type<T>| InjectionToken<T>,
|
||||
previousTView: TView | null) {
|
||||
const currentTView = injectorView[TVIEW];
|
||||
const tNode = currentTView.data[injectorIndex + TNODE] as TNode;
|
||||
const nodeFlags = tNode.flags;
|
||||
const count = nodeFlags & TNodeFlags.DirectiveCountMask;
|
||||
|
||||
if (count !== 0) {
|
||||
const start = nodeFlags >> TNodeFlags.DirectiveStartingIndexShift;
|
||||
const end = start + count;
|
||||
const defs = injectorView[TVIEW].data;
|
||||
|
||||
for (let i = start; i < end; i++) {
|
||||
// Get the definition for the directive at this index and, if it is injectable (diPublic),
|
||||
// and matches the given token, return the directive instance.
|
||||
const directiveDef = defs[i] as DirectiveDef<any>;
|
||||
if (directiveDef.type === token && directiveDef.diPublic) {
|
||||
return injectorView[i];
|
||||
}
|
||||
const nodeProviderIndexes = tNode.providerIndexes;
|
||||
const tInjectables = currentTView.data;
|
||||
// First, we step through providers
|
||||
let canAccessViewProviders = false;
|
||||
// We need to determine if view providers can be accessed by the starting element.
|
||||
// It happens in 2 cases:
|
||||
// 1) On the initial element injector , if we are instantiating a token which can see the
|
||||
// viewProviders of the component of that element. Such token are:
|
||||
// - the component itself (but not other directives)
|
||||
// - viewProviders tokens of the component (but not providers tokens)
|
||||
// 2) Upper in the element injector tree, if the starting element is actually in the view of
|
||||
// the current element. To determine this, we track the transition of view during the climb,
|
||||
// and check the host node of the current view to identify component views.
|
||||
if (previousTView == null && isComponent(tNode) && includeViewProviders ||
|
||||
previousTView != null && previousTView != currentTView &&
|
||||
(currentTView.node == null || currentTView.node !.type === TNodeType.Element)) {
|
||||
canAccessViewProviders = true;
|
||||
}
|
||||
const startInjectables = nodeProviderIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
|
||||
const startDirectives = nodeFlags >> TNodeFlags.DirectiveStartingIndexShift;
|
||||
const cptViewProvidersCount =
|
||||
nodeProviderIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift;
|
||||
const startingIndex =
|
||||
canAccessViewProviders ? startInjectables : startInjectables + cptViewProvidersCount;
|
||||
const directiveCount = nodeFlags & TNodeFlags.DirectiveCountMask;
|
||||
for (let i = startingIndex; i < startDirectives + directiveCount; i++) {
|
||||
const providerTokenOrDef = tInjectables[i] as InjectionToken<any>| Type<any>| DirectiveDef<any>;
|
||||
if (i < startDirectives && token === providerTokenOrDef ||
|
||||
i >= startDirectives && (providerTokenOrDef as DirectiveDef<any>).type === token) {
|
||||
return getNodeInjectable(tInjectables, injectorView, i, tNode as TElementNode);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return NOT_FOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve or instantiate the injectable from the `lData` at particular `index`.
|
||||
*
|
||||
* This function checks to see if the value has already been instantiated and if so returns the
|
||||
* cached `injectable`. Otherwise if it detects that the value is still a factory it
|
||||
* instantiates the `injectable` and caches the value.
|
||||
*/
|
||||
export function getNodeInjectable(
|
||||
tData: TData, lData: LViewData, index: number, tNode: TElementNode): any {
|
||||
let value = lData[index];
|
||||
if (isFactory(value)) {
|
||||
const factory: NodeInjectorFactory = value;
|
||||
if (factory.resolving) {
|
||||
throw new Error(`Circular dep for ${stringify(tData[index])}`);
|
||||
}
|
||||
const previousIncludeViewProviders = setIncludeViewProviders(factory.canSeeViewProviders);
|
||||
factory.resolving = true;
|
||||
let previousInjectImplementation;
|
||||
if (factory.injectImpl) {
|
||||
previousInjectImplementation = setInjectImplementation(factory.injectImpl);
|
||||
}
|
||||
const savePreviousOrParentTNode = getPreviousOrParentTNode();
|
||||
const saveViewData = getViewData();
|
||||
setTNodeAndViewData(tNode, lData);
|
||||
try {
|
||||
value = lData[index] = factory.factory(null, tData, lData, tNode);
|
||||
} finally {
|
||||
if (factory.injectImpl) setInjectImplementation(previousInjectImplementation);
|
||||
setIncludeViewProviders(previousIncludeViewProviders);
|
||||
factory.resolving = false;
|
||||
setTNodeAndViewData(savePreviousOrParentTNode, saveViewData);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -452,7 +470,7 @@ export function bloomHashBitOrFactory(token: Type<any>| InjectionToken<any>): nu
|
||||
return typeof tokenId === 'number' ? tokenId & BLOOM_MASK : tokenId;
|
||||
}
|
||||
|
||||
export function injectorHasToken(
|
||||
export function bloomHasToken(
|
||||
bloomHash: number, injectorIndex: number, injectorView: LViewData | TData) {
|
||||
// Create a mask that targets the specific bit associated with the directive we're looking for.
|
||||
// JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding
|
||||
@ -481,26 +499,14 @@ export function injectorHasToken(
|
||||
}
|
||||
|
||||
/** Returns true if flags prevent parent injector from being searched for tokens */
|
||||
function shouldNotSearchParent(flags: InjectFlags, parentLocation: number): boolean|number {
|
||||
return flags & InjectFlags.Self ||
|
||||
(flags & InjectFlags.Host && (parentLocation >> InjectorLocationFlags.ViewOffsetShift) > 0);
|
||||
function shouldSearchParent(flags: InjectFlags, parentLocation: RelativeInjectorLocation): boolean|
|
||||
number {
|
||||
return !(
|
||||
flags & InjectFlags.Self ||
|
||||
(flags & InjectFlags.Host && getParentInjectorViewOffset(parentLocation) > 0));
|
||||
}
|
||||
|
||||
export class NodeInjector implements Injector {
|
||||
private _injectorIndex: number;
|
||||
|
||||
constructor(
|
||||
private _tNode: TElementNode|TContainerNode|TElementContainerNode,
|
||||
private _hostView: LViewData) {
|
||||
this._injectorIndex = getOrCreateNodeInjectorForNode(_tNode, _hostView);
|
||||
}
|
||||
|
||||
get(token: any): any {
|
||||
setEnvironment(this._tNode, this._hostView);
|
||||
return getOrCreateInjectable(this._tNode, this._hostView, token);
|
||||
}
|
||||
}
|
||||
export function getFactoryOf<T>(type: Type<any>): ((type?: Type<T>) => T)|null {
|
||||
export function getFactoryOf<T>(type: Type<any>): ((type: Type<T>| null) => T)|null {
|
||||
const typeAny = type as any;
|
||||
const def = getComponentDef<T>(typeAny) || getDirectiveDef<T>(typeAny) ||
|
||||
getPipeDef<T>(typeAny) || getInjectableDef<T>(typeAny) || getInjectorDef<T>(typeAny);
|
||||
|
260
packages/core/src/render3/di_setup.ts
Normal file
260
packages/core/src/render3/di_setup.ts
Normal file
@ -0,0 +1,260 @@
|
||||
/*
|
||||
* @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 {resolveForwardRef} from '../di/forward_ref';
|
||||
import {Provider} from '../di/provider';
|
||||
import {isTypeProvider, providerToFactory} from '../di/r3_injector';
|
||||
|
||||
import {DirectiveDef} from '.';
|
||||
import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from './di';
|
||||
import {directiveInject} from './instructions';
|
||||
import {NodeInjectorFactory} from './interfaces/injector';
|
||||
import {TContainerNode, TElementContainerNode, TElementNode, TNodeFlags, TNodeProviderIndexes} from './interfaces/node';
|
||||
import {LViewData, TData, TVIEW, TView} from './interfaces/view';
|
||||
import {getPreviousOrParentTNode, getViewData} from './state';
|
||||
import {isComponentDef} from './util';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Resolves the providers which are defined in the DirectiveDef.
|
||||
*
|
||||
* When inserting the tokens and the factories in their respective arrays, we can assume that
|
||||
* this method is called first for the component (if any), and then for other directives on the same
|
||||
* node.
|
||||
* As a consequence,the providers are always processed in that order:
|
||||
* 1) The view providers of the component
|
||||
* 2) The providers of the component
|
||||
* 3) The providers of the other directives
|
||||
* This matches the structure of the injectables arrays of a view (for each node).
|
||||
* So the tokens and the factories can be pushed at the end of the arrays, except
|
||||
* in one case for multi providers.
|
||||
*
|
||||
* @param def the directive definition
|
||||
* @param providers: Array of `providers`.
|
||||
* @param viewProviders: Array of `viewProviders`.
|
||||
*/
|
||||
export function providersResolver<T>(
|
||||
def: DirectiveDef<T>, providers: Provider[], viewProviders: Provider[]): void {
|
||||
const viewData = getViewData();
|
||||
const tView: TView = viewData[TVIEW];
|
||||
if (tView.firstTemplatePass) {
|
||||
const isComponent = isComponentDef(def);
|
||||
|
||||
// The list of view providers is processed first, and the flags are updated
|
||||
resolveProvider(viewProviders, tView.data, tView.blueprint, isComponent, true);
|
||||
|
||||
// Then, the list of providers is processed, and the flags are updated
|
||||
resolveProvider(providers, tView.data, tView.blueprint, isComponent, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a provider and publishes it to the DI system.
|
||||
*/
|
||||
function resolveProvider(
|
||||
provider: Provider, tInjectables: TData, lInjectablesBlueprint: NodeInjectorFactory[],
|
||||
isComponent: boolean, isViewProvider: boolean): void {
|
||||
provider = resolveForwardRef(provider);
|
||||
if (Array.isArray(provider)) {
|
||||
// Recursively call `resolveProvider`
|
||||
// Recursion is OK in this case because this code will not be in hot-path once we implement
|
||||
// cloning of the initial state.
|
||||
for (let i = 0; i < provider.length; i++) {
|
||||
resolveProvider(
|
||||
provider[i], tInjectables, lInjectablesBlueprint, isComponent, isViewProvider);
|
||||
}
|
||||
} else {
|
||||
const viewData = getViewData();
|
||||
let token: any = isTypeProvider(provider) ? provider : resolveForwardRef(provider.provide);
|
||||
let providerFactory: () => any = providerToFactory(provider);
|
||||
|
||||
const previousOrParentTNode = getPreviousOrParentTNode();
|
||||
const beginIndex =
|
||||
previousOrParentTNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
|
||||
const endIndex = previousOrParentTNode.flags >> TNodeFlags.DirectiveStartingIndexShift;
|
||||
const cptViewProvidersCount =
|
||||
previousOrParentTNode.providerIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift;
|
||||
|
||||
if (isTypeProvider(provider) || !provider.multi) {
|
||||
// Single provider case: the factory is created and pushed immediately
|
||||
const factory = new NodeInjectorFactory(providerFactory, isViewProvider, directiveInject);
|
||||
const existingFactoryIndex = indexOf(
|
||||
token, tInjectables, isViewProvider ? beginIndex : beginIndex + cptViewProvidersCount,
|
||||
endIndex);
|
||||
if (existingFactoryIndex == -1) {
|
||||
diPublicInInjector(
|
||||
getOrCreateNodeInjectorForNode(
|
||||
previousOrParentTNode as TElementNode | TContainerNode | TElementContainerNode,
|
||||
viewData),
|
||||
viewData, token);
|
||||
tInjectables.push(token);
|
||||
previousOrParentTNode.flags += 1 << TNodeFlags.DirectiveStartingIndexShift;
|
||||
if (isViewProvider) {
|
||||
previousOrParentTNode.providerIndexes +=
|
||||
TNodeProviderIndexes.CptViewProvidersCountShifter;
|
||||
}
|
||||
lInjectablesBlueprint.push(factory);
|
||||
viewData.push(factory);
|
||||
} else {
|
||||
lInjectablesBlueprint[existingFactoryIndex] = factory;
|
||||
viewData[existingFactoryIndex] = factory;
|
||||
}
|
||||
} else {
|
||||
// Multi provider case:
|
||||
// We create a multi factory which is going to aggregate all the values.
|
||||
// Since the output of such a factory depends on content or view injection,
|
||||
// we create two of them, which are linked together.
|
||||
//
|
||||
// The first one (for view providers) is always in the first block of the injectables array,
|
||||
// and the second one (for providers) is always in the second block.
|
||||
// This is important because view providers have higher priority. When a multi token
|
||||
// is being looked up, the view providers should be found first.
|
||||
// Note that it is not possible to have a multi factory in the third block (directive block).
|
||||
//
|
||||
// The algorithm to process multi providers is as follows:
|
||||
// 1) If the multi provider comes from the `viewProviders` of the component:
|
||||
// a) If the special view providers factory doesn't exist, it is created and pushed.
|
||||
// b) Else, the multi provider is added to the existing multi factory.
|
||||
// 2) If the multi provider comes from the `providers` of the component or of another
|
||||
// directive:
|
||||
// a) If the multi factory doesn't exist, it is created and provider pushed into it.
|
||||
// It is also linked to the multi factory for view providers, if it exists.
|
||||
// b) Else, the multi provider is added to the existing multi factory.
|
||||
|
||||
const existingProvidersFactoryIndex =
|
||||
indexOf(token, tInjectables, beginIndex + cptViewProvidersCount, endIndex);
|
||||
const existingViewProvidersFactoryIndex =
|
||||
indexOf(token, tInjectables, beginIndex, beginIndex + cptViewProvidersCount);
|
||||
const doesProvidersFactoryExist = existingProvidersFactoryIndex >= 0 &&
|
||||
lInjectablesBlueprint[existingProvidersFactoryIndex];
|
||||
const doesViewProvidersFactoryExist = existingViewProvidersFactoryIndex >= 0 &&
|
||||
lInjectablesBlueprint[existingViewProvidersFactoryIndex];
|
||||
|
||||
if (isViewProvider && !doesViewProvidersFactoryExist ||
|
||||
!isViewProvider && !doesProvidersFactoryExist) {
|
||||
// Cases 1.a and 2.a
|
||||
diPublicInInjector(
|
||||
getOrCreateNodeInjectorForNode(
|
||||
previousOrParentTNode as TElementNode | TContainerNode | TElementContainerNode,
|
||||
viewData),
|
||||
viewData, token);
|
||||
const factory = multiFactory(
|
||||
isViewProvider ? multiViewProvidersFactoryResolver : multiProvidersFactoryResolver,
|
||||
lInjectablesBlueprint.length, isViewProvider, isComponent, providerFactory);
|
||||
if (!isViewProvider && doesViewProvidersFactoryExist) {
|
||||
lInjectablesBlueprint[existingViewProvidersFactoryIndex].providerFactory = factory;
|
||||
}
|
||||
tInjectables.push(token);
|
||||
previousOrParentTNode.flags += 1 << TNodeFlags.DirectiveStartingIndexShift;
|
||||
if (isViewProvider) {
|
||||
previousOrParentTNode.providerIndexes +=
|
||||
TNodeProviderIndexes.CptViewProvidersCountShifter;
|
||||
}
|
||||
lInjectablesBlueprint.push(factory);
|
||||
viewData.push(factory);
|
||||
} else {
|
||||
// Cases 1.b and 2.b
|
||||
multiFactoryAdd(
|
||||
lInjectablesBlueprint ![isViewProvider ? existingViewProvidersFactoryIndex : existingProvidersFactoryIndex],
|
||||
providerFactory, !isViewProvider && isComponent);
|
||||
}
|
||||
if (!isViewProvider && isComponent && doesViewProvidersFactoryExist) {
|
||||
lInjectablesBlueprint[existingViewProvidersFactoryIndex].componentProviders !++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a factory in a multi factory.
|
||||
*/
|
||||
function multiFactoryAdd(
|
||||
multiFactory: NodeInjectorFactory, factory: () => any, isComponentProvider: boolean): void {
|
||||
multiFactory.multi !.push(factory);
|
||||
if (isComponentProvider) {
|
||||
multiFactory.componentProviders !++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of item in the array, but only in the begin to end range.
|
||||
*/
|
||||
function indexOf(item: any, arr: any[], begin: number, end: number) {
|
||||
for (let i = begin; i < end; i++) {
|
||||
if (arr[i] === item) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this with `multi` `providers`.
|
||||
*/
|
||||
function multiProvidersFactoryResolver(
|
||||
this: NodeInjectorFactory, _: null, tData: TData, lData: LViewData,
|
||||
tNode: TElementNode): any[] {
|
||||
return multiResolve(this.multi !, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this with `multi` `viewProviders`.
|
||||
*
|
||||
* This factory knows how to concatenate itself with the existing `multi` `providers`.
|
||||
*/
|
||||
function multiViewProvidersFactoryResolver(
|
||||
this: NodeInjectorFactory, _: null, tData: TData, lData: LViewData,
|
||||
tNode: TElementNode): any[] {
|
||||
const factories = this.multi !;
|
||||
let result: any[];
|
||||
if (this.providerFactory) {
|
||||
const componentCount = this.providerFactory.componentProviders !;
|
||||
const multiProviders = getNodeInjectable(tData, lData, this.providerFactory !.index !, tNode);
|
||||
// Copy the section of the array which contains `multi` `providers` from the component
|
||||
result = multiProviders.slice(0, componentCount);
|
||||
// Insert the `viewProvider` instances.
|
||||
multiResolve(factories, result);
|
||||
// Copy the section of the array which contains `multi` `providers` from other directives
|
||||
for (let i = componentCount; i < multiProviders.length; i++) {
|
||||
result.push(multiProviders[i]);
|
||||
}
|
||||
} else {
|
||||
result = [];
|
||||
// Insert the `viewProvider` instances.
|
||||
multiResolve(factories, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an array of factories into an array of values.
|
||||
*/
|
||||
function multiResolve(factories: Array<() => any>, result: any[]): any[] {
|
||||
for (let i = 0; i < factories.length; i++) {
|
||||
const factory = factories[i] !as() => null;
|
||||
result.push(factory());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a multi factory.
|
||||
*/
|
||||
function multiFactory(
|
||||
factoryFn:
|
||||
(this: NodeInjectorFactory, _: null, tData: TData, lData: LViewData, tNode: TElementNode) =>
|
||||
any,
|
||||
index: number, isViewProvider: boolean, isComponent: boolean,
|
||||
f: () => any): NodeInjectorFactory {
|
||||
const factory = new NodeInjectorFactory(factoryFn, isViewProvider, directiveInject);
|
||||
factory.multi = [];
|
||||
factory.index = index;
|
||||
factory.componentProviders = 0;
|
||||
multiFactoryAdd(factory, f, isComponent && !isViewProvider);
|
||||
return factory;
|
||||
}
|
@ -9,11 +9,12 @@ import {Injector} from '../di/injector';
|
||||
|
||||
import {assertDefined} from './assert';
|
||||
import {discoverDirectives, discoverLocalRefs, getContext, isComponentInstance} from './context_discovery';
|
||||
import {NodeInjector} from './di';
|
||||
import {LContext} from './interfaces/context';
|
||||
import {TElementNode, TNode, TNodeFlags} from './interfaces/node';
|
||||
import {CONTEXT, FLAGS, LViewData, LViewFlags, PARENT, RootContext, TVIEW} from './interfaces/view';
|
||||
import {getComponentViewByIndex, readPatchedLViewData} from './util';
|
||||
import {NodeInjector} from './view_engine_compatibility';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
@ -102,9 +102,9 @@ export function InheritDefinitionFeature(definition: DirectiveDef<any>| Componen
|
||||
const superContentQueries = superDef.contentQueries;
|
||||
if (superContentQueries) {
|
||||
if (prevContentQueries) {
|
||||
definition.contentQueries = () => {
|
||||
superContentQueries();
|
||||
prevContentQueries();
|
||||
definition.contentQueries = (dirIndex: number) => {
|
||||
superContentQueries(dirIndex);
|
||||
prevContentQueries(dirIndex);
|
||||
};
|
||||
} else {
|
||||
definition.contentQueries = superContentQueries;
|
||||
|
45
packages/core/src/render3/features/providers_feature.ts
Normal file
45
packages/core/src/render3/features/providers_feature.ts
Normal file
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @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 {Provider} from '../../di/provider';
|
||||
import {providersResolver} from '../di_setup';
|
||||
import {DirectiveDef} from '../interfaces/definition';
|
||||
|
||||
/**
|
||||
* This feature resolves the providers of a directive (or component),
|
||||
* and publish them into the DI system, making it visible to others for injection.
|
||||
*
|
||||
* For example:
|
||||
* class ComponentWithProviders {
|
||||
* constructor(private greeter: GreeterDE) {}
|
||||
*
|
||||
* static ngComponentDef = defineComponent({
|
||||
* type: ComponentWithProviders,
|
||||
* selectors: [['component-with-providers']],
|
||||
* factory: () => new ComponentWithProviders(directiveInject(GreeterDE as any)),
|
||||
* consts: 1,
|
||||
* vars: 1,
|
||||
* template: function(fs: RenderFlags, ctx: ComponentWithProviders) {
|
||||
* if (fs & RenderFlags.Create) {
|
||||
* text(0);
|
||||
* }
|
||||
* if (fs & RenderFlags.Update) {
|
||||
* textBinding(0, bind(ctx.greeter.greet()));
|
||||
* }
|
||||
* },
|
||||
* features: [ProvidersFeature([GreeterDE])]
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* @param definition
|
||||
*/
|
||||
export function ProvidersFeature<T>(providers: Provider[], viewProviders: Provider[] = []) {
|
||||
return (definition: DirectiveDef<T>) => {
|
||||
definition.providersResolver = (def: DirectiveDef<T>) =>
|
||||
providersResolver(def, providers, viewProviders);
|
||||
};
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
/**
|
||||
* @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 {diPublic} from '../di';
|
||||
import {DirectiveDef} from '../interfaces/definition';
|
||||
|
||||
/**
|
||||
* This feature publishes the directive (or component) into the DI system, making it visible to
|
||||
* others for injection.
|
||||
*
|
||||
* @param definition
|
||||
*/
|
||||
export function PublicFeature<T>(definition: DirectiveDef<T>) {
|
||||
definition.diPublic = diPublic;
|
||||
}
|
@ -9,13 +9,14 @@
|
||||
import {NO_CHANGE} from '../../src/render3/tokens';
|
||||
|
||||
import {assertEqual, assertLessThan} from './assert';
|
||||
import {_getViewData, adjustBlueprintForNewNode, bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, createNodeAtIndex, getRenderer, load, resetComponentState} from './instructions';
|
||||
import {adjustBlueprintForNewNode, bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, createNodeAtIndex, load} from './instructions';
|
||||
import {LContainer, NATIVE, RENDER_PARENT} from './interfaces/container';
|
||||
import {TElementNode, TNode, TNodeType} from './interfaces/node';
|
||||
import {RComment, RElement} from './interfaces/renderer';
|
||||
import {StylingContext} from './interfaces/styling';
|
||||
import {BINDING_INDEX, HEADER_OFFSET, HOST_NODE, TVIEW} from './interfaces/view';
|
||||
import {appendChild, createTextNode, removeChild} from './node_manipulation';
|
||||
import {getRenderer, getViewData, resetComponentState} from './state';
|
||||
import {getNativeByIndex, getNativeByTNode, getTNode, isLContainer, stringify} from './util';
|
||||
|
||||
|
||||
@ -256,7 +257,7 @@ function appendI18nNode(tNode: TNode, parentTNode: TNode, previousTNode: TNode):
|
||||
ngDevMode.rendererMoveNode++;
|
||||
}
|
||||
|
||||
const viewData = _getViewData();
|
||||
const viewData = getViewData();
|
||||
|
||||
// On first pass, re-organize node tree to put this node in the correct position.
|
||||
const firstTemplatePass = viewData[TVIEW].firstTemplatePass;
|
||||
@ -311,7 +312,7 @@ export function i18nEnd(): void {
|
||||
* @param instructions The list of instructions to apply on the current view.
|
||||
*/
|
||||
export function i18nApply(startIndex: number, instructions: I18nInstruction[]): void {
|
||||
const viewData = _getViewData();
|
||||
const viewData = getViewData();
|
||||
if (ngDevMode) {
|
||||
assertEqual(
|
||||
viewData[BINDING_INDEX], viewData[TVIEW].bindingStartIndex,
|
||||
@ -411,7 +412,7 @@ export function i18nExpMapping(
|
||||
* @returns The concatenated string when any of the arguments changes, `NO_CHANGE` otherwise.
|
||||
*/
|
||||
export function i18nInterpolation1(instructions: I18nExpInstruction[], v0: any): string|NO_CHANGE {
|
||||
const different = bindingUpdated(_getViewData()[BINDING_INDEX]++, v0);
|
||||
const different = bindingUpdated(getViewData()[BINDING_INDEX]++, v0);
|
||||
|
||||
if (!different) {
|
||||
return NO_CHANGE;
|
||||
@ -442,7 +443,7 @@ export function i18nInterpolation1(instructions: I18nExpInstruction[], v0: any):
|
||||
*/
|
||||
export function i18nInterpolation2(instructions: I18nExpInstruction[], v0: any, v1: any): string|
|
||||
NO_CHANGE {
|
||||
const viewData = _getViewData();
|
||||
const viewData = getViewData();
|
||||
const different = bindingUpdated2(viewData[BINDING_INDEX], v0, v1);
|
||||
viewData[BINDING_INDEX] += 2;
|
||||
|
||||
@ -482,7 +483,7 @@ export function i18nInterpolation2(instructions: I18nExpInstruction[], v0: any,
|
||||
*/
|
||||
export function i18nInterpolation3(
|
||||
instructions: I18nExpInstruction[], v0: any, v1: any, v2: any): string|NO_CHANGE {
|
||||
const viewData = _getViewData();
|
||||
const viewData = getViewData();
|
||||
const different = bindingUpdated3(viewData[BINDING_INDEX], v0, v1, v2);
|
||||
viewData[BINDING_INDEX] += 3;
|
||||
|
||||
@ -524,7 +525,7 @@ export function i18nInterpolation3(
|
||||
*/
|
||||
export function i18nInterpolation4(
|
||||
instructions: I18nExpInstruction[], v0: any, v1: any, v2: any, v3: any): string|NO_CHANGE {
|
||||
const viewData = _getViewData();
|
||||
const viewData = getViewData();
|
||||
const different = bindingUpdated4(viewData[BINDING_INDEX], v0, v1, v2, v3);
|
||||
viewData[BINDING_INDEX] += 4;
|
||||
|
||||
@ -568,7 +569,7 @@ export function i18nInterpolation4(
|
||||
export function i18nInterpolation5(
|
||||
instructions: I18nExpInstruction[], v0: any, v1: any, v2: any, v3: any, v4: any): string|
|
||||
NO_CHANGE {
|
||||
const viewData = _getViewData();
|
||||
const viewData = getViewData();
|
||||
let different = bindingUpdated4(viewData[BINDING_INDEX], v0, v1, v2, v3);
|
||||
different = bindingUpdated(viewData[BINDING_INDEX] + 4, v4) || different;
|
||||
viewData[BINDING_INDEX] += 5;
|
||||
@ -615,7 +616,7 @@ export function i18nInterpolation5(
|
||||
i18nInterpolation6(
|
||||
instructions: I18nExpInstruction[], v0: any, v1: any, v2: any, v3: any, v4: any, v5: any):
|
||||
string|NO_CHANGE {
|
||||
const viewData = _getViewData();
|
||||
const viewData = getViewData();
|
||||
let different = bindingUpdated4(viewData[BINDING_INDEX], v0, v1, v2, v3);
|
||||
different = bindingUpdated2(viewData[BINDING_INDEX] + 4, v4, v5) || different;
|
||||
viewData[BINDING_INDEX] += 6;
|
||||
@ -663,7 +664,7 @@ i18nInterpolation6(
|
||||
export function i18nInterpolation7(
|
||||
instructions: I18nExpInstruction[], v0: any, v1: any, v2: any, v3: any, v4: any, v5: any,
|
||||
v6: any): string|NO_CHANGE {
|
||||
const viewData = _getViewData();
|
||||
const viewData = getViewData();
|
||||
let different = bindingUpdated4(viewData[BINDING_INDEX], v0, v1, v2, v3);
|
||||
different = bindingUpdated3(viewData[BINDING_INDEX] + 4, v4, v5, v6) || different;
|
||||
viewData[BINDING_INDEX] += 7;
|
||||
@ -712,7 +713,7 @@ export function i18nInterpolation7(
|
||||
export function i18nInterpolation8(
|
||||
instructions: I18nExpInstruction[], v0: any, v1: any, v2: any, v3: any, v4: any, v5: any,
|
||||
v6: any, v7: any): string|NO_CHANGE {
|
||||
const viewData = _getViewData();
|
||||
const viewData = getViewData();
|
||||
let different = bindingUpdated4(viewData[BINDING_INDEX], v0, v1, v2, v3);
|
||||
different = bindingUpdated4(viewData[BINDING_INDEX] + 4, v4, v5, v6, v7) || different;
|
||||
viewData[BINDING_INDEX] += 8;
|
||||
@ -753,7 +754,7 @@ export function i18nInterpolation8(
|
||||
*/
|
||||
export function i18nInterpolationV(instructions: I18nExpInstruction[], values: any[]): string|
|
||||
NO_CHANGE {
|
||||
const viewData = _getViewData();
|
||||
const viewData = getViewData();
|
||||
let different = false;
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
// Check if bindings have changed
|
||||
|
@ -9,11 +9,11 @@ import {LifecycleHooksFeature, getHostElement, getRenderedText, renderComponent,
|
||||
import {defineBase, defineComponent, defineDirective, defineNgModule, definePipe} from './definition';
|
||||
import {InheritDefinitionFeature} from './features/inherit_definition_feature';
|
||||
import {NgOnChangesFeature} from './features/ng_onchanges_feature';
|
||||
import {PublicFeature} from './features/public_feature';
|
||||
import {ProvidersFeature} from './features/providers_feature';
|
||||
import {BaseDef, ComponentDef, ComponentDefWithMeta, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveDefWithMeta, DirectiveType, PipeDef, PipeDefWithMeta} from './interfaces/definition';
|
||||
|
||||
export {ComponentFactory, ComponentFactoryResolver, ComponentRef, WRAP_RENDERER_FACTORY2, injectComponentFactoryResolver} from './component_ref';
|
||||
export {directiveInject, getFactoryOf, getInheritedFactory, injectAttribute} from './di';
|
||||
export {getFactoryOf, getInheritedFactory} from './di';
|
||||
export {RenderFlags} from './interfaces/definition';
|
||||
export {CssSelectorList} from './interfaces/projection';
|
||||
|
||||
@ -51,9 +51,6 @@ export {
|
||||
elementStyleProp,
|
||||
elementStylingApply,
|
||||
|
||||
getCurrentView,
|
||||
restoreView,
|
||||
|
||||
listener,
|
||||
store,
|
||||
load,
|
||||
@ -62,9 +59,6 @@ export {
|
||||
namespaceMathML,
|
||||
namespaceSVG,
|
||||
|
||||
enableBindings,
|
||||
disableBindings,
|
||||
|
||||
projection,
|
||||
projectionDef,
|
||||
|
||||
@ -79,8 +73,19 @@ export {
|
||||
detectChanges,
|
||||
markDirty,
|
||||
tick,
|
||||
|
||||
directiveInject,
|
||||
injectAttribute,
|
||||
} from './instructions';
|
||||
|
||||
export {
|
||||
getCurrentView,
|
||||
restoreView,
|
||||
|
||||
enableBindings,
|
||||
disableBindings,
|
||||
} from './state';
|
||||
|
||||
export {
|
||||
i18nAttribute,
|
||||
i18nExp,
|
||||
@ -157,7 +162,7 @@ export {
|
||||
DirectiveType,
|
||||
NgOnChangesFeature,
|
||||
InheritDefinitionFeature,
|
||||
PublicFeature,
|
||||
ProvidersFeature,
|
||||
PipeDef,
|
||||
PipeDefWithMeta,
|
||||
LifecycleHooksFeature,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Provider, ViewEncapsulation} from '../../core';
|
||||
import {ViewEncapsulation} from '../../core';
|
||||
import {Type} from '../../type';
|
||||
import {CssSelectorList} from './projection';
|
||||
|
||||
@ -112,11 +112,11 @@ export interface DirectiveDef<T> extends BaseDef<T> {
|
||||
/** Token representing the directive. Used by DI. */
|
||||
type: Type<T>;
|
||||
|
||||
/** Function that makes a directive public to the DI system. */
|
||||
diPublic: ((def: DirectiveDef<T>) => void)|null;
|
||||
/** Function that resolves providers and publishes them into the DI system. */
|
||||
providersResolver: ((def: DirectiveDef<T>) => void)|null;
|
||||
|
||||
/** The selectors that will be used to match nodes to this directive. */
|
||||
selectors: CssSelectorList;
|
||||
readonly selectors: CssSelectorList;
|
||||
|
||||
/**
|
||||
* Name under which the directive is exported (for use with local references in template)
|
||||
@ -126,12 +126,12 @@ export interface DirectiveDef<T> extends BaseDef<T> {
|
||||
/**
|
||||
* Factory function used to create a new directive instance.
|
||||
*/
|
||||
factory(): T;
|
||||
factory: (t: Type<T>|null) => T;
|
||||
|
||||
/**
|
||||
* Function to create instances of content queries associated with a given directive.
|
||||
*/
|
||||
contentQueries: (() => void)|null;
|
||||
contentQueries: ((directiveIndex: number) => void)|null;
|
||||
|
||||
/** Refreshes content queries associated with directives in a given view */
|
||||
contentQueriesRefresh: ((directiveIndex: number, queryIndex: number) => void)|null;
|
||||
@ -142,7 +142,7 @@ export interface DirectiveDef<T> extends BaseDef<T> {
|
||||
* Used to calculate the length of the LViewData array for the *parent* component
|
||||
* of this directive/component.
|
||||
*/
|
||||
hostVars: number;
|
||||
readonly hostVars: number;
|
||||
|
||||
/** Refreshes host bindings on the associated directive. */
|
||||
hostBindings: HostBindingsFunction|null;
|
||||
@ -153,7 +153,7 @@ export interface DirectiveDef<T> extends BaseDef<T> {
|
||||
* Even indices: attribute name
|
||||
* Odd indices: attribute value
|
||||
*/
|
||||
attributes: string[]|null;
|
||||
readonly attributes: string[]|null;
|
||||
|
||||
/* The following are lifecycle hooks for this component */
|
||||
onInit: (() => void)|null;
|
||||
@ -167,7 +167,7 @@ export interface DirectiveDef<T> extends BaseDef<T> {
|
||||
/**
|
||||
* The features applied to this directive
|
||||
*/
|
||||
features: DirectiveDefFeature[]|null;
|
||||
readonly features: DirectiveDefFeature[]|null;
|
||||
}
|
||||
|
||||
export type ComponentDefWithMeta<
|
||||
@ -245,18 +245,7 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
|
||||
readonly onPush: boolean;
|
||||
|
||||
/**
|
||||
* Defines the set of injectable providers that are visible to a Directive and its content DOM
|
||||
* children.
|
||||
*/
|
||||
readonly providers: Provider[]|null;
|
||||
|
||||
/**
|
||||
* Defines the set of injectable providers that are visible to a Directive and its view DOM
|
||||
* children only.
|
||||
*/
|
||||
readonly viewProviders: Provider[]|null;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -297,12 +286,12 @@ export interface PipeDef<T> {
|
||||
*
|
||||
* Used to resolve pipe in templates.
|
||||
*/
|
||||
name: string;
|
||||
readonly name: string;
|
||||
|
||||
/**
|
||||
* Factory function used to create a new pipe instance.
|
||||
*/
|
||||
factory: () => T;
|
||||
factory: (t: Type<T>|null) => T;
|
||||
|
||||
/**
|
||||
* Whether or not the pipe is pure.
|
||||
@ -310,7 +299,7 @@ export interface PipeDef<T> {
|
||||
* Pure pipes result only depends on the pipe input and not on internal
|
||||
* state of the pipe.
|
||||
*/
|
||||
pure: boolean;
|
||||
readonly pure: boolean;
|
||||
|
||||
/* The following are lifecycle hooks for this pipe */
|
||||
onDestroy: (() => void)|null;
|
||||
|
@ -6,18 +6,32 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
|
||||
import {TContainerNode, TElementContainerNode, TElementNode,} from './node';
|
||||
import {InjectionToken} from '../../di/injection_token';
|
||||
import {InjectFlags} from '../../di/injector';
|
||||
import {Type} from '../../type';
|
||||
import {TElementNode} from './node';
|
||||
import {LViewData, TData} from './view';
|
||||
|
||||
export const TNODE = 8;
|
||||
export const PARENT_INJECTOR = 8;
|
||||
export const INJECTOR_SIZE = 9;
|
||||
|
||||
export const enum InjectorLocationFlags {
|
||||
/**
|
||||
* Represents a relative location of parent injector.
|
||||
*
|
||||
* The interfaces encodes number of parents `LViewData`s to traverse and index in the `LViewData`
|
||||
* pointing to the parent injector.
|
||||
*/
|
||||
export interface RelativeInjectorLocation { __brand__: 'RelativeInjectorLocationFlags'; }
|
||||
|
||||
export const enum RelativeInjectorLocationFlags {
|
||||
InjectorIndexMask = 0b111111111111111,
|
||||
ViewOffsetShift = 15
|
||||
ViewOffsetShift = 15,
|
||||
NO_PARENT = -1,
|
||||
}
|
||||
|
||||
export const NO_PARENT_INJECTOR: RelativeInjectorLocation = -1 as any;
|
||||
|
||||
/**
|
||||
* Each injector is saved in 9 contiguous slots in `LViewData` and 9 contiguous slots in
|
||||
* `TView.data`. This allows us to store information about the current node's tokens (which
|
||||
@ -98,6 +112,140 @@ export const enum InjectorLocationFlags {
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* Factory for creating instances of injectors in the NodeInjector.
|
||||
*
|
||||
* This factory is complicated by the fact that it can resolve `multi` factories as well.
|
||||
*
|
||||
* NOTE: Some of the fields are optional which means that this class has two hidden classes.
|
||||
* - One without `multi` support (most common)
|
||||
* - One with `multi` values, (rare).
|
||||
*
|
||||
* Since VMs can cache up to 4 inline hidden classes this is OK.
|
||||
*
|
||||
* - Single factory: Only `resolving` and `factory` is defined.
|
||||
* - `providers` factory: `componentProviders` is a number and `index = -1`.
|
||||
* - `viewProviders` factory: `componentProviders` is a number and `index` points to `providers`.
|
||||
*/
|
||||
export class NodeInjectorFactory {
|
||||
/**
|
||||
* The inject implementation to be activated when using the factory.
|
||||
*/
|
||||
injectImpl: null|(<T>(token: Type<T>|InjectionToken<T>, flags: InjectFlags) => T);
|
||||
|
||||
/**
|
||||
* Marker set to true during factory invocation to see if we get into recursive loop.
|
||||
* Recursive loop causes an error to be displayed.
|
||||
*/
|
||||
resolving = false;
|
||||
|
||||
/**
|
||||
* Marks that the token can see other Tokens declared in `viewProviders` on the same node.
|
||||
*/
|
||||
canSeeViewProviders: boolean;
|
||||
|
||||
/**
|
||||
* An array of factories to use in case of `multi` provider.
|
||||
*/
|
||||
multi?: Array<() => any>;
|
||||
|
||||
/**
|
||||
* Number of `multi`-providers which belong to the component.
|
||||
*
|
||||
* This is needed because when multiple components and directives declare the `multi` provider
|
||||
* they have to be concatenated in the correct order.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* If we have a component and directive active an a single element as declared here
|
||||
* ```
|
||||
* component:
|
||||
* provides: [ {provide: String, useValue: 'component', multi: true} ],
|
||||
* viewProvides: [ {provide: String, useValue: 'componentView', multi: true} ],
|
||||
*
|
||||
* directive:
|
||||
* provides: [ {provide: String, useValue: 'directive', multi: true} ],
|
||||
* ```
|
||||
*
|
||||
* Then the expected results are:
|
||||
*
|
||||
* ```
|
||||
* providers: ['component', 'directive']
|
||||
* viewProviders: ['component', 'componentView', 'directive']
|
||||
* ```
|
||||
*
|
||||
* The way to think about it is that the `viewProviders` have been inserted after the component
|
||||
* but before the directives, which is why we need to know how many `multi`s have been declared by
|
||||
* the component.
|
||||
*/
|
||||
componentProviders?: number;
|
||||
|
||||
/**
|
||||
* Current index of the Factory in the `data`. Needed for `viewProviders` and `providers` merging.
|
||||
* See `providerFactory`.
|
||||
*/
|
||||
index?: number;
|
||||
|
||||
/**
|
||||
* Because the same `multi` provider can be declared in `provides` and `viewProvides` it is
|
||||
* possible for `viewProvides` to shadow the `provides`. For this reason we store the
|
||||
* `provideFactory` of the `providers` so that `providers` can be extended with `viewProviders`.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* Given:
|
||||
* ```
|
||||
* provides: [ {provide: String, useValue: 'all', multi: true} ],
|
||||
* viewProvides: [ {provide: String, useValue: 'viewOnly', multi: true} ],
|
||||
* ```
|
||||
*
|
||||
* We have to return `['all']` in case of content injection, but `['all', 'viewOnly']` in case
|
||||
* of view injection. We further have to make sure that the shared instances (in our case
|
||||
* `all`) are the exact same instance in both the content as well as the view injection. (We
|
||||
* have to make sure that we don't double instantiate.) For this reason the `viewProvides`
|
||||
* `Factory` has a pointer to the shadowed `provides` factory so that it can instantiate the
|
||||
* `providers` (`['all']`) and then extend it with `viewProviders` (`['all'] + ['viewOnly'] =
|
||||
* ['all', 'viewOnly']`).
|
||||
*/
|
||||
providerFactory?: NodeInjectorFactory|null;
|
||||
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* Factory to invoke in order to create a new instance.
|
||||
*/
|
||||
public factory:
|
||||
(this: NodeInjectorFactory, _: null,
|
||||
/**
|
||||
* array where injectables tokens are stored. This is used in
|
||||
* case of an error reporting to produce friendlier errors.
|
||||
*/
|
||||
tData: TData,
|
||||
/**
|
||||
* array where existing instances of injectables are stored. This is used in case
|
||||
* of multi shadow is needed. See `multi` field documentation.
|
||||
*/
|
||||
lData: LViewData,
|
||||
/**
|
||||
* The TNode of the same element injector.
|
||||
*/
|
||||
tNode: TElementNode) => any,
|
||||
/**
|
||||
* Set to `true` if the token is declared in `viewProviders` (or if it is component).
|
||||
*/
|
||||
isViewProvider: boolean,
|
||||
injectImplementation: null|(<T>(token: Type<T>|InjectionToken<T>, flags: InjectFlags) => T)) {
|
||||
this.canSeeViewProviders = isViewProvider;
|
||||
this.injectImpl = injectImplementation;
|
||||
}
|
||||
}
|
||||
|
||||
const FactoryPrototype = NodeInjectorFactory.prototype;
|
||||
export function isFactory(obj: any): obj is NodeInjectorFactory {
|
||||
// See: https://jsperf.com/instanceof-vs-getprototypeof
|
||||
return obj != null && typeof obj == 'object' && Object.getPrototypeOf(obj) == FactoryPrototype;
|
||||
}
|
||||
|
||||
// Note: This hack is necessary so we don't erroneously get a circular dependency
|
||||
// failure based on types.
|
||||
export const unusedValueExportToPlacateAjd = 1;
|
||||
|
@ -46,6 +46,18 @@ export const enum TNodeFlags {
|
||||
DirectiveStartingIndexShift = 16,
|
||||
}
|
||||
|
||||
/**
|
||||
* Corresponds to the TNode.providerIndexes property.
|
||||
*/
|
||||
export const enum TNodeProviderIndexes {
|
||||
/** The index of the first provider on this node is encoded on the least significant bits */
|
||||
ProvidersStartIndexMask = 0b00000000000000001111111111111111,
|
||||
|
||||
/** The count of view providers from the component on this node is encoded on the 16 most
|
||||
significant bits */
|
||||
CptViewProvidersCountShift = 16,
|
||||
CptViewProvidersCountShifter = 0b00000000000000010000000000000000,
|
||||
}
|
||||
/**
|
||||
* A set of marker values to be used in the attributes arrays. Those markers indicate that some
|
||||
* items are not regular attributes and the processing should be adapted accordingly.
|
||||
@ -125,6 +137,14 @@ export interface TNode {
|
||||
*/
|
||||
flags: TNodeFlags;
|
||||
|
||||
/**
|
||||
* This number stores two values using its bits:
|
||||
*
|
||||
* - the index of the first provider on that node (first 16 bits)
|
||||
* - the count of view providers from the component on this node (last 16 bits)
|
||||
*/
|
||||
providerIndexes: TNodeProviderIndexes;
|
||||
|
||||
/** The tag name associated with this node. */
|
||||
tagName: string|null;
|
||||
|
||||
|
@ -6,9 +6,11 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {InjectionToken} from '../../di/injection_token';
|
||||
import {Injector} from '../../di/injector';
|
||||
import {QueryList} from '../../linker';
|
||||
import {Sanitizer} from '../../sanitization/security';
|
||||
import {Type} from '../../type';
|
||||
|
||||
import {LContainer} from './container';
|
||||
import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList} from './definition';
|
||||
@ -335,24 +337,6 @@ export interface TView {
|
||||
*/
|
||||
firstChild: TNode|null;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Set of instructions used to process host bindings efficiently.
|
||||
*
|
||||
@ -549,10 +533,9 @@ export type HookData = (number | (() => void))[];
|
||||
*
|
||||
* Injector bloom filters are also stored here.
|
||||
*/
|
||||
export type TData = (TNode | PipeDef<any>| DirectiveDef<any>| ComponentDef<any>| number | null)[];
|
||||
|
||||
/** Type for TView.currentMatches */
|
||||
export type CurrentMatchesList = [DirectiveDef<any>, (string | number | null)];
|
||||
export type TData =
|
||||
(TNode | PipeDef<any>| DirectiveDef<any>| ComponentDef<any>| number | Type<any>|
|
||||
InjectionToken<any>| null)[];
|
||||
|
||||
// Note: This hack is necessary so we don't erroneously get a circular dependency
|
||||
// failure based on types.
|
||||
|
@ -80,6 +80,8 @@ export function compileComponent(type: Type<any>, metadata: Component): void {
|
||||
wrapDirectivesInClosure: false,
|
||||
styles: metadata.styles || [],
|
||||
encapsulation: metadata.encapsulation || ViewEncapsulation.Emulated, animations,
|
||||
viewProviders: metadata.viewProviders ? new WrappedNodeExpr(metadata.viewProviders) :
|
||||
null
|
||||
},
|
||||
constantPool, makeBindingParser());
|
||||
const preStatements = [...constantPool.statements, ...res.statements];
|
||||
@ -180,6 +182,7 @@ function directiveMetadata(type: Type<any>, metadata: Directive): R3DirectiveMet
|
||||
typeSourceSpan: null !,
|
||||
usesInheritance: !extendsDirectlyFromObject(type),
|
||||
exportAs: metadata.exportAs || null,
|
||||
providers: metadata.providers ? new WrappedNodeExpr(metadata.providers) : null
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
|
||||
'ɵinjectAttribute': r3.injectAttribute,
|
||||
'ɵtemplateRefExtractor': r3.templateRefExtractor,
|
||||
'ɵNgOnChangesFeature': r3.NgOnChangesFeature,
|
||||
'ɵPublicFeature': r3.PublicFeature,
|
||||
'ɵProvidersFeature': r3.ProvidersFeature,
|
||||
'ɵInheritDefinitionFeature': r3.InheritDefinitionFeature,
|
||||
'ɵelementAttribute': r3.elementAttribute,
|
||||
'ɵbind': r3.bind,
|
||||
|
@ -8,10 +8,11 @@
|
||||
|
||||
import {PipeTransform} from '../change_detection/pipe_transform';
|
||||
|
||||
import {getTView, load, store} from './instructions';
|
||||
import {load, store} from './instructions';
|
||||
import {PipeDef, PipeDefList} from './interfaces/definition';
|
||||
import {HEADER_OFFSET} from './interfaces/view';
|
||||
import {pureFunction1, pureFunction2, pureFunction3, pureFunction4, pureFunctionV} from './pure_function';
|
||||
import {getTView} from './state';
|
||||
|
||||
/**
|
||||
* Create a pipe.
|
||||
@ -36,7 +37,7 @@ export function pipe(index: number, pipeName: string): any {
|
||||
pipeDef = tView.data[adjustedIndex] as PipeDef<any>;
|
||||
}
|
||||
|
||||
const pipeInstance = pipeDef.factory();
|
||||
const pipeInstance = pipeDef.factory(null);
|
||||
store(index, pipeInstance);
|
||||
return pipeInstance;
|
||||
}
|
||||
|
@ -6,7 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {bindingUpdated, bindingUpdated2, bindingUpdated4, updateBinding, getBinding, getCreationMode, bindingUpdated3, getBindingRoot, getTView,} from './instructions';
|
||||
import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, getBinding, updateBinding} from './instructions';
|
||||
import {getBindingRoot, getCreationMode} from './state';
|
||||
|
||||
|
||||
/**
|
||||
* Bindings for pure functions are stored after regular bindings.
|
||||
|
@ -19,12 +19,13 @@ import {getSymbolIterator} from '../util';
|
||||
|
||||
import {assertDefined, assertEqual} from './assert';
|
||||
import {NG_ELEMENT_ID} from './fields';
|
||||
import {_getViewData, assertPreviousIsParent, getOrCreateCurrentQueries, store, storeCleanupWithContext} from './instructions';
|
||||
import {store, storeCleanupWithContext} from './instructions';
|
||||
import {DirectiveDef, unusedValueExportToPlacateAjd as unused1} from './interfaces/definition';
|
||||
import {unusedValueExportToPlacateAjd as unused2} from './interfaces/injector';
|
||||
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node';
|
||||
import {LQueries, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
|
||||
import {LViewData, TVIEW} from './interfaces/view';
|
||||
import {assertPreviousIsParent, getOrCreateCurrentQueries, getViewData} from './state';
|
||||
import {flatten, isContentQueryHost} from './util';
|
||||
import {createElementRef, createTemplateRef} from './view_engine_compatibility';
|
||||
|
||||
@ -259,7 +260,7 @@ function getIdxOfMatchingDirective(tNode: TNode, currentView: LViewData, type: T
|
||||
const end = start + count;
|
||||
for (let i = start; i < end; i++) {
|
||||
const def = defs[i] as DirectiveDef<any>;
|
||||
if (def.type === type && def.diPublic) {
|
||||
if (def.type === type) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@ -293,7 +294,7 @@ function queryReadByTNodeType(tNode: TNode, currentView: LViewData): any {
|
||||
|
||||
function add(
|
||||
query: LQuery<any>| null, tNode: TElementNode | TContainerNode | TElementContainerNode) {
|
||||
const currentView = _getViewData();
|
||||
const currentView = getViewData();
|
||||
|
||||
while (query) {
|
||||
const predicate = query.predicate;
|
||||
|
432
packages/core/src/render3/state.ts
Normal file
432
packages/core/src/render3/state.ts
Normal file
@ -0,0 +1,432 @@
|
||||
/**
|
||||
* @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 {Sanitizer} from '../sanitization/security';
|
||||
|
||||
import {assertDefined, assertEqual} from './assert';
|
||||
import {executeHooks} from './hooks';
|
||||
import {TElementNode, TNode, TNodeFlags, TViewNode} from './interfaces/node';
|
||||
import {LQueries} from './interfaces/query';
|
||||
import {Renderer3, RendererFactory3} from './interfaces/renderer';
|
||||
import {BINDING_INDEX, CLEANUP, CONTEXT, DECLARATION_VIEW, FLAGS, HOST_NODE, LViewData, LViewFlags, OpaqueViewState, QUERIES, RENDERER, SANITIZER, TVIEW, TView} from './interfaces/view';
|
||||
import {assertDataInRangeInternal, isContentQueryHost} from './util';
|
||||
|
||||
/**
|
||||
* This property gets set before entering a template.
|
||||
*
|
||||
* This renderer can be one of two varieties of Renderer3:
|
||||
*
|
||||
* - ObjectedOrientedRenderer3
|
||||
*
|
||||
* This is the native browser API style, e.g. operations are methods on individual objects
|
||||
* like HTMLElement. With this style, no additional code is needed as a facade (reducing payload
|
||||
* size).
|
||||
*
|
||||
* - ProceduralRenderer3
|
||||
*
|
||||
* In non-native browser environments (e.g. platforms such as web-workers), this is the facade
|
||||
* that enables element manipulation. This also facilitates backwards compatibility with
|
||||
* Renderer2.
|
||||
*/
|
||||
let renderer: Renderer3;
|
||||
|
||||
export function getRenderer(): Renderer3 {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
return renderer;
|
||||
}
|
||||
|
||||
export function setRenderer(r: Renderer3): void {
|
||||
renderer = r;
|
||||
}
|
||||
|
||||
let rendererFactory: RendererFactory3;
|
||||
|
||||
export function getRendererFactory(): RendererFactory3 {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
return rendererFactory;
|
||||
}
|
||||
|
||||
export function setRendererFactory(factory: RendererFactory3): void {
|
||||
rendererFactory = factory;
|
||||
}
|
||||
|
||||
export function getCurrentSanitizer(): Sanitizer|null {
|
||||
return viewData && viewData[SANITIZER];
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the element depth count. This is used to identify the root elements of the template
|
||||
* so that we can than attach `LViewData` to only those elements.
|
||||
*/
|
||||
let elementDepthCount !: number;
|
||||
|
||||
export function getElementDepthCount() {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
return elementDepthCount;
|
||||
}
|
||||
|
||||
export function increaseElementDepthCount() {
|
||||
elementDepthCount++;
|
||||
}
|
||||
|
||||
export function decreaseElementDepthCount() {
|
||||
elementDepthCount--;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores whether directives should be matched to elements.
|
||||
*
|
||||
* When template contains `ngNonBindable` than we need to prevent the runtime form matching
|
||||
* directives on children of that element.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* <my-comp my-directive>
|
||||
* Should match component / directive.
|
||||
* </my-comp>
|
||||
* <div ngNonBindable>
|
||||
* <my-comp my-directive>
|
||||
* Should not match component / directive because we are in ngNonBindable.
|
||||
* </my-comp>
|
||||
* </div>
|
||||
* ```
|
||||
*/
|
||||
let bindingsEnabled !: boolean;
|
||||
|
||||
export function getBindingsEnabled(): boolean {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
return bindingsEnabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enables directive matching on elements.
|
||||
*
|
||||
* * Example:
|
||||
* ```
|
||||
* <my-comp my-directive>
|
||||
* Should match component / directive.
|
||||
* </my-comp>
|
||||
* <div ngNonBindable>
|
||||
* <!-- disabledBindings() -->
|
||||
* <my-comp my-directive>
|
||||
* Should not match component / directive because we are in ngNonBindable.
|
||||
* </my-comp>
|
||||
* <!-- enableBindings() -->
|
||||
* </div>
|
||||
* ```
|
||||
*/
|
||||
export function enableBindings(): void {
|
||||
bindingsEnabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables directive matching on element.
|
||||
*
|
||||
* * Example:
|
||||
* ```
|
||||
* <my-comp my-directive>
|
||||
* Should match component / directive.
|
||||
* </my-comp>
|
||||
* <div ngNonBindable>
|
||||
* <!-- disabledBindings() -->
|
||||
* <my-comp my-directive>
|
||||
* Should not match component / directive because we are in ngNonBindable.
|
||||
* </my-comp>
|
||||
* <!-- enableBindings() -->
|
||||
* </div>
|
||||
* ```
|
||||
*/
|
||||
export function disableBindings(): void {
|
||||
bindingsEnabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current OpaqueViewState instance.
|
||||
*
|
||||
* Used in conjunction with the restoreView() instruction to save a snapshot
|
||||
* of the current view and restore it when listeners are invoked. This allows
|
||||
* walking the declaration view tree in listeners to get vars from parent views.
|
||||
*/
|
||||
export function getCurrentView(): OpaqueViewState {
|
||||
return viewData as any as OpaqueViewState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores `contextViewData` to the given OpaqueViewState instance.
|
||||
*
|
||||
* Used in conjunction with the getCurrentView() instruction to save a snapshot
|
||||
* of the current view and restore it when listeners are invoked. This allows
|
||||
* walking the declaration view tree in listeners to get vars from parent views.
|
||||
*
|
||||
* @param viewToRestore The OpaqueViewState instance to restore.
|
||||
*/
|
||||
export function restoreView(viewToRestore: OpaqueViewState) {
|
||||
contextViewData = viewToRestore as any as LViewData;
|
||||
}
|
||||
|
||||
/** Used to set the parent property when nodes are created and track query results. */
|
||||
let previousOrParentTNode: TNode;
|
||||
|
||||
export function getPreviousOrParentTNode(): TNode {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
return previousOrParentTNode;
|
||||
}
|
||||
|
||||
export function setPreviousOrParentTNode(tNode: TNode) {
|
||||
previousOrParentTNode = tNode;
|
||||
}
|
||||
|
||||
export function setTNodeAndViewData(tNode: TNode, view: LViewData) {
|
||||
previousOrParentTNode = tNode;
|
||||
viewData = view;
|
||||
}
|
||||
|
||||
/**
|
||||
* If `isParent` is:
|
||||
* - `true`: then `previousOrParentTNode` points to a parent node.
|
||||
* - `false`: then `previousOrParentTNode` points to previous node (sibling).
|
||||
*/
|
||||
let isParent: boolean;
|
||||
|
||||
export function getIsParent(): boolean {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
return isParent;
|
||||
}
|
||||
|
||||
export function setIsParent(value: boolean): void {
|
||||
isParent = value;
|
||||
}
|
||||
|
||||
let tView: TView;
|
||||
|
||||
export function getTView(): TView {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
return tView;
|
||||
}
|
||||
|
||||
let currentQueries: LQueries|null;
|
||||
|
||||
export function getCurrentQueries(): LQueries|null {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
return currentQueries;
|
||||
}
|
||||
|
||||
export function setCurrentQueries(queries: LQueries | null): void {
|
||||
currentQueries = queries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query instructions can ask for "current queries" in 2 different cases:
|
||||
* - when creating view queries (at the root of a component view, before any node is created - in
|
||||
* this case currentQueries points to view queries)
|
||||
* - when creating content queries (i.e. this previousOrParentTNode points to a node on which we
|
||||
* create content queries).
|
||||
*/
|
||||
export function getOrCreateCurrentQueries(
|
||||
QueryType: {new (parent: null, shallow: null, deep: null): LQueries}): LQueries {
|
||||
// if this is the first content query on a node, any existing LQueries needs to be cloned
|
||||
// in subsequent template passes, the cloning occurs before directive instantiation.
|
||||
if (previousOrParentTNode && previousOrParentTNode !== viewData[HOST_NODE] &&
|
||||
!isContentQueryHost(previousOrParentTNode)) {
|
||||
currentQueries && (currentQueries = currentQueries.clone());
|
||||
previousOrParentTNode.flags |= TNodeFlags.hasContentQuery;
|
||||
}
|
||||
|
||||
return currentQueries || (currentQueries = new QueryType(null, null, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* This property gets set before entering a template.
|
||||
*/
|
||||
let creationMode: boolean;
|
||||
|
||||
export function getCreationMode(): boolean {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
return creationMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* State of the current view being processed.
|
||||
*
|
||||
* An array of nodes (text, element, container, etc), pipes, their bindings, and
|
||||
* any local variables that need to be stored between invocations.
|
||||
*/
|
||||
let viewData: LViewData;
|
||||
|
||||
/**
|
||||
* Internal function that returns the current LViewData instance.
|
||||
*
|
||||
* The getCurrentView() instruction should be used for anything public.
|
||||
*/
|
||||
export function getViewData(): LViewData {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
return viewData;
|
||||
}
|
||||
|
||||
/**
|
||||
* The last viewData retrieved by nextContext().
|
||||
* Allows building nextContext() and reference() calls.
|
||||
*
|
||||
* e.g. const inner = x().$implicit; const outer = x().$implicit;
|
||||
*/
|
||||
let contextViewData: LViewData = null !;
|
||||
|
||||
export function getContextViewData(): LViewData {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
return contextViewData;
|
||||
}
|
||||
|
||||
export function getCleanup(view: LViewData): any[] {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
return view[CLEANUP] || (view[CLEANUP] = []);
|
||||
}
|
||||
|
||||
export function getTViewCleanup(view: LViewData): any[] {
|
||||
return view[TVIEW].cleanup || (view[TVIEW].cleanup = []);
|
||||
}
|
||||
/**
|
||||
* In this mode, any changes in bindings will throw an ExpressionChangedAfterChecked error.
|
||||
*
|
||||
* Necessary to support ChangeDetectorRef.checkNoChanges().
|
||||
*/
|
||||
let checkNoChangesMode = false;
|
||||
|
||||
export function getCheckNoChangesMode(): boolean {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
return checkNoChangesMode;
|
||||
}
|
||||
|
||||
export function setCheckNoChangesMode(mode: boolean): void {
|
||||
checkNoChangesMode = mode;
|
||||
}
|
||||
|
||||
/** Whether or not this is the first time the current view has been processed. */
|
||||
let firstTemplatePass = true;
|
||||
|
||||
export function getFirstTemplatePass(): boolean {
|
||||
return firstTemplatePass;
|
||||
}
|
||||
|
||||
export function setFirstTemplatePass(value: boolean): void {
|
||||
firstTemplatePass = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The root index from which pure function instructions should calculate their binding
|
||||
* indices. In component views, this is TView.bindingStartIndex. In a host binding
|
||||
* context, this is the TView.expandoStartIndex + any dirs/hostVars before the given dir.
|
||||
*/
|
||||
let bindingRootIndex: number = -1;
|
||||
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
export function getBindingRoot() {
|
||||
return bindingRootIndex;
|
||||
}
|
||||
|
||||
export function setBindingRoot(value: number) {
|
||||
bindingRootIndex = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap the current state with a new state.
|
||||
*
|
||||
* For performance reasons we store the state in the top level of the module.
|
||||
* This way we minimize the number of properties to read. Whenever a new view
|
||||
* is entered we have to store the state for later, and when the view is
|
||||
* exited the state has to be restored
|
||||
*
|
||||
* @param newView New state to become active
|
||||
* @param host Element to which the View is a child of
|
||||
* @returns the previous state;
|
||||
*/
|
||||
export function enterView(
|
||||
newView: LViewData, hostTNode: TElementNode | TViewNode | null): LViewData {
|
||||
const oldView: LViewData = viewData;
|
||||
tView = newView && newView[TVIEW];
|
||||
|
||||
creationMode = newView && (newView[FLAGS] & LViewFlags.CreationMode) === LViewFlags.CreationMode;
|
||||
firstTemplatePass = newView && tView.firstTemplatePass;
|
||||
bindingRootIndex = newView && tView.bindingStartIndex;
|
||||
renderer = newView && newView[RENDERER];
|
||||
|
||||
previousOrParentTNode = hostTNode !;
|
||||
isParent = true;
|
||||
|
||||
viewData = contextViewData = newView;
|
||||
oldView && (oldView[QUERIES] = currentQueries);
|
||||
currentQueries = newView && newView[QUERIES];
|
||||
|
||||
return oldView;
|
||||
}
|
||||
|
||||
export function nextContextImpl<T = any>(level: number = 1): T {
|
||||
contextViewData = walkUpViews(level, contextViewData !);
|
||||
return contextViewData[CONTEXT] as T;
|
||||
}
|
||||
|
||||
function walkUpViews(nestingLevel: number, currentView: LViewData): LViewData {
|
||||
while (nestingLevel > 0) {
|
||||
ngDevMode && assertDefined(
|
||||
currentView[DECLARATION_VIEW],
|
||||
'Declaration view should be defined if nesting level is greater than 0.');
|
||||
currentView = currentView[DECLARATION_VIEW] !;
|
||||
nestingLevel--;
|
||||
}
|
||||
return currentView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the application state.
|
||||
*/
|
||||
export function resetComponentState() {
|
||||
isParent = false;
|
||||
previousOrParentTNode = null !;
|
||||
elementDepthCount = 0;
|
||||
bindingsEnabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in lieu of enterView to make it clear when we are exiting a child view. This makes
|
||||
* the direction of traversal (up or down the view tree) a bit clearer.
|
||||
*
|
||||
* @param newView New state to become active
|
||||
* @param creationOnly An optional boolean to indicate that the view was processed in creation mode
|
||||
* only, i.e. the first update will be done later. Only possible for dynamically created views.
|
||||
*/
|
||||
export function leaveView(newView: LViewData, creationOnly?: boolean): void {
|
||||
if (!creationOnly) {
|
||||
if (!checkNoChangesMode) {
|
||||
executeHooks(viewData, tView.viewHooks, tView.viewCheckHooks, creationMode);
|
||||
}
|
||||
// Views are clean and in update mode after being checked, so these bits are cleared
|
||||
viewData[FLAGS] &= ~(LViewFlags.CreationMode | LViewFlags.Dirty);
|
||||
}
|
||||
viewData[FLAGS] |= LViewFlags.RunInit;
|
||||
viewData[BINDING_INDEX] = tView.bindingStartIndex;
|
||||
enterView(newView, null);
|
||||
}
|
||||
|
||||
export function assertPreviousIsParent() {
|
||||
assertEqual(isParent, true, 'previousOrParentTNode should be a parent');
|
||||
}
|
||||
|
||||
export function assertHasParent() {
|
||||
assertDefined(previousOrParentTNode.parent, 'previousOrParentTNode should have a parent');
|
||||
}
|
||||
|
||||
export function assertDataInRange(index: number, arr?: any[]) {
|
||||
if (arr == null) arr = viewData;
|
||||
assertDataInRangeInternal(index, arr || viewData);
|
||||
}
|
||||
|
||||
export function assertDataNext(index: number, arr?: any[]) {
|
||||
if (arr == null) arr = viewData;
|
||||
assertEqual(
|
||||
arr.length, index, `index ${index} expected to be at the end of arr (length ${arr.length})`);
|
||||
}
|
@ -11,10 +11,13 @@ import {devModeEqual} from '../change_detection/change_detection_util';
|
||||
import {assertDefined, assertLessThan} from './assert';
|
||||
import {ACTIVE_INDEX, LContainer} from './interfaces/container';
|
||||
import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
|
||||
import {TNode, TNodeFlags} from './interfaces/node';
|
||||
import {ComponentDef, DirectiveDef} from './interfaces/definition';
|
||||
import {NO_PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFlags} from './interfaces/injector';
|
||||
import {TContainerNode, TElementNode, TNode, TNodeFlags} from './interfaces/node';
|
||||
import {RComment, RElement, RText} from './interfaces/renderer';
|
||||
import {StylingContext} from './interfaces/styling';
|
||||
import {CONTEXT, FLAGS, HEADER_OFFSET, HOST, LViewData, LViewFlags, PARENT, RootContext, TData, TVIEW} from './interfaces/view';
|
||||
import {CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, LViewData, LViewFlags, PARENT, RootContext, TData, TVIEW} from './interfaces/view';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@ -122,6 +125,10 @@ export function isComponent(tNode: TNode): boolean {
|
||||
return (tNode.flags & TNodeFlags.isComponent) === TNodeFlags.isComponent;
|
||||
}
|
||||
|
||||
export function isComponentDef<T>(def: DirectiveDef<T>): def is ComponentDef<T> {
|
||||
return (def as ComponentDef<T>).template !== null;
|
||||
}
|
||||
|
||||
export function isLContainer(value: RElement | RComment | LContainer | StylingContext): boolean {
|
||||
// Styling contexts are also arrays, but their first index contains an element node
|
||||
return Array.isArray(value) && typeof value[ACTIVE_INDEX] === 'number';
|
||||
@ -161,3 +168,72 @@ export function readPatchedLViewData(target: any): LViewData|null {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function hasParentInjector(parentLocation: RelativeInjectorLocation): boolean {
|
||||
return parentLocation !== NO_PARENT_INJECTOR;
|
||||
}
|
||||
|
||||
export function getParentInjectorIndex(parentLocation: RelativeInjectorLocation): number {
|
||||
return (parentLocation as any as number) & RelativeInjectorLocationFlags.InjectorIndexMask;
|
||||
}
|
||||
|
||||
export function getParentInjectorViewOffset(parentLocation: RelativeInjectorLocation): number {
|
||||
return (parentLocation as any as number) >> RelativeInjectorLocationFlags.ViewOffsetShift;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwraps a parent injector location number to find the view offset from the current injector,
|
||||
* then walks up the declaration view tree until the view is found that contains the parent
|
||||
* injector.
|
||||
*
|
||||
* @param location The location of the parent injector, which contains the view offset
|
||||
* @param startView The LViewData instance from which to start walking up the view tree
|
||||
* @returns The LViewData instance that contains the parent injector
|
||||
*/
|
||||
export function getParentInjectorView(
|
||||
location: RelativeInjectorLocation, startView: LViewData): LViewData {
|
||||
let viewOffset = getParentInjectorViewOffset(location);
|
||||
let parentView = startView;
|
||||
// For most cases, the parent injector can be found on the host node (e.g. for component
|
||||
// or container), but we must keep the loop here to support the rarer case of deeply nested
|
||||
// <ng-template> tags or inline views, where the parent injector might live many views
|
||||
// above the child injector.
|
||||
while (viewOffset > 0) {
|
||||
parentView = parentView[DECLARATION_VIEW] !;
|
||||
viewOffset--;
|
||||
}
|
||||
return parentView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwraps a parent injector location number to find the view offset from the current injector,
|
||||
* then walks up the declaration view tree until the TNode of the parent injector is found.
|
||||
*
|
||||
* @param location The location of the parent injector, which contains the view offset
|
||||
* @param startView The LViewData instance from which to start walking up the view tree
|
||||
* @param startTNode The TNode instance of the starting element
|
||||
* @returns The TNode of the parent injector
|
||||
*/
|
||||
export function getParentInjectorTNode(
|
||||
location: RelativeInjectorLocation, startView: LViewData, startTNode: TNode): TElementNode|
|
||||
TContainerNode|null {
|
||||
if (startTNode.parent && startTNode.parent.injectorIndex !== -1) {
|
||||
// view offset is 0
|
||||
const injectorIndex = startTNode.parent.injectorIndex;
|
||||
let parentTNode = startTNode.parent;
|
||||
while (parentTNode.parent != null && injectorIndex == parentTNode.injectorIndex) {
|
||||
parentTNode = parentTNode.parent;
|
||||
}
|
||||
return parentTNode;
|
||||
}
|
||||
|
||||
let viewOffset = getParentInjectorViewOffset(location);
|
||||
let parentView = startView;
|
||||
let parentTNode = startView[HOST_NODE] as TElementNode;
|
||||
while (viewOffset > 0) {
|
||||
parentView = parentView[DECLARATION_VIEW] !;
|
||||
parentTNode = parentView[HOST_NODE] as TElementNode;
|
||||
viewOffset--;
|
||||
}
|
||||
return parentTNode;
|
||||
}
|
||||
|
@ -17,18 +17,18 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_Vie
|
||||
import {Renderer2} from '../render/api';
|
||||
|
||||
import {assertDefined, assertGreaterThan, assertLessThan} from './assert';
|
||||
import {NodeInjector, getParentInjectorLocation, getParentInjectorView} from './di';
|
||||
import {_getViewData, addToViewTree, createEmbeddedViewAndNode, createLContainer, getPreviousOrParentTNode, getRenderer, renderEmbeddedTemplate} from './instructions';
|
||||
import {ACTIVE_INDEX, LContainer, NATIVE, RENDER_PARENT, VIEWS} from './interfaces/container';
|
||||
import {getOrCreateInjectable, getParentInjectorLocation} from './di';
|
||||
import {addToViewTree, createEmbeddedViewAndNode, createLContainer, renderEmbeddedTemplate} from './instructions';
|
||||
import {ACTIVE_INDEX, LContainer, NATIVE, VIEWS} from './interfaces/container';
|
||||
import {RenderFlags} from './interfaces/definition';
|
||||
import {InjectorLocationFlags} from './interfaces/injector';
|
||||
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TViewNode} from './interfaces/node';
|
||||
import {LQueries} from './interfaces/query';
|
||||
import {RComment, RElement, Renderer3, isProceduralRenderer} from './interfaces/renderer';
|
||||
import {CONTEXT, HOST_NODE, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view';
|
||||
import {CONTEXT, HOST_NODE, LViewData, QUERIES, RENDERER, TView} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||
import {addRemoveViewFromContainer, appendChild, detachView, findComponentView, getBeforeNodeForView, getRenderParent, insertView, removeView} from './node_manipulation';
|
||||
import {getComponentViewByIndex, getNativeByTNode, isComponent, isLContainer} from './util';
|
||||
import {getPreviousOrParentTNode, getRenderer, getViewData} from './state';
|
||||
import {getComponentViewByIndex, getNativeByTNode, getParentInjectorTNode, getParentInjectorView, hasParentInjector, isComponent, isLContainer} from './util';
|
||||
import {ViewRef} from './view_ref';
|
||||
|
||||
|
||||
@ -40,7 +40,7 @@ import {ViewRef} from './view_ref';
|
||||
*/
|
||||
export function injectElementRef(ElementRefToken: typeof ViewEngine_ElementRef):
|
||||
ViewEngine_ElementRef {
|
||||
return createElementRef(ElementRefToken, getPreviousOrParentTNode(), _getViewData());
|
||||
return createElementRef(ElementRefToken, getPreviousOrParentTNode(), getViewData());
|
||||
}
|
||||
|
||||
let R3ElementRef: {new (native: RElement | RComment): ViewEngine_ElementRef};
|
||||
@ -79,7 +79,7 @@ export function injectTemplateRef<T>(
|
||||
TemplateRefToken: typeof ViewEngine_TemplateRef,
|
||||
ElementRefToken: typeof ViewEngine_ElementRef): ViewEngine_TemplateRef<T> {
|
||||
return createTemplateRef<T>(
|
||||
TemplateRefToken, ElementRefToken, getPreviousOrParentTNode(), _getViewData());
|
||||
TemplateRefToken, ElementRefToken, getPreviousOrParentTNode(), getViewData());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -147,9 +147,18 @@ export function injectViewContainerRef(
|
||||
ElementRefToken: typeof ViewEngine_ElementRef): ViewEngine_ViewContainerRef {
|
||||
const previousTNode =
|
||||
getPreviousOrParentTNode() as TElementNode | TElementContainerNode | TContainerNode;
|
||||
return createContainerRef(ViewContainerRefToken, ElementRefToken, previousTNode, _getViewData());
|
||||
return createContainerRef(ViewContainerRefToken, ElementRefToken, previousTNode, getViewData());
|
||||
}
|
||||
|
||||
export class NodeInjector implements Injector {
|
||||
constructor(
|
||||
private _tNode: TElementNode|TContainerNode|TElementContainerNode,
|
||||
private _hostView: LViewData) {}
|
||||
|
||||
get(token: any, notFoundValue?: any): any {
|
||||
return getOrCreateInjectable(this._tNode, this._hostView, token, notFoundValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ViewContainerRef and stores it on the injector.
|
||||
@ -187,11 +196,11 @@ export function createContainerRef(
|
||||
get parentInjector(): Injector {
|
||||
const parentLocation = getParentInjectorLocation(this._hostTNode, this._hostView);
|
||||
const parentView = getParentInjectorView(parentLocation, this._hostView);
|
||||
const parentIndex = parentLocation & InjectorLocationFlags.InjectorIndexMask;
|
||||
const parentTNode = parentView[TVIEW].data[parentIndex] as TElementNode | TContainerNode;
|
||||
const parentTNode = getParentInjectorTNode(parentLocation, this._hostView, this._hostTNode);
|
||||
|
||||
return parentLocation === -1 ? new NullInjector() :
|
||||
new NodeInjector(parentTNode, parentView);
|
||||
return !hasParentInjector(parentLocation) || parentTNode == null ?
|
||||
new NullInjector() :
|
||||
new NodeInjector(parentTNode, parentView);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
@ -310,7 +319,7 @@ export function createContainerRef(
|
||||
|
||||
/** Returns a ChangeDetectorRef (a.k.a. a ViewRef) */
|
||||
export function injectChangeDetectorRef(): ViewEngine_ChangeDetectorRef {
|
||||
return createViewRef(getPreviousOrParentTNode(), _getViewData(), null);
|
||||
return createViewRef(getPreviousOrParentTNode(), getViewData(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -345,5 +354,5 @@ function getOrCreateRenderer2(view: LViewData): Renderer2 {
|
||||
|
||||
/** Returns a Renderer2 (or throws when application was bootstrapped with Renderer3) */
|
||||
export function injectRenderer2(): Renderer2 {
|
||||
return getOrCreateRenderer2(_getViewData());
|
||||
return getOrCreateRenderer2(getViewData());
|
||||
}
|
||||
|
@ -11,10 +11,11 @@ import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detec
|
||||
import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_container_ref';
|
||||
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEngine_InternalViewRef} from '../linker/view_ref';
|
||||
|
||||
import {checkNoChanges, checkNoChangesInRootView, detectChanges, detectChangesInRootView, getRendererFactory, markViewDirty, storeCleanupFn, viewAttached} from './instructions';
|
||||
import {checkNoChanges, checkNoChangesInRootView, detectChanges, detectChangesInRootView, markViewDirty, storeCleanupFn, viewAttached} from './instructions';
|
||||
import {TViewNode} from './interfaces/node';
|
||||
import {FLAGS, LViewData, LViewFlags, PARENT} from './interfaces/view';
|
||||
import {destroyLView} from './node_manipulation';
|
||||
import {getRendererFactory} from './state';
|
||||
|
||||
|
||||
// Needed due to tsickle downleveling where multiple `implements` with classes creates
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {getCurrentSanitizer} from '../render3/instructions';
|
||||
import {getCurrentSanitizer} from '../render3/state';
|
||||
import {stringify} from '../render3/util';
|
||||
|
||||
import {BypassType, allowSanitizationBypass} from './bypass';
|
||||
|
Reference in New Issue
Block a user