feat: introduce `TestBed.overrideProvider` (#16725)

This allows to overwrite all providers for a token, not matter
where they were defined.

This can be used to test JIT and AOT’ed code in the same way.

Design doc: https://docs.google.com/document/d/1VmTkz0EbEVSWfEEWEvQ5sXyQXSCvtMOw4t7pKU-jOwc/edit?usp=sharing
This commit is contained in:
Tobias Bosch 2017-05-15 13:12:10 -07:00 committed by Jason Aden
parent 1eba623d12
commit 39b92f7e54
20 changed files with 735 additions and 121 deletions

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {APP_INITIALIZER, ApplicationInitStatus} from './application_init'; import {ApplicationInitStatus} from './application_init';
import {ApplicationRef, ApplicationRef_} from './application_ref'; import {ApplicationRef, ApplicationRef_} from './application_ref';
import {APP_ID_RANDOM_PROVIDER} from './application_tokens'; import {APP_ID_RANDOM_PROVIDER} from './application_tokens';
import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection'; import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection';
@ -14,7 +14,6 @@ import {Inject, Optional, SkipSelf} from './di/metadata';
import {LOCALE_ID} from './i18n/tokens'; import {LOCALE_ID} from './i18n/tokens';
import {Compiler} from './linker/compiler'; import {Compiler} from './linker/compiler';
import {NgModule} from './metadata'; import {NgModule} from './metadata';
import {initServicesIfNeeded} from './view/index';
export function _iterableDiffersFactory() { export function _iterableDiffersFactory() {
return defaultIterableDiffers; return defaultIterableDiffers;
@ -28,10 +27,6 @@ export function _localeFactory(locale?: string): string {
return locale || 'en-US'; return locale || 'en-US';
} }
export function _initViewEngine() {
initServicesIfNeeded();
}
/** /**
* This module includes the providers of @angular/core that are needed * This module includes the providers of @angular/core that are needed
* to bootstrap components via `ApplicationRef`. * to bootstrap components via `ApplicationRef`.
@ -52,7 +47,6 @@ export function _initViewEngine() {
useFactory: _localeFactory, useFactory: _localeFactory,
deps: [[new Inject(LOCALE_ID), new Optional(), new SkipSelf()]] deps: [[new Inject(LOCALE_ID), new Optional(), new SkipSelf()]]
}, },
{provide: APP_INITIALIZER, useValue: _initViewEngine, multi: true},
] ]
}) })
export class ApplicationModule { export class ApplicationModule {

View File

@ -26,4 +26,5 @@ export {DirectRenderer as ɵDirectRenderer, RenderDebugInfo as ɵRenderDebugInfo
export {global as ɵglobal, looseIdentical as ɵlooseIdentical, stringify as ɵstringify} from './util'; export {global as ɵglobal, looseIdentical as ɵlooseIdentical, stringify as ɵstringify} from './util';
export {makeDecorator as ɵmakeDecorator} from './util/decorators'; export {makeDecorator as ɵmakeDecorator} from './util/decorators';
export {isObservable as ɵisObservable, isPromise as ɵisPromise} from './util/lang'; export {isObservable as ɵisObservable, isPromise as ɵisPromise} from './util/lang';
export {clearProviderOverrides as ɵclearProviderOverrides, overrideProvider as ɵoverrideProvider} from './view/index';
export {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as ɵNOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from './view/provider'; export {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as ɵNOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from './view/provider';

View File

@ -0,0 +1,50 @@
/**
* @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 {Injector} from '../di/injector';
import {NgModuleFactory, NgModuleRef} from '../linker/ng_module_factory';
import {Type} from '../type';
import {initServicesIfNeeded} from './services';
import {NgModuleDefinitionFactory, ProviderOverride, Services} from './types';
import {resolveDefinition} from './util';
export function overrideProvider(override: ProviderOverride) {
initServicesIfNeeded();
return Services.overrideProvider(override);
}
export function clearProviderOverrides() {
initServicesIfNeeded();
return Services.clearProviderOverrides();
}
// Attention: this function is called as top level function.
// Putting any logic in here will destroy closure tree shaking!
export function createNgModuleFactory(
ngModuleType: Type<any>, bootstrapComponents: Type<any>[],
defFactory: NgModuleDefinitionFactory): NgModuleFactory<any> {
return new NgModuleFactory_(ngModuleType, bootstrapComponents, defFactory);
}
class NgModuleFactory_ extends NgModuleFactory<any> {
constructor(
public readonly moduleType: Type<any>, private _bootstrapComponents: Type<any>[],
private _ngModuleDefFactory: NgModuleDefinitionFactory) {
// Attention: this ctor is called as top level function.
// Putting any logic in here will destroy closure tree shaking!
super();
}
create(parentInjector: Injector|null): NgModuleRef<any> {
initServicesIfNeeded();
const def = resolveDefinition(this._ngModuleDefFactory);
return Services.createNgModuleRef(
this.moduleType, parentInjector || Injector.NULL, this._bootstrapComponents, def);
}
}

View File

@ -7,13 +7,13 @@
*/ */
export {anchorDef, elementDef} from './element'; export {anchorDef, elementDef} from './element';
export {clearProviderOverrides, createNgModuleFactory, overrideProvider} from './entrypoint';
export {ngContentDef} from './ng_content'; export {ngContentDef} from './ng_content';
export {moduleDef, moduleProvideDef} from './ng_module'; export {moduleDef, moduleProvideDef} from './ng_module';
export {directiveDef, pipeDef, providerDef} from './provider'; export {directiveDef, pipeDef, providerDef} from './provider';
export {pureArrayDef, pureObjectDef, purePipeDef} from './pure_expression'; export {pureArrayDef, pureObjectDef, purePipeDef} from './pure_expression';
export {queryDef} from './query'; export {queryDef} from './query';
export {ViewRef_, createComponentFactory, getComponentViewDefinitionFactory, nodeValue} from './refs'; export {ViewRef_, createComponentFactory, getComponentViewDefinitionFactory, nodeValue} from './refs';
export {createNgModuleFactory} from './refs';
export {initServicesIfNeeded} from './services'; export {initServicesIfNeeded} from './services';
export {textDef} from './text'; export {textDef} from './text';
export {EMPTY_ARRAY, EMPTY_MAP, createRendererType2, elementEventFullName, inlineInterpolate, interpolate, rootRenderNodes, tokenKey, unwrapValue} from './util'; export {EMPTY_ARRAY, EMPTY_MAP, createRendererType2, elementEventFullName, inlineInterpolate, interpolate, rootRenderNodes, tokenKey, unwrapValue} from './util';

View File

@ -10,7 +10,7 @@ import {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
import {NgModuleRef} from '../linker/ng_module_factory'; import {NgModuleRef} from '../linker/ng_module_factory';
import {DepDef, DepFlags, NgModuleData, NgModuleDefinition, NgModuleDefinitionFactory, NgModuleProviderDef, NodeFlags} from './types'; import {DepDef, DepFlags, NgModuleData, NgModuleDefinition, NgModuleDefinitionFactory, NgModuleProviderDef, NodeFlags} from './types';
import {tokenKey} from './util'; import {splitDepsDsl, tokenKey} from './util';
const NOT_CREATED = new Object(); const NOT_CREATED = new Object();
@ -20,17 +20,7 @@ const NgModuleRefTokenKey = tokenKey(NgModuleRef);
export function moduleProvideDef( export function moduleProvideDef(
flags: NodeFlags, token: any, value: any, flags: NodeFlags, token: any, value: any,
deps: ([DepFlags, any] | any)[]): NgModuleProviderDef { deps: ([DepFlags, any] | any)[]): NgModuleProviderDef {
const depDefs: DepDef[] = deps.map(value => { const depDefs = splitDepsDsl(deps);
let token: any;
let flags: DepFlags;
if (Array.isArray(value)) {
[flags, token] = value;
} else {
flags = DepFlags.None;
token = value;
}
return {flags, token, tokenKey: tokenKey(token)};
});
return { return {
// will bet set by the module definition // will bet set by the module definition
index: -1, index: -1,
@ -97,16 +87,16 @@ function _createProviderInstance(ngModule: NgModuleData, providerDef: NgModulePr
let injectable: any; let injectable: any;
switch (providerDef.flags & NodeFlags.Types) { switch (providerDef.flags & NodeFlags.Types) {
case NodeFlags.TypeClassProvider: case NodeFlags.TypeClassProvider:
injectable = _createClass(ngModule, providerDef !.value, providerDef !.deps); injectable = _createClass(ngModule, providerDef.value, providerDef.deps);
break; break;
case NodeFlags.TypeFactoryProvider: case NodeFlags.TypeFactoryProvider:
injectable = _callFactory(ngModule, providerDef !.value, providerDef !.deps); injectable = _callFactory(ngModule, providerDef.value, providerDef.deps);
break; break;
case NodeFlags.TypeUseExistingProvider: case NodeFlags.TypeUseExistingProvider:
injectable = resolveNgModuleDep(ngModule, providerDef !.deps[0]); injectable = resolveNgModuleDep(ngModule, providerDef.deps[0]);
break; break;
case NodeFlags.TypeValueProvider: case NodeFlags.TypeValueProvider:
injectable = providerDef !.value; injectable = providerDef.value;
break; break;
} }
return injectable; return injectable;

View File

@ -15,7 +15,7 @@ import {Renderer as RendererV1, Renderer2} from '../render/api';
import {createChangeDetectorRef, createInjector, createRendererV1} from './refs'; import {createChangeDetectorRef, createInjector, createRendererV1} from './refs';
import {BindingDef, BindingFlags, DepDef, DepFlags, NodeDef, NodeFlags, OutputDef, OutputType, ProviderData, QueryValueType, Services, ViewData, ViewFlags, ViewState, asElementData, asProviderData} from './types'; import {BindingDef, BindingFlags, DepDef, DepFlags, NodeDef, NodeFlags, OutputDef, OutputType, ProviderData, QueryValueType, Services, ViewData, ViewFlags, ViewState, asElementData, asProviderData} from './types';
import {calcBindingFlags, checkBinding, dispatchEvent, isComponentView, splitMatchedQueriesDsl, tokenKey, viewParentEl} from './util'; import {calcBindingFlags, checkBinding, dispatchEvent, isComponentView, splitDepsDsl, splitMatchedQueriesDsl, tokenKey, viewParentEl} from './util';
const RendererV1TokenKey = tokenKey(RendererV1); const RendererV1TokenKey = tokenKey(RendererV1);
const Renderer2TokenKey = tokenKey(Renderer2); const Renderer2TokenKey = tokenKey(Renderer2);
@ -78,17 +78,7 @@ export function _def(
bindings = []; bindings = [];
} }
const depDefs: DepDef[] = deps.map(value => { const depDefs = splitDepsDsl(deps);
let token: any;
let flags: DepFlags;
if (Array.isArray(value)) {
[flags, token] = value;
} else {
flags = DepFlags.None;
token = value;
}
return {flags, token, tokenKey: tokenKey(token)};
});
return { return {
// will bet set by the view definition // will bet set by the view definition

View File

@ -12,7 +12,7 @@ import {Injector} from '../di/injector';
import {ComponentFactory, ComponentRef} from '../linker/component_factory'; import {ComponentFactory, ComponentRef} from '../linker/component_factory';
import {ComponentFactoryBoundToModule, ComponentFactoryResolver} from '../linker/component_factory_resolver'; import {ComponentFactoryBoundToModule, ComponentFactoryResolver} from '../linker/component_factory_resolver';
import {ElementRef} from '../linker/element_ref'; import {ElementRef} from '../linker/element_ref';
import {InternalNgModuleRef, NgModuleFactory, NgModuleRef} from '../linker/ng_module_factory'; import {InternalNgModuleRef, NgModuleRef} from '../linker/ng_module_factory';
import {TemplateRef} from '../linker/template_ref'; import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref'; import {ViewContainerRef} from '../linker/view_container_ref';
import {EmbeddedViewRef, InternalViewRef, ViewRef} from '../linker/view_ref'; import {EmbeddedViewRef, InternalViewRef, ViewRef} from '../linker/view_ref';
@ -22,7 +22,7 @@ import {stringify} from '../util';
import {VERSION} from '../version'; import {VERSION} from '../version';
import {callNgModuleLifecycle, initNgModule, resolveNgModuleDep} from './ng_module'; import {callNgModuleLifecycle, initNgModule, resolveNgModuleDep} from './ng_module';
import {DepFlags, ElementData, NgModuleData, NgModuleDefinition, NgModuleDefinitionFactory, NodeDef, NodeFlags, Services, TemplateData, ViewContainerData, ViewData, ViewDefinitionFactory, ViewState, asElementData, asProviderData, asTextData} from './types'; import {DepFlags, ElementData, NgModuleData, NgModuleDefinition, NodeDef, NodeFlags, Services, TemplateData, ViewContainerData, ViewData, ViewDefinitionFactory, ViewState, asElementData, asProviderData, asTextData} from './types';
import {markParentViewsForCheck, resolveDefinition, rootRenderNodes, splitNamespace, tokenKey, viewParentEl} from './util'; import {markParentViewsForCheck, resolveDefinition, rootRenderNodes, splitNamespace, tokenKey, viewParentEl} from './util';
import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView, renderDetachView} from './view_attach'; import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView, renderDetachView} from './view_attach';
@ -43,14 +43,6 @@ export function getComponentViewDefinitionFactory(componentFactory: ComponentFac
return (componentFactory as ComponentFactory_).viewDefFactory; return (componentFactory as ComponentFactory_).viewDefFactory;
} }
// Attention: this function is called as top level function.
// Putting any logic in here will destroy closure tree shaking!
export function createNgModuleFactory(
ngModuleType: Type<any>, bootstrapComponents: Type<any>[],
defFactory: NgModuleDefinitionFactory): NgModuleFactory<any> {
return new NgModuleFactory_(ngModuleType, bootstrapComponents, defFactory);
}
class ComponentFactory_ extends ComponentFactory<any> { class ComponentFactory_ extends ComponentFactory<any> {
/** /**
* @internal * @internal
@ -312,7 +304,8 @@ class TemplateRef_ extends TemplateRef<any> implements TemplateData {
constructor(private _parentView: ViewData, private _def: NodeDef) { super(); } constructor(private _parentView: ViewData, private _def: NodeDef) { super(); }
createEmbeddedView(context: any): EmbeddedViewRef<any> { createEmbeddedView(context: any): EmbeddedViewRef<any> {
return new ViewRef_(Services.createEmbeddedView(this._parentView, this._def, context)); return new ViewRef_(Services.createEmbeddedView(
this._parentView, this._def, this._def.element !.template !, context));
} }
get elementRef(): ElementRef { get elementRef(): ElementRef {
@ -464,22 +457,10 @@ class RendererAdapter implements RendererV1 {
} }
class NgModuleFactory_ extends NgModuleFactory<any> { export function createNgModuleRef(
constructor( moduleType: Type<any>, parent: Injector, bootstrapComponents: Type<any>[],
private _moduleType: Type<any>, private _bootstrapComponents: Type<any>[], def: NgModuleDefinition): NgModuleRef<any> {
private _ngModuleDefFactory: NgModuleDefinitionFactory, ) { return new NgModuleRef_(moduleType, parent, bootstrapComponents, def);
// Attention: this ctor is called as top level function.
// Putting any logic in here will destroy closure tree shaking!
super();
}
get moduleType(): Type<any> { return this._moduleType; }
create(parentInjector: Injector|null): NgModuleRef<any> {
const def = resolveDefinition(this._ngModuleDefFactory);
return new NgModuleRef_(
this._moduleType, parentInjector || Injector.NULL, this._bootstrapComponents, def);
}
} }
class NgModuleRef_ implements NgModuleData, InternalNgModuleRef<any> { class NgModuleRef_ implements NgModuleData, InternalNgModuleRef<any> {
@ -488,8 +469,8 @@ class NgModuleRef_ implements NgModuleData, InternalNgModuleRef<any> {
public _providers: any[]; public _providers: any[];
constructor( constructor(
private _moduleType: any, public _parent: Injector, public _bootstrapComponents: Type<any>[], private _moduleType: Type<any>, public _parent: Injector,
public _def: NgModuleDefinition) { public _bootstrapComponents: Type<any>[], public _def: NgModuleDefinition) {
initNgModule(this); initNgModule(this);
} }

View File

@ -13,14 +13,15 @@ import {ErrorHandler} from '../error_handler';
import {NgModuleRef} from '../linker/ng_module_factory'; import {NgModuleRef} from '../linker/ng_module_factory';
import {Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '../render/api'; import {Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '../render/api';
import {Sanitizer} from '../security'; import {Sanitizer} from '../security';
import {Type} from '../type';
import {isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors'; import {isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors';
import {resolveDep} from './provider'; import {resolveDep} from './provider';
import {dirtyParentQueries, getQueryValue} from './query'; import {dirtyParentQueries, getQueryValue} from './query';
import {createInjector} from './refs'; import {createInjector, createNgModuleRef} from './refs';
import {ArgumentType, BindingFlags, CheckType, DebugContext, ElementData, NodeDef, NodeFlags, NodeLogger, RootData, Services, ViewData, ViewDefinition, ViewState, asElementData, asPureExpressionData} from './types'; import {ArgumentType, BindingFlags, CheckType, DebugContext, DepDef, ElementData, NgModuleDefinition, NgModuleProviderDef, NodeDef, NodeFlags, NodeLogger, ProviderOverride, RootData, Services, ViewData, ViewDefinition, ViewState, asElementData, asPureExpressionData} from './types';
import {NOOP, isComponentView, renderNode, viewParentEl} from './util'; import {NOOP, isComponentView, renderNode, splitDepsDsl, viewParentEl} from './util';
import {checkAndUpdateNode, checkAndUpdateView, checkNoChangesNode, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view'; import {checkAndUpdateNode, checkAndUpdateView, checkNoChangesNode, checkNoChangesView, createComponentView, createEmbeddedView, createRootView, destroyView} from './view';
let initialized = false; let initialized = false;
@ -34,6 +35,10 @@ export function initServicesIfNeeded() {
Services.setCurrentNode = services.setCurrentNode; Services.setCurrentNode = services.setCurrentNode;
Services.createRootView = services.createRootView; Services.createRootView = services.createRootView;
Services.createEmbeddedView = services.createEmbeddedView; Services.createEmbeddedView = services.createEmbeddedView;
Services.createComponentView = services.createComponentView;
Services.createNgModuleRef = services.createNgModuleRef;
Services.overrideProvider = services.overrideProvider;
Services.clearProviderOverrides = services.clearProviderOverrides;
Services.checkAndUpdateView = services.checkAndUpdateView; Services.checkAndUpdateView = services.checkAndUpdateView;
Services.checkNoChangesView = services.checkNoChangesView; Services.checkNoChangesView = services.checkNoChangesView;
Services.destroyView = services.destroyView; Services.destroyView = services.destroyView;
@ -50,6 +55,10 @@ function createProdServices() {
setCurrentNode: () => {}, setCurrentNode: () => {},
createRootView: createProdRootView, createRootView: createProdRootView,
createEmbeddedView: createEmbeddedView, createEmbeddedView: createEmbeddedView,
createComponentView: createComponentView,
createNgModuleRef: createNgModuleRef,
overrideProvider: NOOP,
clearProviderOverrides: NOOP,
checkAndUpdateView: checkAndUpdateView, checkAndUpdateView: checkAndUpdateView,
checkNoChangesView: checkNoChangesView, checkNoChangesView: checkNoChangesView,
destroyView: destroyView, destroyView: destroyView,
@ -72,13 +81,17 @@ function createDebugServices() {
setCurrentNode: debugSetCurrentNode, setCurrentNode: debugSetCurrentNode,
createRootView: debugCreateRootView, createRootView: debugCreateRootView,
createEmbeddedView: debugCreateEmbeddedView, createEmbeddedView: debugCreateEmbeddedView,
createComponentView: debugCreateComponentView,
createNgModuleRef: debugCreateNgModuleRef,
overrideProvider: debugOverrideProvider,
clearProviderOverrides: debugClearProviderOverrides,
checkAndUpdateView: debugCheckAndUpdateView, checkAndUpdateView: debugCheckAndUpdateView,
checkNoChangesView: debugCheckNoChangesView, checkNoChangesView: debugCheckNoChangesView,
destroyView: debugDestroyView, destroyView: debugDestroyView,
createDebugContext: (view: ViewData, nodeIndex: number) => new DebugContext_(view, nodeIndex), createDebugContext: (view: ViewData, nodeIndex: number) => new DebugContext_(view, nodeIndex),
handleEvent: debugHandleEvent, handleEvent: debugHandleEvent,
updateDirectives: debugUpdateDirectives, updateDirectives: debugUpdateDirectives,
updateRenderer: debugUpdateRenderer updateRenderer: debugUpdateRenderer,
}; };
} }
@ -98,7 +111,9 @@ function debugCreateRootView(
const root = createRootData( const root = createRootData(
elInjector, ngModule, new DebugRendererFactory2(rendererFactory), projectableNodes, elInjector, ngModule, new DebugRendererFactory2(rendererFactory), projectableNodes,
rootSelectorOrNode); rootSelectorOrNode);
return callWithDebugContext(DebugAction.create, createRootView, null, [root, def, context]); const defWithOverride = applyProviderOverridesToView(def);
return callWithDebugContext(
DebugAction.create, createRootView, null, [root, defWithOverride, context]);
} }
function createRootData( function createRootData(
@ -114,6 +129,136 @@ function createRootData(
}; };
} }
function debugCreateEmbeddedView(
parentView: ViewData, anchorDef: NodeDef, viewDef: ViewDefinition, context?: any): ViewData {
const defWithOverride = applyProviderOverridesToView(viewDef);
return callWithDebugContext(
DebugAction.create, createEmbeddedView, null,
[parentView, anchorDef, defWithOverride, context]);
}
function debugCreateComponentView(
parentView: ViewData, nodeDef: NodeDef, viewDef: ViewDefinition, hostElement: any): ViewData {
const defWithOverride = applyProviderOverridesToView(viewDef);
return callWithDebugContext(
DebugAction.create, createComponentView, null,
[parentView, nodeDef, defWithOverride, hostElement]);
}
function debugCreateNgModuleRef(
moduleType: Type<any>, parentInjector: Injector, bootstrapComponents: Type<any>[],
def: NgModuleDefinition): NgModuleRef<any> {
const defWithOverride = applyProviderOverridesToNgModule(def);
return createNgModuleRef(moduleType, parentInjector, bootstrapComponents, defWithOverride);
}
const providerOverrides = new Map<any, ProviderOverride>();
function debugOverrideProvider(override: ProviderOverride) {
providerOverrides.set(override.token, override);
}
function debugClearProviderOverrides() {
providerOverrides.clear();
}
// Notes about the algorithm:
// 1) Locate the providers of an element and check if one of them was overwritten
// 2) Change the providers of that element
//
// We only create new datastructures if we need to, to keep perf impact
// reasonable.
function applyProviderOverridesToView(def: ViewDefinition): ViewDefinition {
if (providerOverrides.size === 0) {
return def;
}
const elementIndicesWithOverwrittenProviders = findElementIndicesWithOverwrittenProviders(def);
if (elementIndicesWithOverwrittenProviders.length === 0) {
return def;
}
// clone the whole view definition,
// as it maintains references between the nodes that are hard to update.
def = def.factory !(() => NOOP);
for (let i = 0; i < elementIndicesWithOverwrittenProviders.length; i++) {
applyProviderOverridesToElement(def, elementIndicesWithOverwrittenProviders[i]);
}
return def;
function findElementIndicesWithOverwrittenProviders(def: ViewDefinition): number[] {
const elIndicesWithOverwrittenProviders: number[] = [];
let lastElementDef: NodeDef|null = null;
for (let i = 0; i < def.nodes.length; i++) {
const nodeDef = def.nodes[i];
if (nodeDef.flags & NodeFlags.TypeElement) {
lastElementDef = nodeDef;
}
if (lastElementDef && nodeDef.flags & NodeFlags.CatProviderNoDirective &&
providerOverrides.has(nodeDef.provider !.token)) {
elIndicesWithOverwrittenProviders.push(lastElementDef !.index);
lastElementDef = null;
}
}
return elIndicesWithOverwrittenProviders;
}
function applyProviderOverridesToElement(viewDef: ViewDefinition, elIndex: number) {
for (let i = elIndex + 1; i < viewDef.nodes.length; i++) {
const nodeDef = viewDef.nodes[i];
if (nodeDef.flags & NodeFlags.TypeElement) {
// stop at the next element
return;
}
if (nodeDef.flags & NodeFlags.CatProviderNoDirective) {
// Make all providers lazy, so that we don't get into trouble
// with ordering problems of providers on the same element
nodeDef.flags |= NodeFlags.LazyProvider;
const provider = nodeDef.provider !;
const override = providerOverrides.get(provider.token);
if (override) {
nodeDef.flags = (nodeDef.flags & ~NodeFlags.CatProviderNoDirective) | override.flags;
provider.deps = splitDepsDsl(override.deps);
provider.value = override.value;
}
}
}
}
}
// Notes about the algorithm:
// We only create new datastructures if we need to, to keep perf impact
// reasonable.
function applyProviderOverridesToNgModule(def: NgModuleDefinition): NgModuleDefinition {
if (providerOverrides.size === 0 || !hasOverrrides(def)) {
return def;
}
// clone the whole view definition,
// as it maintains references between the nodes that are hard to update.
def = def.factory !(() => NOOP);
applyProviderOverrides(def);
return def;
function hasOverrrides(def: NgModuleDefinition): boolean {
return def.providers.some(
node =>
!!(node.flags & NodeFlags.CatProviderNoDirective) && providerOverrides.has(node.token));
}
function applyProviderOverrides(def: NgModuleDefinition) {
for (let i = 0; i < def.providers.length; i++) {
const provider = def.providers[i];
// Make all providers lazy, so that we don't get into trouble
// with ordering problems of providers on the same element
provider.flags |= NodeFlags.LazyProvider;
const override = providerOverrides.get(provider.token);
if (override) {
provider.flags = (provider.flags & ~NodeFlags.CatProviderNoDirective) | override.flags;
provider.deps = splitDepsDsl(override.deps);
provider.value = override.value;
}
}
}
}
function prodCheckAndUpdateNode( function prodCheckAndUpdateNode(
view: ViewData, nodeIndex: number, argStyle: ArgumentType, v0?: any, v1?: any, v2?: any, view: ViewData, nodeIndex: number, argStyle: ArgumentType, v0?: any, v1?: any, v2?: any,
v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, v9?: any): any { v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, v9?: any): any {
@ -134,11 +279,6 @@ function prodCheckNoChangesNode(
undefined; undefined;
} }
function debugCreateEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData {
return callWithDebugContext(
DebugAction.create, createEmbeddedView, null, [parent, anchorDef, context]);
}
function debugCheckAndUpdateView(view: ViewData) { function debugCheckAndUpdateView(view: ViewData) {
return callWithDebugContext(DebugAction.detectChanges, checkAndUpdateView, null, [view]); return callWithDebugContext(DebugAction.detectChanges, checkAndUpdateView, null, [view]);
} }

View File

@ -14,6 +14,7 @@ import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref'; import {ViewContainerRef} from '../linker/view_container_ref';
import {Renderer2, RendererFactory2, RendererType2} from '../render/api'; import {Renderer2, RendererFactory2, RendererType2} from '../render/api';
import {Sanitizer, SecurityContext} from '../security'; import {Sanitizer, SecurityContext} from '../security';
import {Type} from '../type';
// ------------------------------------- // -------------------------------------
// Defs // Defs
@ -169,8 +170,9 @@ export const enum NodeFlags {
PrivateProvider = 1 << 13, PrivateProvider = 1 << 13,
TypeDirective = 1 << 14, TypeDirective = 1 << 14,
Component = 1 << 15, Component = 1 << 15,
CatProvider = TypeValueProvider | TypeClassProvider | TypeFactoryProvider | CatProviderNoDirective =
TypeUseExistingProvider | TypeDirective, TypeValueProvider | TypeClassProvider | TypeFactoryProvider | TypeUseExistingProvider,
CatProvider = CatProviderNoDirective | TypeDirective,
OnInit = 1 << 16, OnInit = 1 << 16,
OnDestroy = 1 << 17, OnDestroy = 1 << 17,
DoCheck = 1 << 18, DoCheck = 1 << 18,
@ -495,12 +497,27 @@ export abstract class DebugContext {
export const enum CheckType {CheckAndUpdate, CheckNoChanges} export const enum CheckType {CheckAndUpdate, CheckNoChanges}
export interface ProviderOverride {
token: any;
flags: NodeFlags;
value: any;
deps: ([DepFlags, any]|any)[];
}
export interface Services { export interface Services {
setCurrentNode(view: ViewData, nodeIndex: number): void; setCurrentNode(view: ViewData, nodeIndex: number): void;
createRootView( createRootView(
injector: Injector, projectableNodes: any[][], rootSelectorOrNode: string|any, injector: Injector, projectableNodes: any[][], rootSelectorOrNode: string|any,
def: ViewDefinition, ngModule: NgModuleRef<any>, context?: any): ViewData; def: ViewDefinition, ngModule: NgModuleRef<any>, context?: any): ViewData;
createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData; createEmbeddedView(parent: ViewData, anchorDef: NodeDef, viewDef: ViewDefinition, context?: any):
ViewData;
createComponentView(
parentView: ViewData, nodeDef: NodeDef, viewDef: ViewDefinition, hostElement: any): ViewData;
createNgModuleRef(
moduleType: Type<any>, parent: Injector, bootstrapComponents: Type<any>[],
def: NgModuleDefinition): NgModuleRef<any>;
overrideProvider(override: ProviderOverride): void;
clearProviderOverrides(): void;
checkAndUpdateView(view: ViewData): void; checkAndUpdateView(view: ViewData): void;
checkNoChangesView(view: ViewData): void; checkNoChangesView(view: ViewData): void;
destroyView(view: ViewData): void; destroyView(view: ViewData): void;
@ -522,6 +539,10 @@ export const Services: Services = {
setCurrentNode: undefined !, setCurrentNode: undefined !,
createRootView: undefined !, createRootView: undefined !,
createEmbeddedView: undefined !, createEmbeddedView: undefined !,
createComponentView: undefined !,
createNgModuleRef: undefined !,
overrideProvider: undefined !,
clearProviderOverrides: undefined !,
checkAndUpdateView: undefined !, checkAndUpdateView: undefined !,
checkNoChangesView: undefined !, checkNoChangesView: undefined !,
destroyView: undefined !, destroyView: undefined !,

View File

@ -12,7 +12,7 @@ import {RendererType2} from '../render/api';
import {looseIdentical, stringify} from '../util'; import {looseIdentical, stringify} from '../util';
import {expressionChangedAfterItHasBeenCheckedError} from './errors'; import {expressionChangedAfterItHasBeenCheckedError} from './errors';
import {BindingDef, BindingFlags, Definition, DefinitionFactory, ElementData, NodeDef, NodeFlags, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asTextData} from './types'; import {BindingDef, BindingFlags, Definition, DefinitionFactory, DepDef, DepFlags, ElementData, NodeDef, NodeFlags, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asTextData} from './types';
export const NOOP: any = () => {}; export const NOOP: any = () => {};
@ -203,6 +203,20 @@ export function splitMatchedQueriesDsl(
return {matchedQueries, references, matchedQueryIds}; return {matchedQueries, references, matchedQueryIds};
} }
export function splitDepsDsl(deps: ([DepFlags, any] | any)[]): DepDef[] {
return deps.map(value => {
let token: any;
let flags: DepFlags;
if (Array.isArray(value)) {
[flags, token] = value;
} else {
flags = DepFlags.None;
token = value;
}
return {flags, token, tokenKey: tokenKey(token)};
});
}
export function getParentRenderElement(view: ViewData, renderHost: any, def: NodeDef): any { export function getParentRenderElement(view: ViewData, renderHost: any, def: NodeDef): any {
let renderParent = def.renderParent; let renderParent = def.renderParent;
if (renderParent) { if (renderParent) {

View File

@ -184,11 +184,11 @@ function validateNode(parent: NodeDef | null, node: NodeDef, nodeCount: number)
} }
} }
export function createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData { export function createEmbeddedView(
parent: ViewData, anchorDef: NodeDef, viewDef: ViewDefinition, context?: any): ViewData {
// embedded views are seen as siblings to the anchor, so we need // embedded views are seen as siblings to the anchor, so we need
// to get the parent of the anchor and use it as parentIndex. // to get the parent of the anchor and use it as parentIndex.
const view = const view = createView(parent.root, parent.renderer, parent, anchorDef, viewDef);
createView(parent.root, parent.renderer, parent, anchorDef, anchorDef.element !.template !);
initView(view, parent.component, context); initView(view, parent.component, context);
createViewNodes(view); createViewNodes(view);
return view; return view;
@ -201,6 +201,19 @@ export function createRootView(root: RootData, def: ViewDefinition, context?: an
return view; return view;
} }
export function createComponentView(
parentView: ViewData, nodeDef: NodeDef, viewDef: ViewDefinition, hostElement: any): ViewData {
const rendererType = nodeDef.element !.componentRendererType;
let compRenderer: Renderer2;
if (!rendererType) {
compRenderer = parentView.root.renderer;
} else {
compRenderer = parentView.root.rendererFactory.createRenderer(hostElement, rendererType);
}
return createView(
parentView.root, compRenderer, parentView, nodeDef.element !.componentProvider, viewDef);
}
function createView( function createView(
root: RootData, renderer: Renderer2, parent: ViewData | null, parentNodeDef: NodeDef | null, root: RootData, renderer: Renderer2, parent: ViewData | null, parentNodeDef: NodeDef | null,
def: ViewDefinition): ViewData { def: ViewDefinition): ViewData {
@ -241,15 +254,7 @@ function createViewNodes(view: ViewData) {
let componentView: ViewData = undefined !; let componentView: ViewData = undefined !;
if (nodeDef.flags & NodeFlags.ComponentView) { if (nodeDef.flags & NodeFlags.ComponentView) {
const compViewDef = resolveDefinition(nodeDef.element !.componentView !); const compViewDef = resolveDefinition(nodeDef.element !.componentView !);
const rendererType = nodeDef.element !.componentRendererType; componentView = Services.createComponentView(view, nodeDef, compViewDef, el);
let compRenderer: Renderer2;
if (!rendererType) {
compRenderer = view.root.renderer;
} else {
compRenderer = view.root.rendererFactory.createRenderer(el, rendererType);
}
componentView = createView(
view.root, compRenderer, view, nodeDef.element !.componentProvider, compViewDef);
} }
listenToElementOutputs(view, componentView, nodeDef, el); listenToElementOutputs(view, componentView, nodeDef, el);
nodeData = <ElementData>{ nodeData = <ElementData>{

View File

@ -787,14 +787,45 @@ function declareTests({useJit}: {useJit: boolean}) {
expect(child.get(Injector)).toBe(child); expect(child.get(Injector)).toBe(child);
}); });
it('should allow to inject lazy providers via Injector.get from an eager provider that is declared earlier', describe('injecting lazy providers into an eager provider via Injector.get', () => {
() => {
@NgModule({providers: [{provide: 'a', useFactory: () => 'aValue'}]}) it('should inject providers that were declared before it', () => {
class SomeModule { @NgModule({
public a: string; providers: [
constructor(injector: Injector) { this.a = injector.get('a'); } {provide: 'lazy', useFactory: () => 'lazyValue'},
{
provide: 'eager',
useFactory: (i: Injector) => `eagerValue: ${i.get('lazy')}`,
deps: [Injector]
},
]
})
class MyModule {
// NgModule is eager, which makes all of its deps eager
constructor(@Inject('eager') eager: any) {}
} }
expect(createModule(SomeModule).instance.a).toBe('aValue');
expect(createModule(MyModule).injector.get('eager')).toBe('eagerValue: lazyValue');
});
it('should inject providers that were declared after it', () => {
@NgModule({
providers: [
{
provide: 'eager',
useFactory: (i: Injector) => `eagerValue: ${i.get('lazy')}`,
deps: [Injector]
},
{provide: 'lazy', useFactory: () => 'lazyValue'},
]
})
class MyModule {
// NgModule is eager, which makes all of its deps eager
constructor(@Inject('eager') eager: any) {}
}
expect(createModule(MyModule).injector.get('eager')).toBe('eagerValue: lazyValue');
});
}); });
it('should throw when no provider defined', () => { it('should throw when no provider defined', () => {

View File

@ -342,6 +342,53 @@ export function main() {
expect(created).toBe(true); expect(created).toBe(true);
}); });
describe('injecting lazy providers into an eager provider via Injector.get', () => {
it('should inject providers that were declared before it', () => {
@Component({
template: '',
providers: [
{provide: 'lazy', useFactory: () => 'lazyValue'},
{
provide: 'eager',
useFactory: (i: Injector) => `eagerValue: ${i.get('lazy')}`,
deps: [Injector]
},
]
})
class MyComp {
// Component is eager, which makes all of its deps eager
constructor(@Inject('eager') eager: any) {}
}
const ctx =
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
expect(ctx.debugElement.injector.get('eager')).toBe('eagerValue: lazyValue');
});
it('should inject providers that were declared after it', () => {
@Component({
template: '',
providers: [
{
provide: 'eager',
useFactory: (i: Injector) => `eagerValue: ${i.get('lazy')}`,
deps: [Injector]
},
{provide: 'lazy', useFactory: () => 'lazyValue'},
]
})
class MyComp {
// Component is eager, which makes all of its deps eager
constructor(@Inject('eager') eager: any) {}
}
const ctx =
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
expect(ctx.debugElement.injector.get('eager')).toBe('eagerValue: lazyValue');
});
});
it('should allow injecting lazy providers via Injector.get from an eager provider that is declared earlier', it('should allow injecting lazy providers via Injector.get from an eager provider that is declared earlier',
() => { () => {
@Component({providers: [{provide: 'a', useFactory: () => 'aValue'}], template: ''}) @Component({providers: [{provide: 'a', useFactory: () => 'aValue'}], template: ''})

View File

@ -11,7 +11,7 @@ import {ArgumentType, BindingFlags, NodeCheckFn, NodeDef, NodeFlags, RootData, S
import {inject} from '@angular/core/testing'; import {inject} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {createRootView, isBrowser} from './helper'; import {createEmbeddedView, createRootView, isBrowser} from './helper';
export function main() { export function main() {
describe(`Embedded Views`, () => { describe(`Embedded Views`, () => {
@ -45,8 +45,7 @@ export function main() {
]), ]),
parentContext); parentContext);
const childView = const childView = createEmbeddedView(parentView, parentView.def.nodes[1], childContext);
Services.createEmbeddedView(parentView, parentView.def.nodes[1], childContext);
expect(childView.component).toBe(parentContext); expect(childView.component).toBe(parentContext);
expect(childView.context).toBe(childContext); expect(childView.context).toBe(childContext);
}); });
@ -64,8 +63,8 @@ export function main() {
])); ]));
const viewContainerData = asElementData(parentView, 1); const viewContainerData = asElementData(parentView, 1);
const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[1]); const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]);
const childView1 = Services.createEmbeddedView(parentView, parentView.def.nodes[2]); const childView1 = createEmbeddedView(parentView, parentView.def.nodes[2]);
attachEmbeddedView(parentView, viewContainerData, 0, childView0); attachEmbeddedView(parentView, viewContainerData, 0, childView0);
attachEmbeddedView(parentView, viewContainerData, 1, childView1); attachEmbeddedView(parentView, viewContainerData, 1, childView1);
@ -95,8 +94,8 @@ export function main() {
])); ]));
const viewContainerData = asElementData(parentView, 1); const viewContainerData = asElementData(parentView, 1);
const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[1]); const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]);
const childView1 = Services.createEmbeddedView(parentView, parentView.def.nodes[2]); const childView1 = createEmbeddedView(parentView, parentView.def.nodes[2]);
attachEmbeddedView(parentView, viewContainerData, 0, childView0); attachEmbeddedView(parentView, viewContainerData, 0, childView0);
attachEmbeddedView(parentView, viewContainerData, 1, childView1); attachEmbeddedView(parentView, viewContainerData, 1, childView1);
@ -119,7 +118,7 @@ export function main() {
elementDef(NodeFlags.None, null !, null !, 0, 'span', [['name', 'after']]) elementDef(NodeFlags.None, null !, null !, 0, 'span', [['name', 'after']])
])); ]));
const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[0]); const childView0 = createEmbeddedView(parentView, parentView.def.nodes[0]);
attachEmbeddedView(parentView, asElementData(parentView, 0), 0, childView0); attachEmbeddedView(parentView, asElementData(parentView, 0), 0, childView0);
const rootNodes = rootRenderNodes(parentView); const rootNodes = rootRenderNodes(parentView);
@ -146,7 +145,7 @@ export function main() {
update)) update))
])); ]));
const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[1]); const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]);
attachEmbeddedView(parentView, asElementData(parentView, 1), 0, childView0); attachEmbeddedView(parentView, asElementData(parentView, 1), 0, childView0);
@ -180,7 +179,7 @@ export function main() {
])) ]))
])); ]));
const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[1]); const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]);
attachEmbeddedView(parentView, asElementData(parentView, 1), 0, childView0); attachEmbeddedView(parentView, asElementData(parentView, 1), 0, childView0);
Services.destroyView(parentView); Services.destroyView(parentView);

View File

@ -7,7 +7,7 @@
*/ */
import {Injector, NgModuleRef, RootRenderer, Sanitizer} from '@angular/core'; import {Injector, NgModuleRef, RootRenderer, Sanitizer} from '@angular/core';
import {ArgumentType, NodeCheckFn, RootData, Services, ViewData, ViewDefinition, initServicesIfNeeded} from '@angular/core/src/view/index'; import {ArgumentType, NodeCheckFn, NodeDef, RootData, Services, ViewData, ViewDefinition, initServicesIfNeeded} from '@angular/core/src/view/index';
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
@ -37,6 +37,10 @@ export function createRootView(
TestBed.get(NgModuleRef), context); TestBed.get(NgModuleRef), context);
} }
export function createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData {
return Services.createEmbeddedView(parent, anchorDef, anchorDef.element !.template !, context);
}
export let removeNodes: Node[]; export let removeNodes: Node[];
beforeEach(() => { removeNodes = []; }); beforeEach(() => { removeNodes = []; });
afterEach(() => { removeNodes.forEach((node) => getDOM().remove(node)); }); afterEach(() => { removeNodes.forEach((node) => getDOM().remove(node)); });

View File

@ -10,7 +10,7 @@ import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext,
import {DebugContext, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, asTextData, attachEmbeddedView, detachEmbeddedView, directiveDef, elementDef, ngContentDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {DebugContext, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, asTextData, attachEmbeddedView, detachEmbeddedView, directiveDef, elementDef, ngContentDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {createRootView, isBrowser} from './helper'; import {createEmbeddedView, createRootView, isBrowser} from './helper';
export function main() { export function main() {
describe(`View NgContent`, () => { describe(`View NgContent`, () => {
@ -121,7 +121,7 @@ export function main() {
]))); ])));
const componentView = asElementData(view, 0).componentView; const componentView = asElementData(view, 0).componentView;
const view0 = Services.createEmbeddedView(componentView, componentView.def.nodes[1]); const view0 = createEmbeddedView(componentView, componentView.def.nodes[1]);
attachEmbeddedView(view, asElementData(componentView, 1), 0, view0); attachEmbeddedView(view, asElementData(componentView, 1), 0, view0);
expect(getDOM().childNodes(getDOM().firstChild(rootNodes[0])).length).toBe(3); expect(getDOM().childNodes(getDOM().firstChild(rootNodes[0])).length).toBe(3);

View File

@ -12,7 +12,7 @@ import {BindingFlags, DebugContext, NodeDef, NodeFlags, QueryBindingType, QueryV
import {inject} from '@angular/core/testing'; import {inject} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {createRootView} from './helper'; import {createEmbeddedView, createRootView} from './helper';
export function main() { export function main() {
describe(`Query Views`, () => { describe(`Query Views`, () => {
@ -155,7 +155,7 @@ export function main() {
...contentQueryProviders(), ...contentQueryProviders(),
])); ]));
const childView = Services.createEmbeddedView(view, view.def.nodes[3]); const childView = createEmbeddedView(view, view.def.nodes[3]);
attachEmbeddedView(view, asElementData(view, 3), 0, childView); attachEmbeddedView(view, asElementData(view, 3), 0, childView);
Services.checkAndUpdateView(view); Services.checkAndUpdateView(view);
@ -183,7 +183,7 @@ export function main() {
anchorDef(NodeFlags.EmbeddedViews, null !, null !, 0), anchorDef(NodeFlags.EmbeddedViews, null !, null !, 0),
])); ]));
const childView = Services.createEmbeddedView(view, view.def.nodes[3]); const childView = createEmbeddedView(view, view.def.nodes[3]);
// attach at a different place than the one where the template was defined // attach at a different place than the one where the template was defined
attachEmbeddedView(view, asElementData(view, 7), 0, childView); attachEmbeddedView(view, asElementData(view, 7), 0, childView);
@ -214,7 +214,7 @@ export function main() {
const qs: QueryService = asProviderData(view, 1).instance; const qs: QueryService = asProviderData(view, 1).instance;
expect(qs.a.length).toBe(0); expect(qs.a.length).toBe(0);
const childView = Services.createEmbeddedView(view, view.def.nodes[3]); const childView = createEmbeddedView(view, view.def.nodes[3]);
attachEmbeddedView(view, asElementData(view, 3), 0, childView); attachEmbeddedView(view, asElementData(view, 3), 0, childView);
Services.checkAndUpdateView(view); Services.checkAndUpdateView(view);
@ -245,7 +245,7 @@ export function main() {
expect(comp.a.length).toBe(0); expect(comp.a.length).toBe(0);
const compView = asElementData(view, 0).componentView; const compView = asElementData(view, 0).componentView;
const childView = Services.createEmbeddedView(compView, compView.def.nodes[1]); const childView = createEmbeddedView(compView, compView.def.nodes[1]);
attachEmbeddedView(view, asElementData(compView, 1), 0, childView); attachEmbeddedView(view, asElementData(compView, 1), 0, childView);
Services.checkAndUpdateView(view); Services.checkAndUpdateView(view);

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompilerOptions, Component, Directive, InjectionToken, Injector, ModuleWithComponentFactories, NgModule, NgModuleFactory, NgModuleRef, NgZone, Pipe, PlatformRef, Provider, ReflectiveInjector, SchemaMetadata, Type, ɵERROR_COMPONENT_TYPE, ɵstringify as stringify} from '@angular/core'; import {CompilerOptions, Component, Directive, InjectionToken, Injector, ModuleWithComponentFactories, NgModule, NgModuleFactory, NgModuleRef, NgZone, Optional, Pipe, PlatformRef, Provider, ReflectiveInjector, SchemaMetadata, SkipSelf, Type, ɵDepFlags as DepFlags, ɵERROR_COMPONENT_TYPE, ɵNodeFlags as NodeFlags, ɵclearProviderOverrides as clearProviderOverrides, ɵoverrideProvider as overrideProvider, ɵstringify as stringify} from '@angular/core';
import {AsyncTestCompleter} from './async_test_completer'; import {AsyncTestCompleter} from './async_test_completer';
import {ComponentFixture} from './component_fixture'; import {ComponentFixture} from './component_fixture';
@ -141,6 +141,24 @@ export class TestBed implements Injector {
return TestBed; return TestBed;
} }
/**
* Overwrites all providers for the given token with the given provider definition.
*/
static overrideProvider(token: any, provider: {
useFactory: Function,
deps: any[],
}): void;
static overrideProvider(token: any, provider: {useValue: any;}): void;
static overrideProvider(token: any, provider: {
useFactory?: Function,
useValue?: any,
deps?: any[],
}): typeof TestBed {
getTestBed().overrideProvider(token, provider as any);
return TestBed;
}
static get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND) { static get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND) {
return getTestBed().get(token, notFoundValue); return getTestBed().get(token, notFoundValue);
} }
@ -212,6 +230,7 @@ export class TestBed implements Injector {
} }
resetTestingModule() { resetTestingModule() {
clearProviderOverrides();
this._compiler = null !; this._compiler = null !;
this._moduleOverrides = []; this._moduleOverrides = [];
this._componentOverrides = []; this._componentOverrides = [];
@ -364,6 +383,49 @@ export class TestBed implements Injector {
this._pipeOverrides.push([pipe, override]); this._pipeOverrides.push([pipe, override]);
} }
/**
* Overwrites all providers for the given token with the given provider definition.
*/
overrideProvider(token: any, provider: {
useFactory: Function,
deps: any[],
}): void;
overrideProvider(token: any, provider: {useValue: any;}): void;
overrideProvider(token: any, provider: {
useFactory?: Function,
useValue?: any,
deps?: any[],
}): void {
let flags: NodeFlags = 0;
let value: any;
if (provider.useFactory) {
flags |= NodeFlags.TypeFactoryProvider;
value = provider.useFactory;
} else {
flags |= NodeFlags.TypeValueProvider;
value = provider.useValue;
}
const deps = (provider.deps || []).map((dep) => {
let depFlags: DepFlags = DepFlags.None;
let depToken: any;
if (Array.isArray(dep)) {
dep.forEach((entry: any) => {
if (entry instanceof Optional) {
depFlags |= DepFlags.Optional;
} else if (entry instanceof SkipSelf) {
depFlags |= DepFlags.SkipSelf;
} else {
depToken = entry;
}
});
} else {
depToken = dep;
}
return [depFlags, depToken];
});
overrideProvider({token, flags, deps, value});
}
createComponent<T>(component: Type<T>): ComponentFixture<T> { createComponent<T>(component: Type<T>): ComponentFixture<T> {
this._initIfNeeded(); this._initIfNeeded();
const componentFactory = this._compiler.getComponentFactory(component); const componentFactory = this._compiler.getComponentFactory(component);

View File

@ -7,7 +7,7 @@
*/ */
import {CompilerConfig, ResourceLoader} from '@angular/compiler'; import {CompilerConfig, ResourceLoader} from '@angular/compiler';
import {CUSTOM_ELEMENTS_SCHEMA, Component, Directive, Injectable, Input, NgModule, Pipe, ɵstringify as stringify} from '@angular/core'; import {CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, Directive, Inject, Injectable, Injector, Input, NgModule, Optional, Pipe, SkipSelf, ɵstringify as stringify} from '@angular/core';
import {TestBed, async, fakeAsync, getTestBed, inject, tick, withModule} from '@angular/core/testing'; import {TestBed, async, fakeAsync, getTestBed, inject, tick, withModule} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers'; import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -401,6 +401,277 @@ export function main() {
}); });
}); });
describe('overriding providers', () => {
describe('in NgModules', () => {
it('should support useValue', () => {
TestBed.configureTestingModule({
providers: [
{provide: 'a', useValue: 'aValue'},
]
});
TestBed.overrideProvider('a', {useValue: 'mockValue'});
expect(TestBed.get('a')).toBe('mockValue');
});
it('should support useFactory', () => {
TestBed.configureTestingModule({
providers: [
{provide: 'dep', useValue: 'depValue'},
{provide: 'a', useValue: 'aValue'},
]
});
TestBed.overrideProvider(
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: ['dep']});
expect(TestBed.get('a')).toBe('mockA: depValue');
});
it('should support @Optional without matches', () => {
TestBed.configureTestingModule({
providers: [
{provide: 'a', useValue: 'aValue'},
]
});
TestBed.overrideProvider(
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]});
expect(TestBed.get('a')).toBe('mockA: null');
});
it('should support Optional with matches', () => {
TestBed.configureTestingModule({
providers: [
{provide: 'dep', useValue: 'depValue'},
{provide: 'a', useValue: 'aValue'},
]
});
TestBed.overrideProvider(
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]});
expect(TestBed.get('a')).toBe('mockA: depValue');
});
it('should support SkipSelf', () => {
@NgModule({
providers: [
{provide: 'a', useValue: 'aValue'},
{provide: 'dep', useValue: 'depValue'},
]
})
class MyModule {
}
TestBed.overrideProvider(
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new SkipSelf(), 'dep']]});
TestBed.configureTestingModule(
{providers: [{provide: 'dep', useValue: 'parentDepValue'}]});
const compiler = TestBed.get(Compiler) as Compiler;
const modFactory = compiler.compileModuleSync(MyModule);
expect(modFactory.create(getTestBed()).injector.get('a')).toBe('mockA: parentDepValue');
});
describe('injecting eager providers into an eager overwritten provider', () => {
@NgModule({
providers: [
{provide: 'a', useFactory: () => 'aValue'},
{provide: 'b', useFactory: () => 'bValue'},
]
})
class MyModule {
// NgModule is eager, which makes all of its deps eager
constructor(@Inject('a') a: any, @Inject('b') b: any) {}
}
it('should inject providers that were declared before', () => {
TestBed.configureTestingModule({imports: [MyModule]});
TestBed.overrideProvider(
'b', {useFactory: (a: string) => `mockB: ${a}`, deps: ['a']});
expect(TestBed.get('b')).toBe('mockB: aValue');
});
it('should inject providers that were declared afterwards', () => {
TestBed.configureTestingModule({imports: [MyModule]});
TestBed.overrideProvider(
'a', {useFactory: (b: string) => `mockA: ${b}`, deps: ['b']});
expect(TestBed.get('a')).toBe('mockA: bValue');
});
});
});
describe('in Components', () => {
it('should support useValue', () => {
@Component({
template: '',
providers: [
{provide: 'a', useValue: 'aValue'},
]
})
class MComp {
}
TestBed.overrideProvider('a', {useValue: 'mockValue'});
const ctx =
TestBed.configureTestingModule({declarations: [MComp]}).createComponent(MComp);
expect(ctx.debugElement.injector.get('a')).toBe('mockValue');
});
it('should support useFactory', () => {
@Component({
template: '',
providers: [
{provide: 'dep', useValue: 'depValue'},
{provide: 'a', useValue: 'aValue'},
]
})
class MyComp {
}
TestBed.overrideProvider(
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: ['dep']});
const ctx =
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
expect(ctx.debugElement.injector.get('a')).toBe('mockA: depValue');
});
it('should support @Optional without matches', () => {
@Component({
template: '',
providers: [
{provide: 'a', useValue: 'aValue'},
]
})
class MyComp {
}
TestBed.overrideProvider(
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]});
const ctx =
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
expect(ctx.debugElement.injector.get('a')).toBe('mockA: null');
});
it('should support Optional with matches', () => {
@Component({
template: '',
providers: [
{provide: 'dep', useValue: 'depValue'},
{provide: 'a', useValue: 'aValue'},
]
})
class MyComp {
}
TestBed.overrideProvider(
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]});
const ctx =
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
expect(ctx.debugElement.injector.get('a')).toBe('mockA: depValue');
});
it('should support SkipSelf', () => {
@Directive({
selector: '[myDir]',
providers: [
{provide: 'a', useValue: 'aValue'},
{provide: 'dep', useValue: 'depValue'},
]
})
class MyDir {
}
@Component({
template: '<div myDir></div>',
providers: [
{provide: 'dep', useValue: 'parentDepValue'},
]
})
class MyComp {
}
TestBed.overrideProvider(
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new SkipSelf(), 'dep']]});
const ctx = TestBed.configureTestingModule({declarations: [MyComp, MyDir]})
.createComponent(MyComp);
expect(ctx.debugElement.children[0].injector.get('a')).toBe('mockA: parentDepValue');
});
it('should support multiple providers in a template', () => {
@Directive({
selector: '[myDir1]',
providers: [
{provide: 'a', useValue: 'aValue1'},
]
})
class MyDir1 {
}
@Directive({
selector: '[myDir2]',
providers: [
{provide: 'a', useValue: 'aValue2'},
]
})
class MyDir2 {
}
@Component({
template: '<div myDir1></div><div myDir2></div>',
})
class MyComp {
}
TestBed.overrideProvider('a', {useValue: 'mockA'});
const ctx = TestBed.configureTestingModule({declarations: [MyComp, MyDir1, MyDir2]})
.createComponent(MyComp);
expect(ctx.debugElement.children[0].injector.get('a')).toBe('mockA');
expect(ctx.debugElement.children[1].injector.get('a')).toBe('mockA');
});
describe('injecting eager providers into an eager overwritten provider', () => {
@Component({
template: '',
providers: [
{provide: 'a', useFactory: () => 'aValue'},
{provide: 'b', useFactory: () => 'bValue'},
]
})
class MyComp {
// Component is eager, which makes all of its deps eager
constructor(@Inject('a') a: any, @Inject('b') b: any) {}
}
it('should inject providers that were declared before it', () => {
TestBed.overrideProvider(
'b', {useFactory: (a: string) => `mockB: ${a}`, deps: ['a']});
const ctx =
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
expect(ctx.debugElement.injector.get('b')).toBe('mockB: aValue');
});
it('should inject providers that were declared after it', () => {
TestBed.overrideProvider(
'a', {useFactory: (b: string) => `mockA: ${b}`, deps: ['b']});
const ctx =
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
expect(ctx.debugElement.injector.get('a')).toBe('mockA: bValue');
});
});
});
it('should reset overrides when the testing modules is resetted', () => {
TestBed.overrideProvider('a', {useValue: 'mockValue'});
TestBed.resetTestingModule();
TestBed.configureTestingModule({providers: [{provide: 'a', useValue: 'aValue'}]});
expect(TestBed.get('a')).toBe('aValue');
});
});
describe('setting up the compiler', () => { describe('setting up the compiler', () => {
describe('providers', () => { describe('providers', () => {

View File

@ -75,6 +75,13 @@ export declare class TestBed implements Injector {
overrideDirective(directive: Type<any>, override: MetadataOverride<Directive>): void; overrideDirective(directive: Type<any>, override: MetadataOverride<Directive>): void;
overrideModule(ngModule: Type<any>, override: MetadataOverride<NgModule>): void; overrideModule(ngModule: Type<any>, override: MetadataOverride<NgModule>): void;
overridePipe(pipe: Type<any>, override: MetadataOverride<Pipe>): void; overridePipe(pipe: Type<any>, override: MetadataOverride<Pipe>): void;
overrideProvider(token: any, provider: {
useFactory: Function;
deps: any[];
}): void;
overrideProvider(token: any, provider: {
useValue: any;
}): void;
/** @experimental */ resetTestEnvironment(): void; /** @experimental */ resetTestEnvironment(): void;
resetTestingModule(): void; resetTestingModule(): void;
static compileComponents(): Promise<any>; static compileComponents(): Promise<any>;
@ -90,6 +97,13 @@ export declare class TestBed implements Injector {
static overrideDirective(directive: Type<any>, override: MetadataOverride<Directive>): typeof TestBed; static overrideDirective(directive: Type<any>, override: MetadataOverride<Directive>): typeof TestBed;
static overrideModule(ngModule: Type<any>, override: MetadataOverride<NgModule>): typeof TestBed; static overrideModule(ngModule: Type<any>, override: MetadataOverride<NgModule>): typeof TestBed;
static overridePipe(pipe: Type<any>, override: MetadataOverride<Pipe>): typeof TestBed; static overridePipe(pipe: Type<any>, override: MetadataOverride<Pipe>): typeof TestBed;
static overrideProvider(token: any, provider: {
useFactory: Function;
deps: any[];
}): void;
static overrideProvider(token: any, provider: {
useValue: any;
}): void;
static overrideTemplate(component: Type<any>, template: string): typeof TestBed; static overrideTemplate(component: Type<any>, template: string): typeof TestBed;
/** @experimental */ static resetTestEnvironment(): void; /** @experimental */ static resetTestEnvironment(): void;
static resetTestingModule(): typeof TestBed; static resetTestingModule(): typeof TestBed;