/** * @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 {Expression, LiteralExpr, R3DependencyMetadata, WrappedNodeExpr, compileInjectable as compileR3Injectable, jitExpression} from '@angular/compiler'; import {Injectable} from '../../di/injectable'; import {ClassSansProvider, ExistingSansProvider, FactorySansProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from '../../di/provider'; import {Type} from '../../type'; import {getClosureSafeProperty} from '../../util/property'; import {angularCoreEnv} from './environment'; import {convertDependencies, reflectDependencies} from './util'; /** * Compile an Angular injectable according to its `Injectable` metadata, and patch the resulting * `ngInjectableDef` onto the injectable type. */ export function compileInjectable(type: Type, meta?: Injectable): void { // TODO(alxhub): handle JIT of bare @Injectable(). if (!meta) { return; } let def: any = null; Object.defineProperty(type, 'ngInjectableDef', { get: () => { if (def === null) { // Check whether the injectable metadata includes a provider specification. const hasAProvider = isUseClassProvider(meta) || isUseFactoryProvider(meta) || isUseValueProvider(meta) || isUseExistingProvider(meta); let deps: R3DependencyMetadata[]|undefined = undefined; if (!hasAProvider || (isUseClassProvider(meta) && type === meta.useClass)) { deps = reflectDependencies(type); } else if (isUseClassProvider(meta)) { deps = meta.deps && convertDependencies(meta.deps); } else if (isUseFactoryProvider(meta)) { deps = meta.deps && convertDependencies(meta.deps) || []; } // Decide which flavor of factory to generate, based on the provider specified. // Only one of the use* fields should be set. let useClass: Expression|undefined = undefined; let useFactory: Expression|undefined = undefined; let useValue: Expression|undefined = undefined; let useExisting: Expression|undefined = undefined; if (!hasAProvider) { // In the case the user specifies a type provider, treat it as {provide: X, useClass: X}. // The deps will have been reflected above, causing the factory to create the class by // calling // its constructor with injected deps. useClass = new WrappedNodeExpr(type); } else if (isUseClassProvider(meta)) { // The user explicitly specified useClass, and may or may not have provided deps. useClass = new WrappedNodeExpr(meta.useClass); } else if (isUseValueProvider(meta)) { // The user explicitly specified useValue. useValue = new WrappedNodeExpr(meta.useValue); } else if (isUseFactoryProvider(meta)) { // The user explicitly specified useFactory. useFactory = new WrappedNodeExpr(meta.useFactory); } else if (isUseExistingProvider(meta)) { // The user explicitly specified useExisting. useExisting = new WrappedNodeExpr(meta.useExisting); } else { // Can't happen - either hasAProvider will be false, or one of the providers will be set. throw new Error(`Unreachable state.`); } const {expression} = compileR3Injectable({ name: type.name, type: new WrappedNodeExpr(type), providedIn: computeProvidedIn(meta.providedIn), useClass, useFactory, useValue, useExisting, deps, }); def = jitExpression(expression, angularCoreEnv, `ng://${type.name}/ngInjectableDef.js`); } return def; }, }); } function computeProvidedIn(providedIn: Type| string | null | undefined): Expression { if (providedIn == null || typeof providedIn === 'string') { return new LiteralExpr(providedIn); } else { return new WrappedNodeExpr(providedIn); } } type UseClassProvider = Injectable & ClassSansProvider & {deps?: any[]}; function isUseClassProvider(meta: Injectable): meta is UseClassProvider { return (meta as UseClassProvider).useClass !== undefined; } const GET_PROPERTY_NAME = {} as any; const USE_VALUE = getClosureSafeProperty( {provide: String, useValue: GET_PROPERTY_NAME}, GET_PROPERTY_NAME); function isUseValueProvider(meta: Injectable): meta is Injectable&ValueSansProvider { return USE_VALUE in meta; } function isUseFactoryProvider(meta: Injectable): meta is Injectable&FactorySansProvider { return (meta as FactorySansProvider).useFactory !== undefined; } function isUseExistingProvider(meta: Injectable): meta is Injectable&ExistingSansProvider { return (meta as ExistingSansProvider).useExisting !== undefined; }