feat(ivy): support providers and viewProviders (#25803)

PR Close #25803
This commit is contained in:
Marc Laval
2018-10-18 09:23:18 +02:00
committed by Matias Niemelä
parent 9dc52d9d04
commit b0476f308b
76 changed files with 4098 additions and 1645 deletions

View File

@ -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,

View File

@ -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;
}

View File

@ -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++) {

View File

@ -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';
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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);

View 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;
}

View File

@ -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';
/**

View File

@ -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;

View 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);
};
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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.

View File

@ -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
};
}

View File

@ -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,

View File

@ -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;
}

View File

@ -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.

View File

@ -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;

View 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})`);
}

View File

@ -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;
}

View File

@ -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());
}

View File

@ -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

View File

@ -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';