feat(ivy): support injection even if no injector present (#23345)
- Remove default injection value from `inject` / `directiveInject` since it is not possible to set using annotations. - Module `Injector` is stored on `LView` instead of `LInjector` data structure because it can change only at `LView` level. (More efficient) - Add `ngInjectableDef` to `IterableDiffers` so that existing tests can pass as well as enable `IterableDiffers` to be injectable without `Injector` PR Close #23345
This commit is contained in:
parent
6f213a74f2
commit
da31db757b
|
@ -2237,7 +2237,7 @@ describe('ngc transformer command-line', () => {
|
|||
constructor(e: Existing|null) {}
|
||||
}
|
||||
`);
|
||||
expect(source).toMatch(/ngInjectableDef.*return ..\(..\.inject\(Existing, null, 0\)/);
|
||||
expect(source).toMatch(/ngInjectableDef.*return ..\(..\.inject\(Existing, 8\)/);
|
||||
});
|
||||
|
||||
it('compiles a useFactory InjectableDef with skip-self dep', () => {
|
||||
|
@ -2257,7 +2257,7 @@ describe('ngc transformer command-line', () => {
|
|||
constructor(e: Existing) {}
|
||||
}
|
||||
`);
|
||||
expect(source).toMatch(/ngInjectableDef.*return ..\(..\.inject\(Existing, undefined, 4\)/);
|
||||
expect(source).toMatch(/ngInjectableDef.*return ..\(..\.inject\(Existing, 4\)/);
|
||||
});
|
||||
|
||||
it('compiles a service that depends on a token', () => {
|
||||
|
|
|
@ -38,7 +38,6 @@ export class InjectableCompiler {
|
|||
private depsArray(deps: any[], ctx: OutputContext): o.Expression[] {
|
||||
return deps.map(dep => {
|
||||
let token = dep;
|
||||
let defaultValue = undefined;
|
||||
let args = [token];
|
||||
let flags: InjectFlags = InjectFlags.Default;
|
||||
if (Array.isArray(dep)) {
|
||||
|
@ -46,7 +45,7 @@ export class InjectableCompiler {
|
|||
const v = dep[i];
|
||||
if (v) {
|
||||
if (v.ngMetadataName === 'Optional') {
|
||||
defaultValue = null;
|
||||
flags |= InjectFlags.Optional;
|
||||
} else if (v.ngMetadataName === 'SkipSelf') {
|
||||
flags |= InjectFlags.SkipSelf;
|
||||
} else if (v.ngMetadataName === 'Self') {
|
||||
|
@ -69,8 +68,8 @@ export class InjectableCompiler {
|
|||
tokenExpr = ctx.importExpr(token);
|
||||
}
|
||||
|
||||
if (flags !== InjectFlags.Default || defaultValue !== undefined) {
|
||||
args = [tokenExpr, o.literal(defaultValue), o.literal(flags)];
|
||||
if (flags !== InjectFlags.Default) {
|
||||
args = [tokenExpr, o.literal(flags)];
|
||||
} else {
|
||||
args = [tokenExpr];
|
||||
}
|
||||
|
|
|
@ -948,7 +948,7 @@ export function createFactory(
|
|||
const flags = extractFlags(dependency);
|
||||
if (flags != InjectFlags.Default) {
|
||||
// Append flag information if other than default.
|
||||
directiveInjectArgs.push(o.literal(undefined), o.literal(flags));
|
||||
directiveInjectArgs.push(o.literal(flags));
|
||||
}
|
||||
args.push(o.importExpr(R3.directiveInject).callFn(directiveInjectArgs));
|
||||
}
|
||||
|
|
|
@ -53,11 +53,11 @@ describe('compiler compliance: dependency injection', () => {
|
|||
return new MyComponent(
|
||||
$r3$.ɵinjectAttribute('name'),
|
||||
$r3$.ɵdirectiveInject(MyService),
|
||||
$r3$.ɵdirectiveInject(MyService, (undefined as any), 1),
|
||||
$r3$.ɵdirectiveInject(MyService, (undefined as any), 2),
|
||||
$r3$.ɵdirectiveInject(MyService, (undefined as any), 4),
|
||||
$r3$.ɵdirectiveInject(MyService, (undefined as any), 8),
|
||||
$r3$.ɵdirectiveInject(MyService, (undefined as any), 10)
|
||||
$r3$.ɵdirectiveInject(MyService, 1),
|
||||
$r3$.ɵdirectiveInject(MyService, 2),
|
||||
$r3$.ɵdirectiveInject(MyService, 4),
|
||||
$r3$.ɵdirectiveInject(MyService, 8),
|
||||
$r3$.ɵdirectiveInject(MyService, 10)
|
||||
);
|
||||
}`;
|
||||
|
||||
|
|
|
@ -6,8 +6,10 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {InjectableDef, defineInjectable} from '../../di/defs';
|
||||
import {Optional, SkipSelf} from '../../di/metadata';
|
||||
import {StaticProvider} from '../../di/provider';
|
||||
import {DefaultIterableDifferFactory} from '../differs/default_iterable_differ';
|
||||
|
||||
|
||||
/**
|
||||
|
@ -135,6 +137,11 @@ export interface IterableDifferFactory {
|
|||
*
|
||||
*/
|
||||
export class IterableDiffers {
|
||||
static ngInjectableDef: InjectableDef<IterableDiffers> = defineInjectable({
|
||||
providedIn: 'root',
|
||||
factory: () => new IterableDiffers([new DefaultIterableDifferFactory()])
|
||||
});
|
||||
|
||||
/**
|
||||
* @deprecated v4.0.0 - Should be private
|
||||
*/
|
||||
|
|
|
@ -13,7 +13,7 @@ export {devModeEqual as ɵdevModeEqual} from './change_detection/change_detectio
|
|||
export {isListLikeIterable as ɵisListLikeIterable} from './change_detection/change_detection_util';
|
||||
export {ChangeDetectorStatus as ɵChangeDetectorStatus, isDefaultChangeDetectionStrategy as ɵisDefaultChangeDetectionStrategy} from './change_detection/constants';
|
||||
export {Console as ɵConsole} from './console';
|
||||
export {setCurrentInjector as ɵsetCurrentInjector} from './di/injector';
|
||||
export {inject as ɵinject, setCurrentInjector as ɵsetCurrentInjector} from './di/injector';
|
||||
export {APP_ROOT as ɵAPP_ROOT} from './di/scope';
|
||||
export {ComponentFactory as ɵComponentFactory} from './linker/component_factory';
|
||||
export {CodegenComponentFactoryResolver as ɵCodegenComponentFactoryResolver} from './linker/component_factory_resolver';
|
||||
|
|
|
@ -25,8 +25,25 @@ import {ClassProvider, ClassSansProvider, ConstructorProvider, ConstructorSansPr
|
|||
* @experimental
|
||||
*/
|
||||
export interface InjectableDef<T> {
|
||||
/**
|
||||
* Specifies that the given type belongs to a particular injector:
|
||||
* - `InjectorType` such as `NgModule`,
|
||||
* - `'root'` the root injector
|
||||
* - `'any'` all injectors.
|
||||
* - `null`, does not belong to any injector. Must be explicitly listed in the injector
|
||||
* `providers`.
|
||||
*/
|
||||
providedIn: InjectorType<any>|'root'|'any'|null;
|
||||
|
||||
/**
|
||||
* Factory method to execute to create an instance of the injectable.
|
||||
*/
|
||||
factory: () => T;
|
||||
|
||||
/**
|
||||
* In a case of no explicit injector, a location where the instance of the injectable is stored.
|
||||
*/
|
||||
value: T|undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,12 +118,13 @@ export interface InjectorTypeWithProviders<T> {
|
|||
* @experimental
|
||||
*/
|
||||
export function defineInjectable<T>(opts: {
|
||||
providedIn?: Type<any>| 'root' | null,
|
||||
providedIn?: Type<any>| 'root' | 'any' | null,
|
||||
factory: () => T,
|
||||
}): InjectableDef<T> {
|
||||
return {
|
||||
providedIn: (opts.providedIn as InjectorType<any>| 'root' | null | undefined) || null,
|
||||
providedIn: opts.providedIn as any || null,
|
||||
factory: opts.factory,
|
||||
value: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -428,9 +428,15 @@ export const enum InjectFlags {
|
|||
Optional = 1 << 3,
|
||||
}
|
||||
|
||||
let _currentInjector: Injector|null = null;
|
||||
/**
|
||||
* Current injector value used by `inject`.
|
||||
* - `undefined`: it is an error to call `inject`
|
||||
* - `null`: `inject` can be called but there is no injector (limp-mode).
|
||||
* - Injector instance: Use the injector for resolution.
|
||||
*/
|
||||
let _currentInjector: Injector|undefined|null = undefined;
|
||||
|
||||
export function setCurrentInjector(injector: Injector | null): Injector|null {
|
||||
export function setCurrentInjector(injector: Injector | null | undefined): Injector|undefined|null {
|
||||
const former = _currentInjector;
|
||||
_currentInjector = injector;
|
||||
return former;
|
||||
|
@ -450,19 +456,21 @@ export function setCurrentInjector(injector: Injector | null): Injector|null {
|
|||
*
|
||||
* @experimental
|
||||
*/
|
||||
export function inject<T>(
|
||||
token: Type<T>| InjectionToken<T>, notFoundValue?: undefined, flags?: InjectFlags): T;
|
||||
export function inject<T>(
|
||||
token: Type<T>| InjectionToken<T>, notFoundValue: T, flags?: InjectFlags): T;
|
||||
export function inject<T>(
|
||||
token: Type<T>| InjectionToken<T>, notFoundValue: null, flags?: InjectFlags): T|null;
|
||||
export function inject<T>(
|
||||
token: Type<T>| InjectionToken<T>, notFoundValue?: T | null, flags = InjectFlags.Default): T|
|
||||
null {
|
||||
if (_currentInjector === null) {
|
||||
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 {
|
||||
if (_currentInjector === undefined) {
|
||||
throw new Error(`inject() must be called from an injection context`);
|
||||
} else if (_currentInjector === null) {
|
||||
const injectableDef: InjectableDef<T> = (token as any).ngInjectableDef;
|
||||
if (injectableDef && injectableDef.providedIn == 'root') {
|
||||
return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() :
|
||||
injectableDef.value;
|
||||
}
|
||||
throw new Error(`Injector: NOT_FOUND [${stringify(token)}]`);
|
||||
} else {
|
||||
return _currentInjector.get(token, flags & InjectFlags.Optional ? null : undefined, flags);
|
||||
}
|
||||
return _currentInjector.get(token, notFoundValue, flags);
|
||||
}
|
||||
|
||||
export function injectArgs(types: (Type<any>| InjectionToken<any>| any[])[]): any[] {
|
||||
|
@ -474,13 +482,12 @@ export function injectArgs(types: (Type<any>| InjectionToken<any>| any[])[]): an
|
|||
throw new Error('Arguments array must have arguments.');
|
||||
}
|
||||
let type: Type<any>|undefined = undefined;
|
||||
let defaultValue: null|undefined = undefined;
|
||||
let flags: InjectFlags = InjectFlags.Default;
|
||||
|
||||
for (let j = 0; j < arg.length; j++) {
|
||||
const meta = arg[j];
|
||||
if (meta instanceof Optional || meta.__proto__.ngMetadataName === 'Optional') {
|
||||
defaultValue = null;
|
||||
flags |= InjectFlags.Optional;
|
||||
} else if (meta instanceof SkipSelf || meta.__proto__.ngMetadataName === 'SkipSelf') {
|
||||
flags |= InjectFlags.SkipSelf;
|
||||
} else if (meta instanceof Self || meta.__proto__.ngMetadataName === 'Self') {
|
||||
|
@ -492,7 +499,7 @@ export function injectArgs(types: (Type<any>| InjectionToken<any>| any[])[]): an
|
|||
}
|
||||
}
|
||||
|
||||
args.push(inject(type !, defaultValue, InjectFlags.Default));
|
||||
args.push(inject(type !, flags));
|
||||
} else {
|
||||
args.push(inject(arg));
|
||||
}
|
||||
|
|
|
@ -132,10 +132,11 @@ export function renderComponent<T>(
|
|||
scheduler: opts.scheduler || requestAnimationFrame.bind(window),
|
||||
clean: CLEAN_PROMISE,
|
||||
};
|
||||
const rootView = createLView(
|
||||
const rootView: LView = createLView(
|
||||
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType),
|
||||
createTView(null, null), null, rootContext,
|
||||
componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways);
|
||||
rootView.injector = opts.injector || null;
|
||||
|
||||
const oldView = enterView(rootView, null !);
|
||||
let elementNode: LElementNode;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
// We are temporarily importing the existing viewEngine_from core so we can be sure we are
|
||||
// correctly implementing its interfaces for backwards compatibility.
|
||||
import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
|
||||
import {InjectFlags, Injector} from '../di/injector';
|
||||
import {InjectFlags, Injector, inject, setCurrentInjector} from '../di/injector';
|
||||
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
|
||||
import {ElementRef as viewEngine_ElementRef} from '../linker/element_ref';
|
||||
import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
|
||||
|
@ -125,7 +125,6 @@ export function getOrCreateNodeInjectorForNode(node: LElementNode | LContainerNo
|
|||
cbf5: parentInjector == null ? 0 : parentInjector.cbf5 | parentInjector.bf5,
|
||||
cbf6: parentInjector == null ? 0 : parentInjector.cbf6 | parentInjector.bf6,
|
||||
cbf7: parentInjector == null ? 0 : parentInjector.cbf7 | parentInjector.bf7,
|
||||
injector: null,
|
||||
templateRef: null,
|
||||
viewContainerRef: null,
|
||||
elementRef: null,
|
||||
|
@ -133,16 +132,6 @@ export function getOrCreateNodeInjectorForNode(node: LElementNode | LContainerNo
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an injection error with the given text and token.
|
||||
*
|
||||
* @param text The text of the error
|
||||
* @param token The token associated with the error
|
||||
* @returns The error that was created
|
||||
*/
|
||||
function createInjectionError(text: string, token: any) {
|
||||
return new Error(`ElementInjector: ${text} [${stringify(token)}]`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a directive public to the DI system by adding it to an injector's bloom filter.
|
||||
|
@ -188,14 +177,10 @@ export function diPublic(def: DirectiveDef<any>): void {
|
|||
* @param flags Injection flags (e.g. CheckParent)
|
||||
* @returns The instance found
|
||||
*/
|
||||
export function directiveInject<T>(
|
||||
token: Type<T>, notFoundValue?: undefined, flags?: InjectFlags): T;
|
||||
export function directiveInject<T>(token: Type<T>, notFoundValue: T, flags?: InjectFlags): T;
|
||||
export function directiveInject<T>(token: Type<T>, notFoundValue: null, flags?: InjectFlags): T|
|
||||
null;
|
||||
export function directiveInject<T>(
|
||||
token: Type<T>, notFoundValue?: T | null, flags = InjectFlags.Default): T|null {
|
||||
return getOrCreateInjectable<T>(getOrCreateNodeInjector(), token, flags, notFoundValue);
|
||||
export function directiveInject<T>(token: Type<T>): T;
|
||||
export function directiveInject<T>(token: Type<T>, flags?: InjectFlags): T|null;
|
||||
export function directiveInject<T>(token: Type<T>, flags = InjectFlags.Default): T|null {
|
||||
return getOrCreateInjectable<T>(getOrCreateNodeInjector(), token, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -344,21 +329,20 @@ function getClosestComponentAncestor(node: LViewNode | LElementNode): LElementNo
|
|||
* @param flags Injection flags (e.g. CheckParent)
|
||||
* @returns The instance found
|
||||
*/
|
||||
export function getOrCreateInjectable<T>(
|
||||
di: LInjector, token: Type<T>, flags?: InjectFlags, defaultValue?: T | null): T|null {
|
||||
export function getOrCreateInjectable<T>(di: LInjector, token: Type<T>, flags?: InjectFlags): T|
|
||||
null {
|
||||
const bloomHash = bloomHashBit(token);
|
||||
|
||||
// If the token has a bloom hash, then it is a directive that is public to the injection system
|
||||
// (diPublic). If there is no hash, fall back to the module injector.
|
||||
if (bloomHash === null) {
|
||||
const moduleInjector = di.injector;
|
||||
if (!moduleInjector) {
|
||||
if (defaultValue != null) {
|
||||
return defaultValue;
|
||||
}
|
||||
throw createInjectionError('NotFound', token);
|
||||
const moduleInjector = getPreviousOrParentNode().view.injector;
|
||||
const formerInjector = setCurrentInjector(moduleInjector);
|
||||
try {
|
||||
return inject(token, flags);
|
||||
} finally {
|
||||
setCurrentInjector(formerInjector);
|
||||
}
|
||||
moduleInjector.get(token);
|
||||
} else {
|
||||
let injector: LInjector|null = di;
|
||||
|
||||
|
@ -409,7 +393,7 @@ export function getOrCreateInjectable<T>(
|
|||
|
||||
// No directive was found for the given token.
|
||||
// TODO: implement optional, check-self, and check-parent.
|
||||
throw createInjectionError('Not found', token);
|
||||
throw new Error('Implement');
|
||||
}
|
||||
|
||||
function searchMatchesQueuedForCreation<T>(node: LNode, token: any): T|null {
|
||||
|
|
|
@ -301,6 +301,7 @@ export function createLView<T>(
|
|||
dynamicViewCount: 0,
|
||||
lifecycleStage: LifecycleStage.Init,
|
||||
queries: null,
|
||||
injector: currentView && currentView.injector,
|
||||
};
|
||||
|
||||
return newView;
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
*/
|
||||
|
||||
import {ChangeDetectorRef} from '../../change_detection/change_detector_ref';
|
||||
import {Injector} from '../../di/injector';
|
||||
import {ElementRef} from '../../linker/element_ref';
|
||||
import {TemplateRef} from '../../linker/template_ref';
|
||||
import {ViewContainerRef} from '../../linker/view_container_ref';
|
||||
|
@ -69,8 +68,6 @@ export interface LInjector {
|
|||
cbf6: number;
|
||||
cbf7: number;
|
||||
|
||||
injector: Injector|null;
|
||||
|
||||
/** Stores the TemplateRef so subsequent injections of the TemplateRef get the same instance. */
|
||||
templateRef: TemplateRef<any>|null;
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Injector} from '../../di/injector';
|
||||
import {LContainer} from './container';
|
||||
import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDef, PipeDefList} from './definition';
|
||||
import {LElementNode, LViewNode, TNode} from './node';
|
||||
|
@ -189,6 +190,11 @@ export interface LView {
|
|||
* Queries active for this view - nodes from a view are reported to those queries
|
||||
*/
|
||||
queries: LQueries|null;
|
||||
|
||||
/**
|
||||
* An optional Module Injector to be used as fall back after Element Injectors are consulted.
|
||||
*/
|
||||
injector: Injector|null;
|
||||
}
|
||||
|
||||
/** Flags associated with an LView (saved in LView.flags) */
|
||||
|
|
|
@ -19,8 +19,7 @@ NgForOf.ngDirectiveDef = defineDirective({
|
|||
type: NgForOfDef,
|
||||
selectors: [['', 'ngForOf', '']],
|
||||
factory: () => new NgForOfDef(
|
||||
injectViewContainerRef(), injectTemplateRef(),
|
||||
directiveInject(IterableDiffers, defaultIterableDiffers, InjectFlags.Default)),
|
||||
injectViewContainerRef(), injectTemplateRef(), directiveInject(IterableDiffers)),
|
||||
features: [NgOnChangesFeature()],
|
||||
inputs: {
|
||||
ngForOf: 'ngForOf',
|
||||
|
|
|
@ -185,7 +185,7 @@ describe('injection', () => {
|
|||
// NORMATIVE
|
||||
static ngInjectableDef = defineInjectable({
|
||||
factory: function ServiceA_Factory() {
|
||||
return new ServiceB(inject(ServiceA), inject(INJECTOR, undefined, InjectFlags.SkipSelf));
|
||||
return new ServiceB(inject(ServiceA), inject(INJECTOR, InjectFlags.SkipSelf) !);
|
||||
},
|
||||
});
|
||||
// /NORMATIVE
|
||||
|
|
|
@ -71,7 +71,7 @@ xdescribe('NgModule', () => {
|
|||
static ngInjectableDef = defineInjectable({
|
||||
providedIn: MyModule,
|
||||
factory: () => new BurntToast(
|
||||
$r3$.ɵdirectiveInject(Toast, undefined, $core$.InjectFlags.Optional),
|
||||
$r3$.ɵdirectiveInject(Toast, $core$.InjectFlags.Optional),
|
||||
$r3$.ɵdirectiveInject(String)),
|
||||
});
|
||||
// /NORMATIVE
|
||||
|
|
|
@ -7,15 +7,15 @@
|
|||
*/
|
||||
|
||||
|
||||
import {DoCheck, ViewEncapsulation} from '../../src/core';
|
||||
import {ComponentFactory, DoCheck, ViewEncapsulation, createInjector, defineInjectable, defineInjector} from '../../src/core';
|
||||
import {getRenderedText} from '../../src/render3/component';
|
||||
import {LifecycleHooksFeature, defineComponent, markDirty} from '../../src/render3/index';
|
||||
import {LifecycleHooksFeature, defineComponent, directiveInject, markDirty} from '../../src/render3/index';
|
||||
import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, text, textBinding, tick} from '../../src/render3/instructions';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {createRendererType2} from '../../src/view/index';
|
||||
|
||||
import {getRendererFactory2} from './imported_renderer2';
|
||||
import {containerEl, renderComponent, renderToHtml, requestAnimationFrame, toHtml} from './render_util';
|
||||
import {ComponentFixture, containerEl, renderComponent, renderToHtml, requestAnimationFrame, toHtml} from './render_util';
|
||||
|
||||
describe('component', () => {
|
||||
class CounterComponent {
|
||||
|
@ -60,6 +60,45 @@ describe('component', () => {
|
|||
expect(toHtml(containerEl)).toEqual('124');
|
||||
});
|
||||
|
||||
class MyService {
|
||||
constructor(public value: string) {}
|
||||
static ngInjectableDef =
|
||||
defineInjectable({providedIn: 'root', factory: () => new MyService('no-injector')});
|
||||
}
|
||||
class MyComponent {
|
||||
constructor(public myService: MyService) {}
|
||||
static ngComponentDef = defineComponent({
|
||||
type: MyComponent,
|
||||
selectors: [['my-component']],
|
||||
factory: () => new MyComponent(directiveInject(MyService)),
|
||||
template: function(fs: RenderFlags, ctx: MyComponent) {
|
||||
if (fs & RenderFlags.Create) {
|
||||
text(0);
|
||||
}
|
||||
if (fs & RenderFlags.Update) {
|
||||
textBinding(0, bind(ctx.myService.value));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class MyModule {
|
||||
static ngInjectorDef = defineInjector({
|
||||
factory: () => new MyModule(),
|
||||
providers: [{provide: MyService, useValue: new MyService('injector')}]
|
||||
});
|
||||
}
|
||||
|
||||
it('should support bootstrapping without injector', () => {
|
||||
const fixture = new ComponentFixture(MyComponent);
|
||||
expect(fixture.html).toEqual('no-injector');
|
||||
});
|
||||
|
||||
it('should support bootstrapping with injector', () => {
|
||||
const fixture = new ComponentFixture(MyComponent, {injector: createInjector(MyModule)});
|
||||
expect(fixture.html).toEqual('injector');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef, ElementRef, InjectFlags, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
import {ChangeDetectorRef, ElementRef, InjectFlags, TemplateRef, ViewContainerRef, defineInjectable} from '@angular/core';
|
||||
import {RenderFlags} from '@angular/core/src/render3/interfaces/definition';
|
||||
|
||||
import {defineComponent} from '../../src/render3/definition';
|
||||
|
@ -397,6 +397,35 @@ describe('di', () => {
|
|||
expect(log).toEqual(['DirB', 'DirB', 'DirA (dep: DirB - 2)']);
|
||||
});
|
||||
|
||||
it('should create instance even when no injector present', () => {
|
||||
class MyService {
|
||||
value = 'MyService';
|
||||
static ngInjectableDef =
|
||||
defineInjectable({providedIn: 'root', factory: () => new MyService()});
|
||||
}
|
||||
|
||||
class MyComponent {
|
||||
constructor(public myService: MyService) {}
|
||||
static ngComponentDef = defineComponent({
|
||||
type: MyComponent,
|
||||
selectors: [['my-component']],
|
||||
factory: () => new MyComponent(directiveInject(MyService)),
|
||||
template: function(rf: RenderFlags, ctx: MyComponent) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
text(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
textBinding(0, bind(ctx.myService.value));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(MyComponent);
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('MyService');
|
||||
});
|
||||
|
||||
it('should throw if directive is not found', () => {
|
||||
class Dir {
|
||||
constructor(siblingDir: OtherDir) {}
|
||||
|
@ -426,8 +455,7 @@ describe('di', () => {
|
|||
}
|
||||
}, [Dir, OtherDir]);
|
||||
|
||||
expect(() => new ComponentFixture(App))
|
||||
.toThrowError(/ElementInjector: NotFound \[OtherDir\]/);
|
||||
expect(() => new ComponentFixture(App)).toThrowError(/Injector: NOT_FOUND \[OtherDir\]/);
|
||||
});
|
||||
|
||||
it('should throw if directives try to inject each other', () => {
|
||||
|
@ -1010,24 +1038,6 @@ describe('di', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('flags', () => {
|
||||
it('should return defaultValue not found', () => {
|
||||
class MyApp {
|
||||
constructor(public value: string) {}
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
type: MyApp,
|
||||
selectors: [['my-app']],
|
||||
factory: () => new MyApp(
|
||||
directiveInject(String as any, 'DefaultValue', InjectFlags.Default)),
|
||||
template: () => null
|
||||
});
|
||||
}
|
||||
const myApp = renderComponent(MyApp);
|
||||
expect(myApp.value).toEqual('DefaultValue');
|
||||
});
|
||||
});
|
||||
|
||||
it('should inject from parent view', () => {
|
||||
const ParentDirective = createDirective('parentDir');
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import {stringifyElement} from '@angular/platform-browser/testing/src/browser_util';
|
||||
|
||||
import {Injector} from '../../src/di/injector';
|
||||
import {CreateComponentOptions} from '../../src/render3/component';
|
||||
import {extractDirectiveDef, extractPipeDef} from '../../src/render3/definition';
|
||||
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, PublicFeature, RenderFlags, defineComponent, defineDirective, renderComponent as _renderComponent, tick} from '../../src/render3/index';
|
||||
|
@ -93,7 +94,7 @@ export class ComponentFixture<T> extends BaseFixture {
|
|||
component: T;
|
||||
requestAnimationFrame: {(fn: () => void): void; flush(): void; queue: (() => void)[];};
|
||||
|
||||
constructor(private componentType: ComponentType<T>) {
|
||||
constructor(private componentType: ComponentType<T>, opts: {injector?: Injector} = {}) {
|
||||
super();
|
||||
this.requestAnimationFrame = function(fn: () => void) {
|
||||
requestAnimationFrame.queue.push(fn);
|
||||
|
@ -105,10 +106,9 @@ export class ComponentFixture<T> extends BaseFixture {
|
|||
}
|
||||
};
|
||||
|
||||
this.component = _renderComponent(componentType, {
|
||||
host: this.hostElement,
|
||||
scheduler: this.requestAnimationFrame,
|
||||
});
|
||||
this.component = _renderComponent(
|
||||
componentType,
|
||||
{host: this.hostElement, scheduler: this.requestAnimationFrame, injector: opts.injector});
|
||||
}
|
||||
|
||||
update(): void {
|
||||
|
|
|
@ -59,7 +59,7 @@ class HasOptionalDep {
|
|||
constructor(public baz: Baz|null) {}
|
||||
|
||||
static ngInjectableDef: InjectableDef<HasOptionalDep> = defineInjectable({
|
||||
factory: () => new HasOptionalDep(inject(Baz, null)),
|
||||
factory: () => new HasOptionalDep(inject(Baz, InjectFlags.Optional)),
|
||||
providedIn: MyModule,
|
||||
});
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ class ChildDep {
|
|||
class FromChildWithOptionalDep {
|
||||
constructor(public baz: Baz|null) {}
|
||||
static ngInjectableDef: InjectableDef<FromChildWithOptionalDep> = defineInjectable({
|
||||
factory: () => new FromChildWithOptionalDep(inject(Baz, null, InjectFlags.Default)),
|
||||
factory: () => new FromChildWithOptionalDep(inject(Baz, InjectFlags.Default)),
|
||||
providedIn: MyChildModule,
|
||||
});
|
||||
}
|
||||
|
@ -83,7 +83,8 @@ class FromChildWithSkipSelfDep {
|
|||
constructor(public depFromParent: ChildDep|null, public depFromChild: Bar|null) {}
|
||||
static ngInjectableDef: InjectableDef<FromChildWithSkipSelfDep> = defineInjectable({
|
||||
factory: () => new FromChildWithSkipSelfDep(
|
||||
inject(ChildDep, null, InjectFlags.SkipSelf), inject(Bar, null, InjectFlags.Self)),
|
||||
inject(ChildDep, InjectFlags.SkipSelf|InjectFlags.Optional),
|
||||
inject(Bar, InjectFlags.Self|InjectFlags.Optional)),
|
||||
providedIn: MyChildModule,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -242,7 +242,7 @@ export declare class DefaultIterableDiffer<V> implements IterableDiffer<V>, Iter
|
|||
|
||||
/** @experimental */
|
||||
export declare function defineInjectable<T>(opts: {
|
||||
providedIn?: Type<any> | 'root' | null;
|
||||
providedIn?: Type<any> | 'root' | 'any' | null;
|
||||
factory: () => T;
|
||||
}): InjectableDef<T>;
|
||||
|
||||
|
@ -336,9 +336,8 @@ export interface HostDecorator {
|
|||
export declare const HostListener: HostListenerDecorator;
|
||||
|
||||
/** @experimental */
|
||||
export declare function inject<T>(token: Type<T> | InjectionToken<T>, notFoundValue?: undefined, flags?: InjectFlags): T;
|
||||
export declare function inject<T>(token: Type<T> | InjectionToken<T>, notFoundValue: T, flags?: InjectFlags): T;
|
||||
export declare function inject<T>(token: Type<T> | InjectionToken<T>, notFoundValue: null, flags?: InjectFlags): T | null;
|
||||
export declare function inject<T>(token: Type<T> | InjectionToken<T>): T;
|
||||
export declare function inject<T>(token: Type<T> | InjectionToken<T>, flags?: InjectFlags): T | null;
|
||||
|
||||
export declare const Inject: InjectDecorator;
|
||||
|
||||
|
@ -359,6 +358,7 @@ export interface InjectableDecorator {
|
|||
export interface InjectableDef<T> {
|
||||
factory: () => T;
|
||||
providedIn: InjectorType<any> | 'root' | 'any' | null;
|
||||
value: T | undefined;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
|
@ -462,6 +462,7 @@ export declare class IterableDiffers {
|
|||
/** @deprecated */ factories: IterableDifferFactory[];
|
||||
constructor(factories: IterableDifferFactory[]);
|
||||
find(iterable: any): IterableDifferFactory;
|
||||
static ngInjectableDef: InjectableDef<IterableDiffers>;
|
||||
static create(factories: IterableDifferFactory[], parent?: IterableDiffers): IterableDiffers;
|
||||
static extend(factories: IterableDifferFactory[]): StaticProvider;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue