diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts index ee0ad85820d..63b88bfd805 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts @@ -280,10 +280,10 @@ function getDep(dep: ts.Expression, reflector: ReflectionHost): R3DependencyMeta }; function maybeUpdateDecorator( - dec: ts.Identifier, reflector: ReflectionHost, token?: ts.Expression): void { + dec: ts.Identifier, reflector: ReflectionHost, token?: ts.Expression): boolean { const source = reflector.getImportOfIdentifier(dec); if (source === null || source.from !== '@angular/core') { - return; + return false; } switch (source.name) { case 'Inject': @@ -300,16 +300,23 @@ function getDep(dep: ts.Expression, reflector: ReflectionHost): R3DependencyMeta case 'Self': meta.self = true; break; + default: + return false; } + return true; } if (ts.isArrayLiteralExpression(dep)) { dep.elements.forEach(el => { + let isDecorator = false; if (ts.isIdentifier(el)) { - maybeUpdateDecorator(el, reflector); + isDecorator = maybeUpdateDecorator(el, reflector); } else if (ts.isNewExpression(el) && ts.isIdentifier(el.expression)) { const token = el.arguments && el.arguments.length > 0 && el.arguments[0] || undefined; - maybeUpdateDecorator(el.expression, reflector, token); + isDecorator = maybeUpdateDecorator(el.expression, reflector, token); + } + if (!isDecorator) { + meta.token = new WrappedNodeExpr(el); } }); } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/GOLDEN_PARTIAL.js index 5b81a8c9f30..2ba9b1d8fda 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/GOLDEN_PARTIAL.js @@ -162,19 +162,24 @@ export declare class MyService { /**************************************************************************************************** * PARTIAL FILE: usefactory_with_deps.js ****************************************************************************************************/ -import { Injectable } from '@angular/core'; +import { Injectable, Optional } from '@angular/core'; import * as i0 from "@angular/core"; class SomeDep { } class MyAlternateService { + constructor(dep, optional) { } } export class MyService { } MyService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); -MyService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, providedIn: 'root', useFactory: () => new MyAlternateService(), deps: [{ token: SomeDep }] }); +MyService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, providedIn: 'root', useFactory: (dep, optional) => new MyAlternateService(dep, optional), deps: [{ token: SomeDep }, { token: SomeDep, optional: true }] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, decorators: [{ type: Injectable, - args: [{ providedIn: 'root', useFactory: () => new MyAlternateService(), deps: [SomeDep] }] + args: [{ + providedIn: 'root', + useFactory: (dep, optional) => new MyAlternateService(dep, optional), + deps: [SomeDep, [new Optional(), SomeDep]] + }] }] }); /**************************************************************************************************** diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/usefactory_with_deps.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/usefactory_with_deps.js index b4a46b95c74..938a0b8013c 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/usefactory_with_deps.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/usefactory_with_deps.js @@ -5,7 +5,7 @@ MyService.ɵprov = /*@__PURE__*/ $r3$.ɵɵdefineInjectable({ if (t) { r = new t(); } else { - r = (() => new MyAlternateService())($r3$.ɵɵinject(SomeDep)); + r = ((dep, optional) => new MyAlternateService(dep, optional))($r3$.ɵɵinject(SomeDep), $r3$.ɵɵinject(SomeDep, 8)); } return r; }, diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/usefactory_with_deps.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/usefactory_with_deps.ts index c4821e3e6b5..06dc8d1dd6b 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/usefactory_with_deps.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/usefactory_with_deps.ts @@ -1,8 +1,14 @@ -import {Injectable} from '@angular/core'; +import {Injectable, Optional} from '@angular/core'; class SomeDep {} -class MyAlternateService {} +class MyAlternateService { + constructor(dep: SomeDep, optional: SomeDep|null) {} +} -@Injectable({providedIn: 'root', useFactory: () => new MyAlternateService(), deps: [SomeDep]}) +@Injectable({ + providedIn: 'root', + useFactory: (dep: SomeDep, optional: SomeDep|null) => new MyAlternateService(dep, optional), + deps: [SomeDep, [new Optional(), SomeDep]] +}) export class MyService { } diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 64cd47c6a3a..48fdac38357 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -202,6 +202,40 @@ function allTests(os: string) { expect(dtsContents).toContain('static ɵfac: i0.ɵɵFactoryDeclaration;'); }); + it('should compile Injectables with providedIn and factory with deps with array literal tokens', + () => { + env.write('test.ts', ` + import {Injectable, Optional, Self} from '@angular/core'; + + @Injectable() + export class Dep {} + + @Injectable({ + providedIn: 'root', + useFactory: (dep: Dep) => new Service(dep), + deps: [[new Optional(), new Self(), Dep]], + }) + export class Service { + constructor(dep: Dep) {} + } + `); + + env.driveMain(); + + const jsContents = env.getContents('test.js'); + expect(jsContents).toContain('Service.ɵprov ='); + expect(jsContents) + .toContain('factory: function Service_Factory(t) { var r = null; if (t) {'); + expect(jsContents).toContain('return new (t || Service)(i0.ɵɵinject(Dep));'); + expect(jsContents) + .toContain('r = (function (dep) { return new Service(dep); })(i0.ɵɵinject(Dep, 10));'); + expect(jsContents).toContain(`return r; }, providedIn: 'root' });`); + expect(jsContents).not.toContain('__decorate'); + const dtsContents = env.getContents('test.d.ts'); + expect(dtsContents).toContain('static ɵprov: i0.ɵɵInjectableDeclaration;'); + expect(dtsContents).toContain('static ɵfac: i0.ɵɵFactoryDeclaration;'); + }); + it('should compile Injectables with providedIn using forwardRef without errors', () => { env.write('test.ts', ` import {Injectable, NgModule, forwardRef} from '@angular/core';