From ff13a1f08540aa9fc5d80f7fa150467fe6490399 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 12 Apr 2018 12:30:21 -0700 Subject: [PATCH 1/4] feat(ivy): support generation of flags for directive injection This change changes: - compiler uses `directiveInject` instead of `inject` for `Directive`s - unifies the flags in `di` as well as `render3` - changes the signature of `directiveInject` to match `inject` In prep for #23330 - compiler now generates flags for injection. Compiler portion of #23342 Prep for #23330 --- packages/compiler-cli/test/ngc_spec.ts | 2 +- packages/compiler/src/core.ts | 15 +++- .../compiler/src/render3/r3_identifiers.ts | 4 +- .../compiler/src/render3/r3_view_compiler.ts | 43 +++++++++--- .../test/render3/r3_view_compiler_di_spec.ts | 70 +++++++++++++++++++ .../core/src/core_render3_private_export.ts | 1 - packages/core/src/di/injector.ts | 13 ++-- packages/core/src/render3/di.ts | 27 +++---- packages/core/src/render3/index.ts | 3 +- packages/core/test/render3/common_with_def.ts | 6 +- .../compiler_canonical/ng_module_spec.ts | 4 +- packages/core/test/render3/di_spec.ts | 6 +- tools/public_api_guard/core/core.d.ts | 4 +- 13 files changed, 152 insertions(+), 46 deletions(-) create mode 100644 packages/compiler/test/render3/r3_view_compiler_di_spec.ts diff --git a/packages/compiler-cli/test/ngc_spec.ts b/packages/compiler-cli/test/ngc_spec.ts index 048548cb10c89..9c929839e42a4 100644 --- a/packages/compiler-cli/test/ngc_spec.ts +++ b/packages/compiler-cli/test/ngc_spec.ts @@ -2257,7 +2257,7 @@ describe('ngc transformer command-line', () => { constructor(e: Existing) {} } `); - expect(source).toMatch(/ngInjectableDef.*return ..\(..\.inject\(Existing, undefined, 1\)/); + expect(source).toMatch(/ngInjectableDef.*return ..\(..\.inject\(Existing, undefined, 4\)/); }); it('compiles a service that depends on a token', () => { diff --git a/packages/compiler/src/core.ts b/packages/compiler/src/core.ts index f834d18ad87b5..2f4241e0c900c 100644 --- a/packages/compiler/src/core.ts +++ b/packages/compiler/src/core.ts @@ -217,14 +217,23 @@ export const enum DepFlags { Value = 1 << 3, } -/** Injection flags for DI. */ +/** + * Injection flags for DI. + */ export const enum InjectFlags { Default = 0, - /** Skip the node that is requesting injection. */ - SkipSelf = 1 << 0, + /** + * Specifies that an injector should retrieve a dependency from any injector until reaching the + * host element of the current component. (Only used with Element Injector) + */ + Host = 1 << 0, /** Don't descend into ancestors of the node requesting injection. */ Self = 1 << 1, + /** Skip the node that is requesting injection. */ + SkipSelf = 1 << 2, + /** Inject `defaultValue` instead if token not found. */ + Optional = 1 << 3, } export const enum ArgumentType {Inline = 0, Dynamic = 1} diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index cb301d7f228e2..dff47eb6f9f16 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -81,6 +81,8 @@ export class Identifiers { static directiveLifeCycle: o.ExternalReference = {name: 'ɵl', moduleName: CORE}; + static injectAttribute: o.ExternalReference = {name: 'ɵinjectAttribute', moduleName: CORE}; + static injectElementRef: o.ExternalReference = {name: 'ɵinjectElementRef', moduleName: CORE}; static injectTemplateRef: o.ExternalReference = {name: 'ɵinjectTemplateRef', moduleName: CORE}; @@ -88,7 +90,7 @@ export class Identifiers { static injectViewContainerRef: o.ExternalReference = {name: 'ɵinjectViewContainerRef', moduleName: CORE}; - static inject: o.ExternalReference = {name: 'ɵinject', moduleName: CORE}; + static directiveInject: o.ExternalReference = {name: 'ɵdirectiveInject', moduleName: CORE}; static defineComponent: o.ExternalReference = {name: 'ɵdefineComponent', moduleName: CORE}; diff --git a/packages/compiler/src/render3/r3_view_compiler.ts b/packages/compiler/src/render3/r3_view_compiler.ts index e3673754006f4..5941149a4a9ef 100644 --- a/packages/compiler/src/render3/r3_view_compiler.ts +++ b/packages/compiler/src/render3/r3_view_compiler.ts @@ -6,10 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, CompileTypeSummary, flatten, identifierName, rendererTypeName, sanitizeIdentifier, tokenReference, viewClassName} from '../compile_metadata'; +import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, CompileTypeSummary, flatten, identifierName, rendererTypeName, sanitizeIdentifier, tokenReference, viewClassName} from '../compile_metadata'; import {CompileReflector} from '../compile_reflector'; import {BindingForm, BuiltinConverter, BuiltinFunctionCall, ConvertPropertyBindingResult, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter'; import {ConstantPool, DefinitionKind} from '../constant_pool'; +import {InjectFlags} from '../core'; import {AST, AstMemoryEfficientTransformer, AstTransformer, BindingPipe, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, PropertyRead} from '../expression_parser/ast'; import {Identifiers} from '../identifiers'; import {LifecycleHooks} from '../lifecycle_reflector'; @@ -19,9 +20,11 @@ import {CssSelector} from '../selector'; import {BindingParser} from '../template_parser/binding_parser'; import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, QueryMatch, RecursiveTemplateAstVisitor, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast'; import {OutputContext, error} from '../util'; + import {Identifiers as R3} from './r3_identifiers'; import {BUILD_OPTIMIZER_COLOCATE, OutputMode} from './r3_types'; + /** Name of the context parameter passed into a template function */ const CONTEXT_NAME = 'ctx'; @@ -927,12 +930,6 @@ export function createFactory( const viewContainerRef = reflector.resolveExternalReference(Identifiers.ViewContainerRef); for (let dependency of type.diDeps) { - if (dependency.isValue) { - unsupported('value dependencies'); - } - if (dependency.isHost) { - unsupported('host dependencies'); - } const token = dependency.token; if (token) { const tokenRef = tokenReference(token); @@ -942,10 +939,18 @@ export function createFactory( args.push(o.importExpr(R3.injectTemplateRef).callFn([])); } else if (tokenRef === viewContainerRef) { args.push(o.importExpr(R3.injectViewContainerRef).callFn([])); + } else if (dependency.isAttribute) { + args.push(o.importExpr(R3.injectAttribute).callFn([o.literal(dependency.token !.value)])); } else { - const value = + const tokenValue = token.identifier != null ? outputCtx.importExpr(tokenRef) : o.literal(tokenRef); - args.push(o.importExpr(R3.inject).callFn([value])); + const directiveInjectArgs = [tokenValue]; + const flags = extractFlags(dependency); + if (flags != InjectFlags.Default) { + // Append flag information if other than default. + directiveInjectArgs.push(o.literal(undefined), o.literal(flags)); + } + args.push(o.importExpr(R3.directiveInject).callFn(directiveInjectArgs)); } } else { unsupported('dependency without a token'); @@ -979,6 +984,26 @@ export function createFactory( type.reference.name ? `${type.reference.name}_Factory` : null); } +function extractFlags(dependency: CompileDiDependencyMetadata): InjectFlags { + let flags = InjectFlags.Default; + if (dependency.isHost) { + flags |= InjectFlags.Host; + } + if (dependency.isOptional) { + flags |= InjectFlags.Optional; + } + if (dependency.isSelf) { + flags |= InjectFlags.Self; + } + if (dependency.isSkipSelf) { + flags |= InjectFlags.SkipSelf; + } + if (dependency.isValue) { + unsupported('value dependencies'); + } + return flags; +} + /** * Remove trailing null nodes as they are implied. */ diff --git a/packages/compiler/test/render3/r3_view_compiler_di_spec.ts b/packages/compiler/test/render3/r3_view_compiler_di_spec.ts new file mode 100644 index 0000000000000..ecb6a3aa73731 --- /dev/null +++ b/packages/compiler/test/render3/r3_view_compiler_di_spec.ts @@ -0,0 +1,70 @@ +/** + * @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 {MockDirectory, setup} from '../aot/test_util'; +import {compile, expectEmit} from './mock_compile'; + +describe('compiler compliance: dependency injection', () => { + const angularFiles = setup({ + compileAngular: true, + compileAnimations: false, + compileCommon: true, + }); + + it('should create factory methods', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule, Injectable, Attribute, Host, SkipSelf, Self, Optional} from '@angular/core'; + import {CommonModule} from '@angular/common'; + + @Injectable() + export class MyService {} + + @Component({ + selector: 'my-component', + template: \`\` + }) + export class MyComponent { + constructor( + @Attribute('name') name:string, + s1: MyService, + @Host() s2: MyService, + @Self() s4: MyService, + @SkipSelf() s3: MyService, + @Optional() s5: MyService, + @Self() @Optional() s6: MyService, + ) {} + } + + @NgModule({declarations: [MyComponent], imports: [CommonModule], providers: [MyService]}) + export class MyModule {} + ` + } + }; + + const factory = ` + factory: function MyComponent_Factory() { + 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) + ); + }`; + + + const result = compile(files, angularFiles); + + expectEmit(result.source, factory, 'Incorrect factory'); + }); + +}); diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 3674945ed85a9..136b4fd3e4569 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -21,7 +21,6 @@ export { injectViewContainerRef as ɵinjectViewContainerRef, injectChangeDetectorRef as ɵinjectChangeDetectorRef, injectAttribute as ɵinjectAttribute, - InjectFlags as ɵInjectFlags, PublicFeature as ɵPublicFeature, NgOnChangesFeature as ɵNgOnChangesFeature, CssSelectorList as ɵCssSelectorList, diff --git a/packages/core/src/di/injector.ts b/packages/core/src/di/injector.ts index 8bb91dd68ebd9..bb8a54f4ffa80 100644 --- a/packages/core/src/di/injector.ts +++ b/packages/core/src/di/injector.ts @@ -411,16 +411,21 @@ function getClosureSafeProperty(objWithPropertyToExtract: T): string { /** * Injection flags for DI. - * - * */ export const enum InjectFlags { Default = 0, - /** Skip the node that is requesting injection. */ - SkipSelf = 1 << 0, + /** + * Specifies that an injector should retrieve a dependency from any injector until reaching the + * host element of the current component. (Only used with Element Injector) + */ + Host = 1 << 0, /** Don't descend into ancestors of the node requesting injection. */ Self = 1 << 1, + /** Skip the node that is requesting injection. */ + SkipSelf = 1 << 2, + /** Inject `defaultValue` instead if token not found. */ + Optional = 1 << 3, } let _currentInjector: Injector|null = null; diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 4fb335c385e4e..fdddb7007075e 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 {Injector} from '../di/injector'; +import {InjectFlags, Injector} 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'; @@ -133,19 +133,6 @@ export function getOrCreateNodeInjectorForNode(node: LElementNode | LContainerNo }; } -/** Injection flags for DI. */ -export const enum InjectFlags { - /** Dependency is not required. Null will be injected if there is no provider for the dependency. - */ - Optional = 1 << 0, - /** When resolving a dependency, include the node that is requesting injection. */ - CheckSelf = 1 << 1, - /** When resolving a dependency, include ancestors of the node requesting injection. */ - CheckParent = 1 << 2, - /** Default injection options: required, checks both self and ancestors. */ - Default = CheckSelf | CheckParent, -} - /** * Constructs an injection error with the given text and token. * @@ -201,8 +188,14 @@ export function diPublic(def: DirectiveDef): void { * @param flags Injection flags (e.g. CheckParent) * @returns The instance found */ -export function directiveInject(token: Type, flags?: InjectFlags, defaultValue?: T): T { - return getOrCreateInjectable(getOrCreateNodeInjector(), token, flags, defaultValue); +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); } /** @@ -352,7 +345,7 @@ function getClosestComponentAncestor(node: LViewNode | LElementNode): LElementNo * @returns The instance found */ export function getOrCreateInjectable( - di: LInjector, token: Type, flags?: InjectFlags, defaultValue?: T): T { + di: LInjector, token: Type, flags?: InjectFlags, defaultValue?: T | null): 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 diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 44365e3773041..ea2206fa10153 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -8,10 +8,9 @@ import {LifecycleHooksFeature, createComponentRef, getHostElement, getRenderedText, renderComponent, whenRendered} from './component'; import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, definePipe} from './definition'; -import {InjectFlags} from './di'; import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType, PipeDef} from './interfaces/definition'; -export {InjectFlags, QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, directiveInject, injectAttribute, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di'; +export {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, directiveInject, injectAttribute, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di'; export {RenderFlags} from './interfaces/definition'; export {CssSelectorList} from './interfaces/projection'; diff --git a/packages/core/test/render3/common_with_def.ts b/packages/core/test/render3/common_with_def.ts index c046b617a6be6..170f70b644674 100644 --- a/packages/core/test/render3/common_with_def.ts +++ b/packages/core/test/render3/common_with_def.ts @@ -7,10 +7,10 @@ */ import {NgForOf as NgForOfDef, NgIf as NgIfDef} from '@angular/common'; -import {IterableDiffers} from '@angular/core'; +import {InjectFlags, IterableDiffers} from '@angular/core'; import {defaultIterableDiffers} from '../../src/change_detection/change_detection'; -import {DirectiveType, InjectFlags, NgOnChangesFeature, defineDirective, directiveInject, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; +import {DirectiveType, NgOnChangesFeature, defineDirective, directiveInject, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; export const NgForOf: DirectiveType> = NgForOfDef as any; export const NgIf: DirectiveType = NgIfDef as any; @@ -20,7 +20,7 @@ NgForOf.ngDirectiveDef = defineDirective({ selectors: [['', 'ngForOf', '']], factory: () => new NgForOfDef( injectViewContainerRef(), injectTemplateRef(), - directiveInject(IterableDiffers, InjectFlags.Default, defaultIterableDiffers)), + directiveInject(IterableDiffers, defaultIterableDiffers, InjectFlags.Default)), features: [NgOnChangesFeature()], inputs: { ngForOf: 'ngForOf', 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 16b60e801a1df..434430facebdb 100644 --- a/packages/core/test/render3/compiler_canonical/ng_module_spec.ts +++ b/packages/core/test/render3/compiler_canonical/ng_module_spec.ts @@ -6,10 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ +import * as $core$ from '../../../index'; import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; import * as $r3$ from '../../../src/core_render3_private_export'; import {renderComponent, toHtml} from '../render_util'; + /// See: `normative.md` xdescribe('NgModule', () => { @@ -69,7 +71,7 @@ xdescribe('NgModule', () => { static ngInjectableDef = defineInjectable({ providedIn: MyModule, factory: () => new BurntToast( - $r3$.ɵdirectiveInject(Toast, $r3$.ɵInjectFlags.Optional), + $r3$.ɵdirectiveInject(Toast, undefined, $core$.InjectFlags.Optional), $r3$.ɵdirectiveInject(String)), }); // /NORMATIVE diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index d7f6e0d49ee72..5ebe58bfa0ebb 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -6,11 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectorRef, ElementRef, TemplateRef, ViewContainerRef} from '@angular/core'; +import {ChangeDetectorRef, ElementRef, InjectFlags, TemplateRef, ViewContainerRef} from '@angular/core'; import {RenderFlags} from '@angular/core/src/render3/interfaces/definition'; import {defineComponent} from '../../src/render3/definition'; -import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di'; +import {bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di'; import {NgOnChangesFeature, PublicFeature, defineDirective, directiveInject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {LInjector} from '../../src/render3/interfaces/injector'; @@ -1019,7 +1019,7 @@ describe('di', () => { type: MyApp, selectors: [['my-app']], factory: () => new MyApp( - directiveInject(String as any, InjectFlags.Default, 'DefaultValue')), + directiveInject(String as any, 'DefaultValue', InjectFlags.Default)), template: () => null }); } diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index e1234003b7ec2..ff83be39e8c63 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -376,8 +376,10 @@ export interface InjectDecorator { export declare const enum InjectFlags { Default = 0, - SkipSelf = 1, + Host = 1, Self = 2, + SkipSelf = 4, + Optional = 8, } export declare class InjectionToken { From c869c882b4fe7b2dae819fd7ad605e69b00714fb Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 12 Apr 2018 15:54:16 -0700 Subject: [PATCH 2/4] feat(ivy): support injection even if no injector present - 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` --- packages/compiler-cli/test/ngc_spec.ts | 4 +- packages/compiler/src/injectable_compiler.ts | 7 ++- .../compiler/src/render3/r3_view_compiler.ts | 2 +- .../test/render3/r3_view_compiler_di_spec.ts | 10 ++-- .../differs/iterable_differs.ts | 7 +++ packages/core/src/core_private_export.ts | 2 +- packages/core/src/di/defs.ts | 22 +++++++- packages/core/src/di/injector.ts | 39 ++++++++------ packages/core/src/render3/component.ts | 3 +- packages/core/src/render3/di.ts | 44 +++++----------- packages/core/src/render3/instructions.ts | 1 + .../core/src/render3/interfaces/injector.ts | 3 -- packages/core/src/render3/interfaces/view.ts | 6 +++ packages/core/test/render3/common_with_def.ts | 3 +- .../compiler_canonical/injection_spec.ts | 2 +- .../compiler_canonical/ng_module_spec.ts | 2 +- packages/core/test/render3/component_spec.ts | 45 ++++++++++++++-- packages/core/test/render3/di_spec.ts | 52 +++++++++++-------- packages/core/test/render3/render_util.ts | 10 ++-- packages/core/test/view/ng_module_spec.ts | 7 +-- tools/public_api_guard/core/core.d.ts | 9 ++-- 21 files changed, 175 insertions(+), 105 deletions(-) diff --git a/packages/compiler-cli/test/ngc_spec.ts b/packages/compiler-cli/test/ngc_spec.ts index 9c929839e42a4..14845f2c0744f 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 8677a091fb3e1..8d07ed0fbd5e2 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 5941149a4a9ef..8315c9292fdff 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 ecb6a3aa73731..11912e0ee668b 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 09f988d0a1057..912bfe7b7c0cf 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 82e56380ad111..7b6101355e7e6 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 97dd574f4f98d..9cfa828cbd8ce 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 bb8a54f4ffa80..87d829e2ba911 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 13c579b4588bb..c4f53ac9a9352 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 fdddb7007075e..c1c76c53faad4 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 38844d70693c6..f8ce9c69ecdaf 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 f321287cc4b31..400e38eaf434d 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 db3d2b0147552..24742998ee625 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 170f70b644674..41975dad0e5d0 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 4c08f69e9e585..3c61d4901c619 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 434430facebdb..b585b7707e523 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 0194667b3af7d..4b54a7e3ea082 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 5ebe58bfa0ebb..520668381cf09 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 09d848aa9f5ae..9e523f86bd318 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 763f68363a717..4e89b95ae0da2 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 ff83be39e8c63..40a2081fbd65b 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; } From 8e0d74b4e072162f3465d71633a892e0505c4316 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 12 Apr 2018 16:02:33 -0700 Subject: [PATCH 3/4] docs(ivy): Clean up incorrect comments --- packages/core/src/di/r3_injector.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/core/src/di/r3_injector.ts b/packages/core/src/di/r3_injector.ts index 8601d1af7e5f1..a3d679b762766 100644 --- a/packages/core/src/di/r3_injector.ts +++ b/packages/core/src/di/r3_injector.ts @@ -64,12 +64,12 @@ interface Record { } /** - * Create a new `Injector` which is configured using `InjectorDefType`s. + * Create a new `Injector` which is configured using `InjectorType`s. * * @experimental */ export function createInjector( - defType: /* InjectorDefType */ any, parent: Injector | null = null): Injector { + defType: /* InjectorType */ any, parent: Injector | null = null): Injector { parent = parent || getNullInjector(); return new R3Injector(defType, parent); } @@ -81,7 +81,7 @@ export class R3Injector { private records = new Map|InjectionToken, Record>(); /** - * The transitive set of `InjectorDefType`s which define this injector. + * The transitive set of `InjectorType`s which define this injector. */ private injectorDefTypes = new Set>(); @@ -102,7 +102,7 @@ export class R3Injector { private destroyed = false; constructor(def: InjectorType, readonly parent: Injector) { - // Start off by creating Records for every provider declared in every InjectorDefType + // Start off by creating Records for every provider declared in every InjectorType // included transitively in `def`. deepForEach( [def], injectorDef => this.processInjectorType(injectorDef, new Set>())); @@ -114,7 +114,7 @@ export class R3Injector { // any injectable scoped to APP_ROOT_SCOPE. this.isRootInjector = this.records.has(APP_ROOT); - // Eagerly instantiate the InjectorDefType classes themselves. + // Eagerly instantiate the InjectorType classes themselves. this.injectorDefTypes.forEach(defType => this.get(defType)); } @@ -187,7 +187,7 @@ export class R3Injector { } /** - * Add an `InjectorDefType` or `InjectorDefTypeWithProviders` and all of its transitive providers + * Add an `InjectorType` or `InjectorDefTypeWithProviders` and all of its transitive providers * to this injector. */ private processInjectorType( @@ -195,7 +195,7 @@ export class R3Injector { parents: Set>) { defOrWrappedDef = resolveForwardRef(defOrWrappedDef); - // Either the defOrWrappedDef is an InjectorDefType (with ngInjectorDef) or an + // Either the defOrWrappedDef is an InjectorType (with ngInjectorDef) or an // InjectorDefTypeWithProviders (aka ModuleWithProviders). Detecting either is a megamorphic // read, so care is taken to only do the read once. @@ -206,7 +206,7 @@ export class R3Injector { const ngModule = (def == null) && (defOrWrappedDef as InjectorTypeWithProviders).ngModule || undefined; - // Determine the InjectorDefType. In the case where `defOrWrappedDef` is an `InjectorDefType`, + // Determine the InjectorType. In the case where `defOrWrappedDef` is an `InjectorType`, // then this is easy. In the case of an InjectorDefTypeWithProviders, then the definition type // is the `ngModule`. const defType: InjectorType = @@ -234,7 +234,7 @@ export class R3Injector { throw new Error(`Circular dependency: type ${stringify(defType)} ends up importing itself.`); } - // Track the InjectorDefType and add a provider for it. + // Track the InjectorType and add a provider for it. this.injectorDefTypes.add(defType); this.records.set(defType, makeRecord(def.factory)); From 7cf605d02973d9b0c00e420a22c89c03e299e9da Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 12 Apr 2018 16:03:05 -0700 Subject: [PATCH 4/4] fix(ivy): Update Todo app to take advantage of optional injector --- .../bundling/todo/bundle.golden_symbols.json | 36 +++++++++++++++++-- packages/core/test/bundling/todo/index.ts | 14 +++----- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 3b1857f9d1cae..d402f1e7d0654 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -1,4 +1,7 @@ [ + { + "name": "BLOOM_SIZE" + }, { "name": "CIRCULAR$2" }, @@ -29,6 +32,12 @@ { "name": "IterableChangeRecord_" }, + { + "name": "IterableDiffers" + }, + { + "name": "NG_ELEMENT_ID" + }, { "name": "NG_HOST_SYMBOL" }, @@ -176,6 +185,9 @@ { "name": "_c9" }, + { + "name": "_currentInjector" + }, { "name": "_devMode" }, @@ -218,6 +230,12 @@ { "name": "bindingUpdated" }, + { + "name": "bloomFindPossibleInjector" + }, + { + "name": "bloomHashBit" + }, { "name": "cacheMatchingDirectivesForNode" }, @@ -275,9 +293,6 @@ { "name": "currentView" }, - { - "name": "defaultIterableDiffers" - }, { "name": "defineComponent" }, @@ -302,6 +317,9 @@ { "name": "directiveCreate" }, + { + "name": "directiveInject" + }, { "name": "domRendererFactory3" }, @@ -380,6 +398,9 @@ { "name": "getOrCreateElementRef" }, + { + "name": "getOrCreateInjectable" + }, { "name": "getOrCreateNodeInjector" }, @@ -428,6 +449,9 @@ { "name": "initChangeDetectorIfExisting" }, + { + "name": "inject" + }, { "name": "injectTemplateRef" }, @@ -572,6 +596,12 @@ { "name": "scheduleTick" }, + { + "name": "searchMatchesQueuedForCreation" + }, + { + "name": "setCurrentInjector" + }, { "name": "setHostBindings" }, diff --git a/packages/core/test/bundling/todo/index.ts b/packages/core/test/bundling/todo/index.ts index 99c39cf831473..cbaf6b8caaab9 100644 --- a/packages/core/test/bundling/todo/index.ts +++ b/packages/core/test/bundling/todo/index.ts @@ -7,7 +7,7 @@ */ import {CommonModule, NgForOf, NgIf} from '@angular/common'; -import {ChangeDetectionStrategy, Component, EventEmitter, InjectFlags, Injectable, Input, IterableDiffers, NgModule, Output, createInjector, defineInjector, inject, ɵComponentDef as ComponentDef, ɵComponentType as ComponentType, ɵDirectiveDef as DirectiveDef, ɵDirectiveType as DirectiveType, ɵNgOnChangesFeature as NgOnChangesFeature, ɵdefaultIterableDiffers as defaultIterableDiffers, ɵdefineDirective as defineDirective, ɵinjectTemplateRef as injectTemplateRef, ɵinjectViewContainerRef as injectViewContainerRef, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent} from '@angular/core'; +import {ChangeDetectionStrategy, Component, EventEmitter, InjectFlags, Injectable, Input, IterableDiffers, NgModule, Output, createInjector, defineInjector, inject, ɵComponentDef as ComponentDef, ɵComponentType as ComponentType, ɵDirectiveDef as DirectiveDef, ɵDirectiveType as DirectiveType, ɵNgOnChangesFeature as NgOnChangesFeature, ɵdefaultIterableDiffers as defaultIterableDiffers, ɵdefineDirective as defineDirective, ɵdirectiveInject as directiveInject, ɵinjectTemplateRef as injectTemplateRef, ɵinjectViewContainerRef as injectViewContainerRef, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent} from '@angular/core'; export class Todo { @@ -23,7 +23,7 @@ export class Todo { } } -@Injectable() +@Injectable({providedIn: 'root'}) export class TodoStore { todos: Array = [ new Todo('Demonstrate Components'), @@ -108,12 +108,9 @@ export class TodoStore { // changeDetection: ChangeDetectionStrategy.OnPush }) export class ToDoAppComponent { - todoStore: TodoStore; newTodoText = ''; - // TODO(misko) Fix injection - // constructor(todoStore: TodoStore) { this.todoStore = todoStore; } - constructor() { this.todoStore = new TodoStore(); } + constructor(public todoStore: TodoStore) {} stopEditing(todo: Todo, editedTitle: string) { todo.title = editedTitle; @@ -157,10 +154,7 @@ export class ToDoAppComponent { type: NgForOf, selectors: [['', 'ngFor', '', 'ngForOf', '']], factory: () => new NgForOf( - injectViewContainerRef(), injectTemplateRef(), - // TODO(misko): inject does not work since it needs to be directiveInject - // inject(IterableDiffers, defaultIterableDiffers) - defaultIterableDiffers), + injectViewContainerRef(), injectTemplateRef(), directiveInject(IterableDiffers)), features: [NgOnChangesFeature({ ngForOf: 'ngForOf', ngForTrackBy: 'ngForTrackBy',