From 6f213a74f22b9b4188b65d8757b5693e6ec41e0c Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 12 Apr 2018 12:30:21 -0700 Subject: [PATCH] feat(ivy): support generation of flags for directive injection (#23345) 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 PR Close #23345 --- 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 048548cb10..9c929839e4 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 f834d18ad8..2f4241e0c9 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 cb301d7f22..dff47eb6f9 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 e367375400..5941149a4a 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 0000000000..ecb6a3aa73 --- /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 3674945ed8..136b4fd3e4 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 8bb91dd68e..bb8a54f4ff 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 4fb335c385..fdddb70070 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 44365e3773..ea2206fa10 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 c046b617a6..170f70b644 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 16b60e801a..434430face 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 d7f6e0d49e..5ebe58bfa0 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 e1234003b7..ff83be39e8 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 {