/** * @license * Copyright Google LLC 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 {Identifiers} from './identifiers'; import * as o from './output/output_ast'; import {compileFactoryFunction, R3DependencyMetadata, R3FactoryDelegateType, R3FactoryMetadata, R3FactoryTarget} from './render3/r3_factory'; import {mapToMapExpression, R3Reference, typeWithParameters} from './render3/util'; export interface InjectableDef { expression: o.Expression; type: o.Type; statements: o.Statement[]; } export interface R3InjectableMetadata { name: string; type: R3Reference; internalType: o.Expression; typeArgumentCount: number; providedIn: o.Expression; useClass?: o.Expression; useFactory?: o.Expression; useExisting?: o.Expression; useValue?: o.Expression; userDeps?: R3DependencyMetadata[]; } export function compileInjectable(meta: R3InjectableMetadata): InjectableDef { let result: {factory: o.Expression, statements: o.Statement[]}|null = null; const factoryMeta: R3FactoryMetadata = { name: meta.name, type: meta.type, internalType: meta.internalType, typeArgumentCount: meta.typeArgumentCount, deps: [], injectFn: Identifiers.inject, target: R3FactoryTarget.Injectable, }; if (meta.useClass !== undefined) { // meta.useClass has two modes of operation. Either deps are specified, in which case `new` is // used to instantiate the class with dependencies injected, or deps are not specified and // the factory of the class is used to instantiate it. // // A special case exists for useClass: Type where Type is the injectable type itself and no // deps are specified, in which case 'useClass' is effectively ignored. const useClassOnSelf = meta.useClass.isEquivalent(meta.internalType); let deps: R3DependencyMetadata[]|undefined = undefined; if (meta.userDeps !== undefined) { deps = meta.userDeps; } if (deps !== undefined) { // factory: () => new meta.useClass(...deps) result = compileFactoryFunction({ ...factoryMeta, delegate: meta.useClass, delegateDeps: deps, delegateType: R3FactoryDelegateType.Class, }); } else if (useClassOnSelf) { result = compileFactoryFunction(factoryMeta); } else { result = delegateToFactory( meta.type.value as o.WrappedNodeExpr, meta.useClass as o.WrappedNodeExpr); } } else if (meta.useFactory !== undefined) { if (meta.userDeps !== undefined) { result = compileFactoryFunction({ ...factoryMeta, delegate: meta.useFactory, delegateDeps: meta.userDeps || [], delegateType: R3FactoryDelegateType.Function, }); } else { result = { statements: [], factory: o.fn([], [new o.ReturnStatement(meta.useFactory.callFn([]))]) }; } } else if (meta.useValue !== undefined) { // Note: it's safe to use `meta.useValue` instead of the `USE_VALUE in meta` check used for // client code because meta.useValue is an Expression which will be defined even if the actual // value is undefined. result = compileFactoryFunction({ ...factoryMeta, expression: meta.useValue, }); } else if (meta.useExisting !== undefined) { // useExisting is an `inject` call on the existing token. result = compileFactoryFunction({ ...factoryMeta, expression: o.importExpr(Identifiers.inject).callFn([meta.useExisting]), }); } else { result = delegateToFactory( meta.type.value as o.WrappedNodeExpr, meta.internalType as o.WrappedNodeExpr); } const token = meta.internalType; const injectableProps: {[key: string]: o.Expression} = {token, factory: result.factory}; // Only generate providedIn property if it has a non-null value if ((meta.providedIn as o.LiteralExpr).value !== null) { injectableProps.providedIn = meta.providedIn; } const expression = o.importExpr(Identifiers.ɵɵdefineInjectable) .callFn([mapToMapExpression(injectableProps)], undefined, true); const type = new o.ExpressionType(o.importExpr( Identifiers.InjectableDef, [typeWithParameters(meta.type.type, meta.typeArgumentCount)])); return { expression, type, statements: result.statements, }; } function delegateToFactory(type: o.WrappedNodeExpr, internalType: o.WrappedNodeExpr) { return { statements: [], // If types are the same, we can generate `factory: type.ɵfac` // If types are different, we have to generate a wrapper function to ensure // the internal type has been resolved (`factory: function(t) { return type.ɵfac(t); }`) factory: type.node === internalType.node ? internalType.prop('ɵfac') : o.fn([new o.FnParam('t', o.DYNAMIC_TYPE)], [new o.ReturnStatement(internalType.callMethod( 'ɵfac', [o.variable('t')]))]) }; }