diff --git a/packages/compiler-cli/test/ngc_spec.ts b/packages/compiler-cli/test/ngc_spec.ts index 9c929839e4..14845f2c07 100644 --- a/packages/compiler-cli/test/ngc_spec.ts +++ b/packages/compiler-cli/test/ngc_spec.ts @@ -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', () => { diff --git a/packages/compiler/src/injectable_compiler.ts b/packages/compiler/src/injectable_compiler.ts index 8677a091fb..8d07ed0fbd 100644 --- a/packages/compiler/src/injectable_compiler.ts +++ b/packages/compiler/src/injectable_compiler.ts @@ -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]; } diff --git a/packages/compiler/src/render3/r3_view_compiler.ts b/packages/compiler/src/render3/r3_view_compiler.ts index 5941149a4a..8315c9292f 100644 --- a/packages/compiler/src/render3/r3_view_compiler.ts +++ b/packages/compiler/src/render3/r3_view_compiler.ts @@ -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)); } diff --git a/packages/compiler/test/render3/r3_view_compiler_di_spec.ts b/packages/compiler/test/render3/r3_view_compiler_di_spec.ts index ecb6a3aa73..11912e0ee6 100644 --- a/packages/compiler/test/render3/r3_view_compiler_di_spec.ts +++ b/packages/compiler/test/render3/r3_view_compiler_di_spec.ts @@ -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) ); }`; diff --git a/packages/core/src/change_detection/differs/iterable_differs.ts b/packages/core/src/change_detection/differs/iterable_differs.ts index 09f988d0a1..912bfe7b7c 100644 --- a/packages/core/src/change_detection/differs/iterable_differs.ts +++ b/packages/core/src/change_detection/differs/iterable_differs.ts @@ -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 = defineInjectable({ + providedIn: 'root', + factory: () => new IterableDiffers([new DefaultIterableDifferFactory()]) + }); + /** * @deprecated v4.0.0 - Should be private */ diff --git a/packages/core/src/core_private_export.ts b/packages/core/src/core_private_export.ts index 82e56380ad..7b6101355e 100644 --- a/packages/core/src/core_private_export.ts +++ b/packages/core/src/core_private_export.ts @@ -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'; diff --git a/packages/core/src/di/defs.ts b/packages/core/src/di/defs.ts index 97dd574f4f..9cfa828cbd 100644 --- a/packages/core/src/di/defs.ts +++ b/packages/core/src/di/defs.ts @@ -25,8 +25,25 @@ import {ClassProvider, ClassSansProvider, ConstructorProvider, ConstructorSansPr * @experimental */ export interface InjectableDef { + /** + * 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|'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 { * @experimental */ export function defineInjectable(opts: { - providedIn?: Type| 'root' | null, + providedIn?: Type| 'root' | 'any' | null, factory: () => T, }): InjectableDef { return { - providedIn: (opts.providedIn as InjectorType| 'root' | null | undefined) || null, + providedIn: opts.providedIn as any || null, factory: opts.factory, + value: undefined, }; } diff --git a/packages/core/src/di/injector.ts b/packages/core/src/di/injector.ts index bb8a54f4ff..87d829e2ba 100644 --- a/packages/core/src/di/injector.ts +++ b/packages/core/src/di/injector.ts @@ -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( - token: Type| InjectionToken, notFoundValue?: undefined, flags?: InjectFlags): T; -export function inject( - token: Type| InjectionToken, notFoundValue: T, flags?: InjectFlags): T; -export function inject( - token: Type| InjectionToken, notFoundValue: null, flags?: InjectFlags): T|null; -export function inject( - token: Type| InjectionToken, notFoundValue?: T | null, flags = InjectFlags.Default): T| - null { - if (_currentInjector === null) { +export function inject(token: Type| InjectionToken): T; +export function inject(token: Type| InjectionToken, flags?: InjectFlags): T|null; +export function inject(token: Type| InjectionToken, 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 = (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| InjectionToken| any[])[]): any[] { @@ -474,13 +482,12 @@ export function injectArgs(types: (Type| InjectionToken| any[])[]): an throw new Error('Arguments array must have arguments.'); } let type: Type|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| InjectionToken| any[])[]): an } } - args.push(inject(type !, defaultValue, InjectFlags.Default)); + args.push(inject(type !, flags)); } else { args.push(inject(arg)); } diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index 13c579b458..c4f53ac9a9 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -132,10 +132,11 @@ export function renderComponent( 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; diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index fdddb70070..c1c76c53fa 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -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): void { * @param flags Injection flags (e.g. CheckParent) * @returns The instance found */ -export function directiveInject( - token: Type, notFoundValue?: undefined, flags?: InjectFlags): T; -export function directiveInject(token: Type, notFoundValue: T, flags?: InjectFlags): T; -export function directiveInject(token: Type, notFoundValue: null, flags?: InjectFlags): T| - null; -export function directiveInject( - token: Type, notFoundValue?: T | null, flags = InjectFlags.Default): T|null { - return getOrCreateInjectable(getOrCreateNodeInjector(), token, flags, notFoundValue); +export function directiveInject(token: Type): T; +export function directiveInject(token: Type, flags?: InjectFlags): T|null; +export function directiveInject(token: Type, flags = InjectFlags.Default): T|null { + return getOrCreateInjectable(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( - di: LInjector, token: Type, flags?: InjectFlags, defaultValue?: T | null): T|null { +export function getOrCreateInjectable(di: LInjector, token: Type, 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( // 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(node: LNode, token: any): T|null { diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 38844d7069..f8ce9c69ec 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -301,6 +301,7 @@ export function createLView( dynamicViewCount: 0, lifecycleStage: LifecycleStage.Init, queries: null, + injector: currentView && currentView.injector, }; return newView; diff --git a/packages/core/src/render3/interfaces/injector.ts b/packages/core/src/render3/interfaces/injector.ts index f321287cc4..400e38eaf4 100644 --- a/packages/core/src/render3/interfaces/injector.ts +++ b/packages/core/src/render3/interfaces/injector.ts @@ -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|null; diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index db3d2b0147..24742998ee 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -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) */ diff --git a/packages/core/test/render3/common_with_def.ts b/packages/core/test/render3/common_with_def.ts index 170f70b644..41975dad0e 100644 --- a/packages/core/test/render3/common_with_def.ts +++ b/packages/core/test/render3/common_with_def.ts @@ -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', diff --git a/packages/core/test/render3/compiler_canonical/injection_spec.ts b/packages/core/test/render3/compiler_canonical/injection_spec.ts index 4c08f69e9e..3c61d4901c 100644 --- a/packages/core/test/render3/compiler_canonical/injection_spec.ts +++ b/packages/core/test/render3/compiler_canonical/injection_spec.ts @@ -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 diff --git a/packages/core/test/render3/compiler_canonical/ng_module_spec.ts b/packages/core/test/render3/compiler_canonical/ng_module_spec.ts index 434430face..b585b7707e 100644 --- a/packages/core/test/render3/compiler_canonical/ng_module_spec.ts +++ b/packages/core/test/render3/compiler_canonical/ng_module_spec.ts @@ -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 diff --git a/packages/core/test/render3/component_spec.ts b/packages/core/test/render3/component_spec.ts index 0194667b3a..4b54a7e3ea 100644 --- a/packages/core/test/render3/component_spec.ts +++ b/packages/core/test/render3/component_spec.ts @@ -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'); + }); + }); }); diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 5ebe58bfa0..520668381c 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -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'); diff --git a/packages/core/test/render3/render_util.ts b/packages/core/test/render3/render_util.ts index 09d848aa9f..9e523f86bd 100644 --- a/packages/core/test/render3/render_util.ts +++ b/packages/core/test/render3/render_util.ts @@ -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 extends BaseFixture { component: T; requestAnimationFrame: {(fn: () => void): void; flush(): void; queue: (() => void)[];}; - constructor(private componentType: ComponentType) { + constructor(private componentType: ComponentType, opts: {injector?: Injector} = {}) { super(); this.requestAnimationFrame = function(fn: () => void) { requestAnimationFrame.queue.push(fn); @@ -105,10 +106,9 @@ export class ComponentFixture 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 { diff --git a/packages/core/test/view/ng_module_spec.ts b/packages/core/test/view/ng_module_spec.ts index 763f68363a..4e89b95ae0 100644 --- a/packages/core/test/view/ng_module_spec.ts +++ b/packages/core/test/view/ng_module_spec.ts @@ -59,7 +59,7 @@ class HasOptionalDep { constructor(public baz: Baz|null) {} static ngInjectableDef: InjectableDef = 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 = 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 = 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, }); } diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index ff83be39e8..40a2081fbd 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -242,7 +242,7 @@ export declare class DefaultIterableDiffer implements IterableDiffer, Iter /** @experimental */ export declare function defineInjectable(opts: { - providedIn?: Type | 'root' | null; + providedIn?: Type | 'root' | 'any' | null; factory: () => T; }): InjectableDef; @@ -336,9 +336,8 @@ export interface HostDecorator { export declare const HostListener: HostListenerDecorator; /** @experimental */ -export declare function inject(token: Type | InjectionToken, notFoundValue?: undefined, flags?: InjectFlags): T; -export declare function inject(token: Type | InjectionToken, notFoundValue: T, flags?: InjectFlags): T; -export declare function inject(token: Type | InjectionToken, notFoundValue: null, flags?: InjectFlags): T | null; +export declare function inject(token: Type | InjectionToken): T; +export declare function inject(token: Type | InjectionToken, flags?: InjectFlags): T | null; export declare const Inject: InjectDecorator; @@ -359,6 +358,7 @@ export interface InjectableDecorator { export interface InjectableDef { factory: () => T; providedIn: InjectorType | '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; static create(factories: IterableDifferFactory[], parent?: IterableDiffers): IterableDiffers; static extend(factories: IterableDifferFactory[]): StaticProvider; }