diff --git a/packages/compiler-cli/src/ngcc/test/rendering/renderer_spec.ts b/packages/compiler-cli/src/ngcc/test/rendering/renderer_spec.ts index 46cf87b6e8..8b907f9278 100644 --- a/packages/compiler-cli/src/ngcc/test/rendering/renderer_spec.ts +++ b/packages/compiler-cli/src/ngcc/test/rendering/renderer_spec.ts @@ -127,7 +127,7 @@ describe('Renderer', () => { .toBe(analyzedFile.analyzedClasses[0]); expect(renderer.addDefinitions.calls.first().args[2]) .toEqual( - `A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""]], factory: function A_Factory() { return new A(); }, features: [ɵngcc0.ɵPublicFeature] });`); + `A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""]], factory: function A_Factory(t) { return new (t || A)(); }, features: [ɵngcc0.ɵPublicFeature] });`); }); it('should call removeDecorators with the source code, a map of class decorators that have been analyzed', diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts index 5d634895b6..fceb6ecfa0 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts @@ -38,7 +38,7 @@ export class InjectableDecoratorHandler implements DecoratorHandler 0) { + throw new Error(`deps not yet supported`); + } + userDeps = depsExpr.elements.map(dep => getDep(dep, reflector)); + } + if (meta.has('useValue')) { - return {name, type, providedIn, useValue: new WrappedNodeExpr(meta.get('useValue') !)}; + return { + name, + type, + ctorDeps, + providedIn, + useValue: new WrappedNodeExpr(meta.get('useValue') !) + }; } else if (meta.has('useExisting')) { - return {name, type, providedIn, useExisting: new WrappedNodeExpr(meta.get('useExisting') !)}; + return { + name, + type, + ctorDeps, + providedIn, + useExisting: new WrappedNodeExpr(meta.get('useExisting') !) + }; } else if (meta.has('useClass')) { - return {name, type, providedIn, useClass: new WrappedNodeExpr(meta.get('useClass') !)}; + return { + name, + type, + ctorDeps, + providedIn, + useClass: new WrappedNodeExpr(meta.get('useClass') !), userDeps + }; } else if (meta.has('useFactory')) { // useFactory is special - the 'deps' property must be analyzed. const factory = new WrappedNodeExpr(meta.get('useFactory') !); - const deps: R3DependencyMetadata[] = []; - if (meta.has('deps')) { - const depsExpr = meta.get('deps') !; - if (!ts.isArrayLiteralExpression(depsExpr)) { - throw new Error(`In Ivy, deps metadata must be inline.`); - } - if (depsExpr.elements.length > 0) { - throw new Error(`deps not yet supported`); - } - deps.push(...depsExpr.elements.map(dep => getDep(dep, reflector))); - } - return {name, type, providedIn, useFactory: factory, deps}; + return {name, type, providedIn, useFactory: factory, ctorDeps, userDeps}; } else { - const deps = getConstructorDependencies(clazz, reflector, isCore); - return {name, type, providedIn, deps}; + return {name, type, providedIn, ctorDeps}; } } else { throw new Error(`Too many arguments to @Injectable`); diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts index 45cc51d530..21135f9233 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts @@ -13,10 +13,17 @@ import {Decorator, ReflectionHost} from '../../host'; import {AbsoluteReference, ImportMode, Reference} from '../../metadata'; export function getConstructorDependencies( - clazz: ts.ClassDeclaration, reflector: ReflectionHost, - isCore: boolean): R3DependencyMetadata[] { + clazz: ts.ClassDeclaration, reflector: ReflectionHost, isCore: boolean): R3DependencyMetadata[]| + null { const useType: R3DependencyMetadata[] = []; - const ctorParams = reflector.getConstructorParameters(clazz) || []; + let ctorParams = reflector.getConstructorParameters(clazz); + if (ctorParams === null) { + if (reflector.hasBaseClass(clazz)) { + return null; + } else { + ctorParams = []; + } + } ctorParams.forEach((param, idx) => { let tokenExpr = param.type; let optional = false, self = false, skipSelf = false, host = false; diff --git a/packages/compiler-cli/src/ngtsc/host/src/reflection.ts b/packages/compiler-cli/src/ngtsc/host/src/reflection.ts index bfea18a9ac..16b3867fb0 100644 --- a/packages/compiler-cli/src/ngtsc/host/src/reflection.ts +++ b/packages/compiler-cli/src/ngtsc/host/src/reflection.ts @@ -338,4 +338,6 @@ export interface ReflectionHost { * Check whether the given declaration node actually represents a class. */ isClass(node: ts.Declaration): boolean; + + hasBaseClass(node: ts.Declaration): boolean; } diff --git a/packages/compiler-cli/src/ngtsc/metadata/src/reflector.ts b/packages/compiler-cli/src/ngtsc/metadata/src/reflector.ts index 5d604f8ec0..2332fe94c1 100644 --- a/packages/compiler-cli/src/ngtsc/metadata/src/reflector.ts +++ b/packages/compiler-cli/src/ngtsc/metadata/src/reflector.ts @@ -132,6 +132,11 @@ export class TypeScriptReflectionHost implements ReflectionHost { return ts.isClassDeclaration(node); } + hasBaseClass(node: ts.Declaration): boolean { + return ts.isClassDeclaration(node) && node.heritageClauses !== undefined && + node.heritageClauses.some(clause => clause.token === ts.SyntaxKind.ExtendsKeyword); + } + getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null { // Resolve the identifier to a Symbol, and return the declaration of that. let symbol: ts.Symbol|undefined = this.checker.getSymbolAtLocation(id); diff --git a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts index 19a9da5b69..429ba92b44 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts @@ -72,7 +72,7 @@ class IvyVisitor extends Visitor { node.modifiers, node.name, node.typeParameters, node.heritageClauses || [], // Map over the class members and remove any Angular decorators from them. members.map(member => this._stripAngularDecorators(member))); - return {node, before: statements}; + return {node, after: statements}; } return {node}; diff --git a/packages/compiler-cli/src/ngtsc/util/src/visitor.ts b/packages/compiler-cli/src/ngtsc/util/src/visitor.ts index 455a1d3613..2c96f9304c 100644 --- a/packages/compiler-cli/src/ngtsc/util/src/visitor.ts +++ b/packages/compiler-cli/src/ngtsc/util/src/visitor.ts @@ -14,7 +14,8 @@ import * as ts from 'typescript'; */ export type VisitListEntryResult = { node: T, - before?: B[] + before?: B[], + after?: B[], }; /** @@ -35,6 +36,11 @@ export abstract class Visitor { */ private _before = new Map(); + /** + * Maps statements to an array of statements that should be inserted after them. + */ + private _after = new Map(); + /** * Visit a class declaration, returning at least the transformed declaration and optionally other * nodes to insert before the declaration. @@ -52,6 +58,10 @@ export abstract class Visitor { // parent's _visit call is responsible for performing this insertion. this._before.set(result.node, result.before); } + if (result.after !== undefined) { + // Same with nodes that should be inserted after. + this._after.set(result.node, result.after); + } return result.node; } @@ -88,8 +98,9 @@ export abstract class Visitor { private _maybeProcessStatements}>( node: T): T { - // Shortcut - if every statement doesn't require nodes to be prepended, this is a no-op. - if (node.statements.every(stmt => !this._before.has(stmt))) { + // Shortcut - if every statement doesn't require nodes to be prepended or appended, + // this is a no-op. + if (node.statements.every(stmt => !this._before.has(stmt) && !this._after.has(stmt))) { return node; } @@ -104,6 +115,10 @@ export abstract class Visitor { this._before.delete(stmt); } newStatements.push(stmt); + if (this._after.has(stmt)) { + newStatements.push(...(this._after.get(stmt) !as ts.Statement[])); + this._after.delete(stmt); + } }); clone.statements = ts.createNodeArray(newStatements, node.statements.hasTrailingComma); return clone; diff --git a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts index 8748fbb583..15f5986888 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -42,7 +42,8 @@ describe('compiler compliance', () => { }; // The factory should look like this: - const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }'; + const factory = + 'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; // The template should look like this (where IDENT is a wild card for an identifier): const template = ` @@ -93,7 +94,8 @@ describe('compiler compliance', () => { }; // The factory should look like this: - const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }'; + const factory = + 'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; // The template should look like this (where IDENT is a wild card for an identifier): const template = ` @@ -143,7 +145,8 @@ describe('compiler compliance', () => { }; // The factory should look like this: - const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }'; + const factory = + 'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; // The template should look like this (where IDENT is a wild card for an identifier): const template = ` @@ -192,7 +195,8 @@ describe('compiler compliance', () => { }; // The factory should look like this: - const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }'; + const factory = + 'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; // The template should look like this (where IDENT is a wild card for an identifier): const template = ` @@ -238,7 +242,8 @@ describe('compiler compliance', () => { } }; - const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }'; + const factory = + 'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; const template = ` template: function MyComponent_Template(rf, ctx) { if (rf & 1) { @@ -282,7 +287,8 @@ describe('compiler compliance', () => { } }; - const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }'; + const factory = + 'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; const template = ` template: function MyComponent_Template(rf, ctx) { if (rf & 1) { @@ -327,14 +333,15 @@ describe('compiler compliance', () => { } }; - const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }'; + const factory = + 'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; const template = ` const _c0 = ["error"]; const _c1 = ["background-color"]; … MyComponent.ngComponentDef = i0.ɵdefineComponent({type:MyComponent,selectors:[["my-component"]], - factory: function MyComponent_Factory(){ - return new MyComponent(); + factory: function MyComponent_Factory(t){ + return new (t || MyComponent)(); }, features: [$r3$.ɵPublicFeature], template:function MyComponent_Template(rf,ctx){ @@ -388,7 +395,7 @@ describe('compiler compliance', () => { ChildComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: ChildComponent, selectors: [["child"]], - factory: function ChildComponent_Factory() { return new ChildComponent(); }, + factory: function ChildComponent_Factory(t) { return new (t || ChildComponent)(); }, features: [$r3$.ɵPublicFeature], template: function ChildComponent_Template(rf, ctx) { if (rf & 1) { @@ -402,7 +409,7 @@ describe('compiler compliance', () => { SomeDirective.ngDirectiveDef = $r3$.ɵdefineDirective({ type: SomeDirective, selectors: [["", "some-directive", ""]], - factory: function SomeDirective_Factory() {return new SomeDirective(); }, + factory: function SomeDirective_Factory(t) {return new (t || SomeDirective)(); }, features: [$r3$.ɵPublicFeature] }); `; @@ -414,7 +421,7 @@ describe('compiler compliance', () => { MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], - factory: function MyComponent_Factory() { return new MyComponent(); }, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, features: [$r3$.ɵPublicFeature], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { @@ -458,7 +465,7 @@ describe('compiler compliance', () => { SomeDirective.ngDirectiveDef = $r3$.ɵdefineDirective({ type: SomeDirective, selectors: [["div", "some-directive", "", 8, "foo", 3, "title", "", 9, "baz"]], - factory: function SomeDirective_Factory() {return new SomeDirective(); }, + factory: function SomeDirective_Factory(t) {return new (t || SomeDirective)(); }, features: [$r3$.ɵPublicFeature] }); `; @@ -468,7 +475,7 @@ describe('compiler compliance', () => { OtherDirective.ngDirectiveDef = $r3$.ɵdefineDirective({ type: OtherDirective, selectors: [["", 5, "span", "title", "", 9, "baz"]], - factory: function OtherDirective_Factory() {return new OtherDirective(); }, + factory: function OtherDirective_Factory(t) {return new (t || OtherDirective)(); }, features: [$r3$.ɵPublicFeature] }); `; @@ -501,7 +508,7 @@ describe('compiler compliance', () => { HostBindingDir.ngDirectiveDef = $r3$.ɵdefineDirective({ type: HostBindingDir, selectors: [["", "hostBindingDir", ""]], - factory: function HostBindingDir_Factory() { return new HostBindingDir(); }, + factory: function HostBindingDir_Factory(t) { return new (t || HostBindingDir)(); }, hostBindings: function HostBindingDir_HostBindings(dirIndex, elIndex) { $r3$.ɵp(elIndex, "id", $r3$.ɵb($r3$.ɵd(dirIndex).dirId)); }, @@ -544,7 +551,7 @@ describe('compiler compliance', () => { IfDirective.ngDirectiveDef = $r3$.ɵdefineDirective({ type: IfDirective, selectors: [["", "if", ""]], - factory: function IfDirective_Factory() { return new IfDirective($r3$.ɵinjectTemplateRef()); }, + factory: function IfDirective_Factory(t) { return new (t || IfDirective)($r3$.ɵinjectTemplateRef()); }, features: [$r3$.ɵPublicFeature] });`; const MyComponentDefinition = ` @@ -566,7 +573,7 @@ describe('compiler compliance', () => { MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], - factory: function MyComponent_Factory() { return new MyComponent(); }, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, features: [$r3$.ɵPublicFeature], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { @@ -626,7 +633,7 @@ describe('compiler compliance', () => { MyApp.ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [["my-app"]], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, features: [$r3$.ɵPublicFeature], template: function MyApp_Template(rf, ctx) { if (rf & 1) { @@ -706,7 +713,7 @@ describe('compiler compliance', () => { MyApp.ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [["my-app"]], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, features: [$r3$.ɵPublicFeature], template: function MyApp_Template(rf, ctx) { if (rf & 1) { @@ -768,7 +775,7 @@ describe('compiler compliance', () => { MyApp.ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [["my-app"]], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, features: [$r3$.ɵPublicFeature], template: function MyApp_Template(rf, ctx) { if (rf & 1) { @@ -834,7 +841,7 @@ describe('compiler compliance', () => { MyApp.ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [["my-app"]], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, features: [$r3$.ɵPublicFeature], template: function MyApp_Template(rf, ctx) { if (rf & 1) { @@ -892,7 +899,7 @@ describe('compiler compliance', () => { SimpleComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: SimpleComponent, selectors: [["simple"]], - factory: function SimpleComponent_Factory() { return new SimpleComponent(); }, + factory: function SimpleComponent_Factory(t) { return new (t || SimpleComponent)(); }, features: [$r3$.ɵPublicFeature], template: function SimpleComponent_Template(rf, ctx) { if (rf & 1) { @@ -913,7 +920,7 @@ describe('compiler compliance', () => { ComplexComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: ComplexComponent, selectors: [["complex"]], - factory: function ComplexComponent_Factory() { return new ComplexComponent(); }, + factory: function ComplexComponent_Factory(t) { return new (t || ComplexComponent)(); }, features: [$r3$.ɵPublicFeature], template: function ComplexComponent_Template(rf, ctx) { if (rf & 1) { @@ -979,7 +986,7 @@ describe('compiler compliance', () => { ViewQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: ViewQueryComponent, selectors: [["view-query-component"]], - factory: function ViewQueryComponent_Factory() { return new ViewQueryComponent(); }, + factory: function ViewQueryComponent_Factory(t) { return new (t || ViewQueryComponent)(); }, features: [$r3$.ɵPublicFeature], viewQuery: function ViewQueryComponent_Query(rf, ctx) { if (rf & 1) { @@ -1043,8 +1050,8 @@ describe('compiler compliance', () => { ContentQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: ContentQueryComponent, selectors: [["content-query-component"]], - factory: function ContentQueryComponent_Factory() { - return new ContentQueryComponent(); + factory: function ContentQueryComponent_Factory(t) { + return new (t || ContentQueryComponent)(); }, contentQueries: function ContentQueryComponent_ContentQueries() { $r3$.ɵQr($r3$.ɵQ(null, SomeDirective, true)); @@ -1119,7 +1126,7 @@ describe('compiler compliance', () => { MyPipe.ngPipeDef = $r3$.ɵdefinePipe({ name: "myPipe", type: MyPipe, - factory: function MyPipe_Factory() { return new MyPipe(); }, + factory: function MyPipe_Factory(t) { return new (t || MyPipe)(); }, pure: false }); `; @@ -1128,7 +1135,7 @@ describe('compiler compliance', () => { MyPurePipe.ngPipeDef = $r3$.ɵdefinePipe({ name: "myPurePipe", type: MyPurePipe, - factory: function MyPurePipe_Factory() { return new MyPurePipe(); }, + factory: function MyPurePipe_Factory(t) { return new (t || MyPurePipe)(); }, pure: true });`; @@ -1140,7 +1147,7 @@ describe('compiler compliance', () => { MyApp.ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [["my-app"]], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, features: [$r3$.ɵPublicFeature], template: function MyApp_Template(rf, ctx) { if (rf & 1) { @@ -1191,7 +1198,7 @@ describe('compiler compliance', () => { MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], - factory: function MyComponent_Factory() { return new MyComponent(); }, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, features: [$r3$.ɵPublicFeature], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { @@ -1283,7 +1290,7 @@ describe('compiler compliance', () => { MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], - factory: function MyComponent_Factory() { return new MyComponent(); }, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, features: [$r3$.ɵPublicFeature], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { @@ -1426,7 +1433,7 @@ describe('compiler compliance', () => { LifecycleComp.ngComponentDef = $r3$.ɵdefineComponent({ type: LifecycleComp, selectors: [["lifecycle-comp"]], - factory: function LifecycleComp_Factory() { return new LifecycleComp(); }, + factory: function LifecycleComp_Factory(t) { return new (t || LifecycleComp)(); }, inputs: {nameMin: "name"}, features: [$r3$.ɵPublicFeature, $r3$.ɵNgOnChangesFeature], template: function LifecycleComp_Template(rf, ctx) {} @@ -1436,7 +1443,7 @@ describe('compiler compliance', () => { SimpleLayout.ngComponentDef = $r3$.ɵdefineComponent({ type: SimpleLayout, selectors: [["simple-layout"]], - factory: function SimpleLayout_Factory() { return new SimpleLayout(); }, + factory: function SimpleLayout_Factory(t) { return new (t || SimpleLayout)(); }, features: [$r3$.ɵPublicFeature], template: function SimpleLayout_Template(rf, ctx) { if (rf & 1) { @@ -1542,8 +1549,8 @@ describe('compiler compliance', () => { ForOfDirective.ngDirectiveDef = $r3$.ɵdefineDirective({ type: ForOfDirective, selectors: [["", "forOf", ""]], - factory: function ForOfDirective_Factory() { - return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef()); + factory: function ForOfDirective_Factory(t) { + return new (t || ForOfDirective)($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef()); }, features: [$r3$.ɵPublicFeature, $r3$.ɵNgOnChangesFeature], inputs: {forOf: "forOf"} @@ -1564,7 +1571,7 @@ describe('compiler compliance', () => { MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], - factory: function MyComponent_Factory() { return new MyComponent(); }, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, features: [$r3$.ɵPublicFeature], template: function MyComponent_Template(rf, ctx){ if (rf & 1) { @@ -1616,8 +1623,8 @@ describe('compiler compliance', () => { ForOfDirective.ngDirectiveDef = $r3$.ɵdefineDirective({ type: ForOfDirective, selectors: [["", "forOf", ""]], - factory: function ForOfDirective_Factory() { - return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef()); + factory: function ForOfDirective_Factory(t) { + return new (t || ForOfDirective)($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef()); }, features: [$r3$.ɵPublicFeature, $r3$.ɵNgOnChangesFeature], inputs: {forOf: "forOf"} @@ -1641,7 +1648,7 @@ describe('compiler compliance', () => { MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], - factory: function MyComponent_Factory() { return new MyComponent(); }, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, features: [$r3$.ɵPublicFeature], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { @@ -1739,7 +1746,7 @@ describe('compiler compliance', () => { MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], - factory: function MyComponent_Factory() { return new MyComponent(); }, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, features: [$r3$.ɵPublicFeature], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_di_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_di_spec.ts index 1075fa1fa7..3e9acc5783 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_di_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_di_spec.ts @@ -48,8 +48,8 @@ describe('compiler compliance: dependency injection', () => { }; const factory = ` - factory: function MyComponent_Factory() { - return new MyComponent( + factory: function MyComponent_Factory(t) { + return new (t || MyComponent)( $r3$.ɵinjectAttribute('name'), $r3$.ɵdirectiveInject(MyService), $r3$.ɵdirectiveInject(MyService, 1), diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_listener_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_listener_spec.ts index 6d2b4aaffb..510e442918 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_listener_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_listener_spec.ts @@ -152,7 +152,7 @@ describe('compiler compliance: listen()', () => { MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], - factory: function MyComponent_Factory() { return new MyComponent(); }, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, features: [$r3$.ɵPublicFeature], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts index 79230243c8..b645468be7 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts @@ -90,8 +90,8 @@ describe('compiler compliance: styling', () => { MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors:[["my-component"]], - factory:function MyComponent_Factory(){ - return new MyComponent(); + factory:function MyComponent_Factory(t){ + return new (t || MyComponent)(); }, features: [$r3$.ɵPublicFeature], template: function MyComponent_Template(rf, $ctx$) { @@ -147,8 +147,8 @@ describe('compiler compliance: styling', () => { MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], - factory: function MyComponent_Factory() { - return new MyComponent(); + factory: function MyComponent_Factory(t) { + return new (t || MyComponent)(); }, features: [$r3$.ɵPublicFeature], template: function MyComponent_Template(rf, ctx) { @@ -242,8 +242,8 @@ describe('compiler compliance: styling', () => { MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors:[["my-component"]], - factory:function MyComponent_Factory(){ - return new MyComponent(); + factory:function MyComponent_Factory(t){ + return new (t || MyComponent)(); }, features: [$r3$.ɵPublicFeature], template: function MyComponent_Template(rf, $ctx$) { @@ -296,8 +296,8 @@ describe('compiler compliance: styling', () => { MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors:[["my-component"]], - factory:function MyComponent_Factory(){ - return new MyComponent(); + factory:function MyComponent_Factory(t){ + return new (t || MyComponent)(); }, features: [$r3$.ɵPublicFeature], template: function MyComponent_Template(rf, $ctx$) { diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 5ce8dea047..f963d35878 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -237,7 +237,7 @@ describe('ngtsc behavioral tests', () => { expect(jsContents) .toContain( `TestModule.ngInjectorDef = i0.defineInjector({ factory: ` + - `function TestModule_Factory() { return new TestModule(); }, providers: [{ provide: ` + + `function TestModule_Factory(t) { return new (t || TestModule)(); }, providers: [{ provide: ` + `Token, useValue: 'test' }], imports: [[OtherModule]] });`); const dtsContents = getContents('test.d.ts'); @@ -328,7 +328,7 @@ describe('ngtsc behavioral tests', () => { expect(jsContents) .toContain( 'TestPipe.ngPipeDef = i0.ɵdefinePipe({ name: "test-pipe", type: TestPipe, ' + - 'factory: function TestPipe_Factory() { return new TestPipe(); }, pure: false })'); + 'factory: function TestPipe_Factory(t) { return new (t || TestPipe)(); }, pure: false })'); expect(dtsContents).toContain('static ngPipeDef: i0.ɵPipeDef;'); }); @@ -353,7 +353,7 @@ describe('ngtsc behavioral tests', () => { expect(jsContents) .toContain( 'TestPipe.ngPipeDef = i0.ɵdefinePipe({ name: "test-pipe", type: TestPipe, ' + - 'factory: function TestPipe_Factory() { return new TestPipe(); }, pure: true })'); + 'factory: function TestPipe_Factory(t) { return new (t || TestPipe)(); }, pure: true })'); expect(dtsContents).toContain('static ngPipeDef: i0.ɵPipeDef;'); }); @@ -378,7 +378,7 @@ describe('ngtsc behavioral tests', () => { expect(exitCode).toBe(0); const jsContents = getContents('test.js'); - expect(jsContents).toContain('return new TestPipe(i0.ɵdirectiveInject(Dep));'); + expect(jsContents).toContain('return new (t || TestPipe)(i0.ɵdirectiveInject(Dep));'); }); it('should include @Pipes in @NgModule scopes', () => { @@ -474,7 +474,7 @@ describe('ngtsc behavioral tests', () => { const jsContents = getContents('test.js'); expect(jsContents) .toContain( - `factory: function FooCmp_Factory() { return new FooCmp(i0.ɵinjectAttribute("test"), i0.ɵinjectChangeDetectorRef(), i0.ɵinjectElementRef(), i0.ɵdirectiveInject(i0.INJECTOR), i0.ɵinjectTemplateRef(), i0.ɵinjectViewContainerRef()); }`); + `factory: function FooCmp_Factory(t) { return new (t || FooCmp)(i0.ɵinjectAttribute("test"), i0.ɵinjectChangeDetectorRef(), i0.ɵinjectElementRef(), i0.ɵdirectiveInject(i0.INJECTOR), i0.ɵinjectTemplateRef(), i0.ɵinjectViewContainerRef()); }`); }); it('should generate queries for components', () => { @@ -657,4 +657,42 @@ describe('ngtsc behavioral tests', () => { expect(errorSpy).not.toHaveBeenCalled(); expect(exitCode).toBe(0); }); + + it('generates inherited factory definitions', () => { + writeConfig(); + write(`test.ts`, ` + import {Injectable} from '@angular/core'; + + class Dep {} + + @Injectable() + class Base { + constructor(dep: Dep) {} + } + + @Injectable() + class Child extends Base {} + + @Injectable() + class GrandChild extends Child { + constructor() { + super(null!); + } + } + `); + + + const exitCode = main(['-p', basePath], errorSpy); + expect(errorSpy).not.toHaveBeenCalled(); + expect(exitCode).toBe(0); + const jsContents = getContents('test.js'); + + expect(jsContents) + .toContain('function Base_Factory(t) { return new (t || Base)(i0.inject(Dep)); }'); + expect(jsContents).toContain('var ɵChild_BaseFactory = i0.ɵgetInheritedFactory(Child)'); + expect(jsContents) + .toContain('function Child_Factory(t) { return ɵChild_BaseFactory((t || Child)); }'); + expect(jsContents) + .toContain('function GrandChild_Factory(t) { return new (t || GrandChild)(); }'); + }); }); diff --git a/packages/compiler/src/injectable_compiler_2.ts b/packages/compiler/src/injectable_compiler_2.ts index abe1d9c76c..e2b5845254 100644 --- a/packages/compiler/src/injectable_compiler_2.ts +++ b/packages/compiler/src/injectable_compiler_2.ts @@ -9,103 +9,103 @@ import {InjectFlags} from './core'; import {Identifiers} from './identifiers'; import * as o from './output/output_ast'; -import {R3DependencyMetadata, compileFactoryFunction} from './render3/r3_factory'; +import {R3DependencyMetadata, R3FactoryDelegateType, R3FactoryMetadata, compileFactoryFunction} from './render3/r3_factory'; import {mapToMapExpression} from './render3/util'; export interface InjectableDef { expression: o.Expression; type: o.Type; + statements: o.Statement[]; } export interface R3InjectableMetadata { name: string; type: o.Expression; + ctorDeps: R3DependencyMetadata[]|null; providedIn: o.Expression; useClass?: o.Expression; useFactory?: o.Expression; useExisting?: o.Expression; useValue?: o.Expression; - deps?: R3DependencyMetadata[]; + userDeps?: R3DependencyMetadata[]; } export function compileInjectable(meta: R3InjectableMetadata): InjectableDef { - let factory: o.Expression = o.NULL_EXPR; + let result: {factory: o.Expression, statements: o.Statement[]}|null = null; function makeFn(ret: o.Expression): o.Expression { return o.fn([], [new o.ReturnStatement(ret)], undefined, undefined, `${meta.name}_Factory`); } - if (meta.useClass !== undefined || meta.useFactory !== undefined) { - // First, handle useClass and useFactory together, since both involve a similar call to - // `compileFactoryFunction`. Either dependencies are explicitly specified, in which case - // a factory function call is generated, or they're not specified and the calls are special- - // cased. - if (meta.deps !== undefined) { - // Either call `new meta.useClass(...)` or `meta.useFactory(...)`. - const fnOrClass: o.Expression = meta.useClass || meta.useFactory !; + const factoryMeta = { + name: meta.name, + type: meta.type, + deps: meta.ctorDeps, + injectFn: Identifiers.inject, + }; - // useNew: true if meta.useClass, false for meta.useFactory. - const useNew = meta.useClass !== undefined; + 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, in which + // case omitting deps just uses the constructor dependencies instead. - factory = compileFactoryFunction({ - name: meta.name, - fnOrClass, - useNew, - injectFn: Identifiers.inject, - deps: meta.deps, + const useClassOnSelf = meta.useClass.isEquivalent(meta.type); + const deps = meta.userDeps || (useClassOnSelf && meta.ctorDeps) || undefined; + + if (deps !== undefined) { + // factory: () => new meta.useClass(...deps) + result = compileFactoryFunction({ + ...factoryMeta, + delegate: meta.useClass, + delegateDeps: deps, + delegateType: R3FactoryDelegateType.Class, }); - } else if (meta.useClass !== undefined) { - // Special case for useClass where the factory from the class's ngInjectableDef is used. - if (meta.useClass.isEquivalent(meta.type)) { - // For the injectable compiler, useClass represents a foreign type that should be - // instantiated to satisfy construction of the given type. It's not valid to specify - // useClass === type, since the useClass type is expected to already be compiled. - throw new Error( - `useClass is the same as the type, but no deps specified, which is invalid.`); - } - factory = - makeFn(new o.ReadPropExpr(new o.ReadPropExpr(meta.useClass, 'ngInjectableDef'), 'factory') - .callFn([])); - } else if (meta.useFactory !== undefined) { - // Special case for useFactory where no arguments are passed. - factory = meta.useFactory.callFn([]); } else { - // Can't happen - outer conditional guards against both useClass and useFactory being - // undefined. - throw new Error('Reached unreachable block in injectable compiler.'); + result = compileFactoryFunction({ + ...factoryMeta, + delegate: meta.useClass, + delegateType: R3FactoryDelegateType.Factory, + }); } + } else if (meta.useFactory !== undefined) { + result = compileFactoryFunction({ + ...factoryMeta, + delegate: meta.useFactory, + delegateDeps: meta.userDeps || [], + delegateType: R3FactoryDelegateType.Function, + }); } 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. - factory = makeFn(meta.useValue); + result = compileFactoryFunction({ + ...factoryMeta, + expression: meta.useValue, + }); } else if (meta.useExisting !== undefined) { // useExisting is an `inject` call on the existing token. - factory = makeFn(o.importExpr(Identifiers.inject).callFn([meta.useExisting])); - } else { - // A strict type is compiled according to useClass semantics, except the dependencies are - // required. - if (meta.deps === undefined) { - throw new Error(`Type compilation of an injectable requires dependencies.`); - } - factory = compileFactoryFunction({ - name: meta.name, - fnOrClass: meta.type, - useNew: true, - injectFn: Identifiers.inject, - deps: meta.deps, + result = compileFactoryFunction({ + ...factoryMeta, + expression: o.importExpr(Identifiers.inject).callFn([meta.useExisting]), }); + } else { + result = compileFactoryFunction(factoryMeta); } const token = meta.type; const providedIn = meta.providedIn; const expression = o.importExpr(Identifiers.defineInjectable).callFn([mapToMapExpression( - {token, factory, providedIn})]); + {token, factory: result.factory, providedIn})]); const type = new o.ExpressionType( o.importExpr(Identifiers.InjectableDef, [new o.ExpressionType(meta.type)])); return { - expression, type, + expression, + type, + statements: result.statements, }; } diff --git a/packages/compiler/src/render3/r3_factory.ts b/packages/compiler/src/render3/r3_factory.ts index 949f6672de..0808db3053 100644 --- a/packages/compiler/src/render3/r3_factory.ts +++ b/packages/compiler/src/render3/r3_factory.ts @@ -15,12 +15,13 @@ import * as o from '../output/output_ast'; import {Identifiers as R3} from '../render3/r3_identifiers'; import {OutputContext} from '../util'; -import {unsupported} from './view/util'; +import {MEANING_SEPARATOR, unsupported} from './view/util'; + /** * Metadata required by the factory generator to generate a `factory` function for a type. */ -export interface R3FactoryMetadata { +export interface R3ConstructorFactoryMetadata { /** * String name of the type being generated (used to name the factory function). */ @@ -33,21 +34,15 @@ export interface R3FactoryMetadata { * This could be a reference to a constructor type, or to a user-defined factory function. The * `useNew` property determines whether it will be called as a constructor or not. */ - fnOrClass: o.Expression; + type: o.Expression; /** * Regardless of whether `fnOrClass` is a constructor function or a user-defined factory, it * may have 0 or more parameters, which will be injected according to the `R3DependencyMetadata` - * for those parameters. + * for those parameters. If this is `null`, then the type's constructor is nonexistent and will + * be inherited from `fnOrClass` which is interpreted as the current type. */ - deps: R3DependencyMetadata[]; - - /** - * Whether to interpret `fnOrClass` as a constructor function (`useNew: true`) or as a factory - * (`useNew: false`). - */ - useNew: boolean; - + deps: R3DependencyMetadata[]|null; /** * An expression for the function which will be used to inject dependencies. The API of this @@ -56,6 +51,30 @@ export interface R3FactoryMetadata { injectFn: o.ExternalReference; } +export enum R3FactoryDelegateType { + Class, + Function, + Factory, +} + +export interface R3DelegatedFactoryMetadata extends R3ConstructorFactoryMetadata { + delegate: o.Expression; + delegateType: R3FactoryDelegateType.Factory; +} + +export interface R3DelegatedFnOrClassMetadata extends R3ConstructorFactoryMetadata { + delegate: o.Expression; + delegateType: R3FactoryDelegateType.Class|R3FactoryDelegateType.Function; + delegateDeps: R3DependencyMetadata[]; +} + +export interface R3ExpressionFactoryMetadata extends R3ConstructorFactoryMetadata { + expression: o.Expression; +} + +export type R3FactoryMetadata = R3ConstructorFactoryMetadata | R3DelegatedFactoryMetadata | + R3DelegatedFnOrClassMetadata | R3ExpressionFactoryMetadata; + /** * Resolved type of a dependency. * @@ -142,16 +161,84 @@ export interface R3DependencyMetadata { /** * Construct a factory function expression for the given `R3FactoryMetadata`. */ -export function compileFactoryFunction(meta: R3FactoryMetadata): o.Expression { - // Each dependency becomes an invocation of an inject*() function. - const args = meta.deps.map(dep => compileInjectDependency(dep, meta.injectFn)); +export function compileFactoryFunction(meta: R3FactoryMetadata): + {factory: o.Expression, statements: o.Statement[]} { + const t = o.variable('t'); + const statements: o.Statement[] = []; - // The overall result depends on whether this is construction or function invocation. - const expr = meta.useNew ? new o.InstantiateExpr(meta.fnOrClass, args) : - new o.InvokeFunctionExpr(meta.fnOrClass, args); + // The type to instantiate via constructor invocation. If there is no delegated factory, meaning + // this type is always created by constructor invocation, then this is the type-to-create + // parameter provided by the user (t) if specified, or the current type if not. If there is a + // delegated factory (which is used to create the current type) then this is only the type-to- + // create parameter (t). + const typeForCtor = + !isDelegatedMetadata(meta) ? new o.BinaryOperatorExpr(o.BinaryOperator.Or, t, meta.type) : t; - return o.fn( - [], [new o.ReturnStatement(expr)], o.INFERRED_TYPE, undefined, `${meta.name}_Factory`); + let ctorExpr: o.Expression|null = null; + if (meta.deps !== null) { + // There is a constructor (either explicitly or implicitly defined). + ctorExpr = new o.InstantiateExpr(typeForCtor, injectDependencies(meta.deps, meta.injectFn)); + } else { + const baseFactory = o.variable(`ɵ${meta.name}_BaseFactory`); + const getInheritedFactory = o.importExpr(R3.getInheritedFactory); + const baseFactoryStmt = baseFactory.set(getInheritedFactory.callFn([meta.type])).toDeclStmt(); + statements.push(baseFactoryStmt); + + // There is no constructor, use the base class' factory to construct typeForCtor. + ctorExpr = baseFactory.callFn([typeForCtor]); + } + const ctorExprFinal = ctorExpr; + + const body: o.Statement[] = []; + let retExpr: o.Expression|null = null; + + function makeConditionalFactory(nonCtorExpr: o.Expression): o.ReadVarExpr { + const r = o.variable('r'); + body.push(r.set(o.NULL_EXPR).toDeclStmt()); + body.push(o.ifStmt(t, [r.set(ctorExprFinal).toStmt()], [r.set(nonCtorExpr).toStmt()])); + return r; + } + + if (isDelegatedMetadata(meta) && meta.delegateType === R3FactoryDelegateType.Factory) { + const delegateFactory = o.variable(`ɵ${meta.name}_BaseFactory`); + const getFactoryOf = o.importExpr(R3.getFactoryOf); + if (meta.delegate.isEquivalent(meta.type)) { + throw new Error(`Illegal state: compiling factory that delegates to itself`); + } + const delegateFactoryStmt = + delegateFactory.set(getFactoryOf.callFn([meta.delegate])).toDeclStmt(); + + statements.push(delegateFactoryStmt); + const r = makeConditionalFactory(delegateFactory.callFn([])); + retExpr = r; + } else if (isDelegatedMetadata(meta)) { + // This type is created with a delegated factory. If a type parameter is not specified, call + // the factory instead. + const delegateArgs = injectDependencies(meta.delegateDeps, meta.injectFn); + // Either call `new delegate(...)` or `delegate(...)` depending on meta.useNewForDelegate. + const factoryExpr = new ( + meta.delegateType === R3FactoryDelegateType.Class ? + o.InstantiateExpr : + o.InvokeFunctionExpr)(meta.delegate, delegateArgs); + retExpr = makeConditionalFactory(factoryExpr); + } else if (isExpressionFactoryMetadata(meta)) { + // TODO(alxhub): decide whether to lower the value here or in the caller + retExpr = makeConditionalFactory(meta.expression); + } else { + retExpr = ctorExpr; + } + + return { + factory: o.fn( + [new o.FnParam('t', o.DYNAMIC_TYPE)], [...body, new o.ReturnStatement(retExpr)], + o.INFERRED_TYPE, undefined, `${meta.name}_Factory`), + statements, + }; +} + +function injectDependencies( + deps: R3DependencyMetadata[], injectFn: o.ExternalReference): o.Expression[] { + return deps.map(dep => compileInjectDependency(dep, injectFn)); } function compileInjectDependency( @@ -253,3 +340,12 @@ export function dependenciesFromGlobalMetadata( return deps; } + +function isDelegatedMetadata(meta: R3FactoryMetadata): meta is R3DelegatedFactoryMetadata| + R3DelegatedFnOrClassMetadata { + return (meta as any).delegateType !== undefined; +} + +function isExpressionFactoryMetadata(meta: R3FactoryMetadata): meta is R3ExpressionFactoryMetadata { + return (meta as any).expression !== undefined; +} \ No newline at end of file diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 47ff7cb8a9..f68d4a0ccc 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -162,6 +162,16 @@ export class Identifiers { static listener: o.ExternalReference = {name: 'ɵL', moduleName: CORE}; + static getFactoryOf: o.ExternalReference = { + name: 'ɵgetFactoryOf', + moduleName: CORE, + }; + + static getInheritedFactory: o.ExternalReference = { + name: 'ɵgetInheritedFactory', + moduleName: CORE, + }; + // Reserve slots for pure functions static reserveSlots: o.ExternalReference = {name: 'ɵrS', moduleName: CORE}; diff --git a/packages/compiler/src/render3/r3_jit.ts b/packages/compiler/src/render3/r3_jit.ts index 17ccd0e186..2626dbbdbe 100644 --- a/packages/compiler/src/render3/r3_jit.ts +++ b/packages/compiler/src/render3/r3_jit.ts @@ -60,12 +60,12 @@ class R3JitReflector implements CompileReflector { */ export function jitExpression( def: o.Expression, context: {[key: string]: any}, sourceUrl: string, - constantPool?: ConstantPool): any { + preStatements: o.Statement[]): any { // The ConstantPool may contain Statements which declare variables used in the final expression. // Therefore, its statements need to precede the actual JIT operation. The final statement is a // declaration of $def which is set to the expression being compiled. const statements: o.Statement[] = [ - ...(constantPool !== undefined ? constantPool.statements : []), + ...preStatements, new o.DeclareVarStmt('$def', def, undefined, [o.StmtModifier.Exported]), ]; diff --git a/packages/compiler/src/render3/r3_module_compiler.ts b/packages/compiler/src/render3/r3_module_compiler.ts index a1153a0ef7..f27fbd2b1b 100644 --- a/packages/compiler/src/render3/r3_module_compiler.ts +++ b/packages/compiler/src/render3/r3_module_compiler.ts @@ -85,31 +85,32 @@ export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef { export interface R3InjectorDef { expression: o.Expression; type: o.Type; + statements: o.Statement[]; } export interface R3InjectorMetadata { name: string; type: o.Expression; - deps: R3DependencyMetadata[]; + deps: R3DependencyMetadata[]|null; providers: o.Expression; imports: o.Expression; } export function compileInjector(meta: R3InjectorMetadata): R3InjectorDef { + const result = compileFactoryFunction({ + name: meta.name, + type: meta.type, + deps: meta.deps, + injectFn: R3.inject, + }); const expression = o.importExpr(R3.defineInjector).callFn([mapToMapExpression({ - factory: compileFactoryFunction({ - name: meta.name, - fnOrClass: meta.type, - deps: meta.deps, - useNew: true, - injectFn: R3.inject, - }), + factory: result.factory, providers: meta.providers, imports: meta.imports, })]); const type = new o.ExpressionType(o.importExpr(R3.InjectorDef, [new o.ExpressionType(meta.type)])); - return {expression, type}; + return {expression, type, statements: result.statements}; } // TODO(alxhub): integrate this with `compileNgModule`. Currently the two are separate operations. diff --git a/packages/compiler/src/render3/r3_pipe_compiler.ts b/packages/compiler/src/render3/r3_pipe_compiler.ts index 4f534054e2..569b8ab6bb 100644 --- a/packages/compiler/src/render3/r3_pipe_compiler.ts +++ b/packages/compiler/src/render3/r3_pipe_compiler.ts @@ -19,13 +19,14 @@ export interface R3PipeMetadata { name: string; type: o.Expression; pipeName: string; - deps: R3DependencyMetadata[]; + deps: R3DependencyMetadata[]|null; pure: boolean; } export interface R3PipeDef { expression: o.Expression; type: o.Type; + statements: o.Statement[]; } export function compilePipeFromMetadata(metadata: R3PipeMetadata) { @@ -39,12 +40,11 @@ export function compilePipeFromMetadata(metadata: R3PipeMetadata) { const templateFactory = compileFactoryFunction({ name: metadata.name, - fnOrClass: metadata.type, + type: metadata.type, deps: metadata.deps, - useNew: true, injectFn: R3.directiveInject, }); - definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false}); + definitionMapValues.push({key: 'factory', value: templateFactory.factory, quoted: false}); // e.g. `pure: true` definitionMapValues.push({key: 'pure', value: o.literal(metadata.pure), quoted: false}); @@ -54,7 +54,7 @@ export function compilePipeFromMetadata(metadata: R3PipeMetadata) { new o.ExpressionType(metadata.type), new o.ExpressionType(new o.LiteralExpr(metadata.pipeName)), ])); - return {expression, type}; + return {expression, type, statements: templateFactory.statements}; } /** diff --git a/packages/compiler/src/render3/view/api.ts b/packages/compiler/src/render3/view/api.ts index a315990f5e..6e45d13783 100644 --- a/packages/compiler/src/render3/view/api.ts +++ b/packages/compiler/src/render3/view/api.ts @@ -38,7 +38,7 @@ export interface R3DirectiveMetadata { /** * Dependencies of the directive's constructor. */ - deps: R3DependencyMetadata[]; + deps: R3DependencyMetadata[]|null; /** * Unparsed selector of the directive, or `null` if there was no selector. @@ -177,6 +177,7 @@ export interface R3QueryMetadata { export interface R3DirectiveDef { expression: o.Expression; type: o.Type; + statements: o.Statement[]; } /** @@ -185,4 +186,5 @@ export interface R3DirectiveDef { export interface R3ComponentDef { expression: o.Expression; type: o.Type; + statements: o.Statement[]; } diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 84952e75d6..0c1ec63a7c 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -29,7 +29,7 @@ import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, co function baseDirectiveFields( meta: R3DirectiveMetadata, constantPool: ConstantPool, - bindingParser: BindingParser): DefinitionMap { + bindingParser: BindingParser): {definitionMap: DefinitionMap, statements: o.Statement[]} { const definitionMap = new DefinitionMap(); // e.g. `type: MyDirective` @@ -40,13 +40,13 @@ function baseDirectiveFields( // e.g. `factory: () => new MyApp(injectElementRef())` - definitionMap.set('factory', compileFactoryFunction({ - name: meta.name, - fnOrClass: meta.type, - deps: meta.deps, - useNew: true, - injectFn: R3.directiveInject, - })); + const result = compileFactoryFunction({ + name: meta.name, + type: meta.type, + deps: meta.deps, + injectFn: R3.directiveInject, + }); + definitionMap.set('factory', result.factory); definitionMap.set('contentQueries', createContentQueriesFunction(meta, constantPool)); @@ -80,7 +80,7 @@ function baseDirectiveFields( definitionMap.set('features', o.literalArr(features)); } - return definitionMap; + return {definitionMap, statements: result.statements}; } /** @@ -89,7 +89,7 @@ function baseDirectiveFields( export function compileDirectiveFromMetadata( meta: R3DirectiveMetadata, constantPool: ConstantPool, bindingParser: BindingParser): R3DirectiveDef { - const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser); + const {definitionMap, statements} = baseDirectiveFields(meta, constantPool, bindingParser); const expression = o.importExpr(R3.defineDirective).callFn([definitionMap.toLiteralMap()]); // On the type side, remove newlines from the selector as it will need to fit into a TypeScript @@ -100,7 +100,7 @@ export function compileDirectiveFromMetadata( typeWithParameters(meta.type, meta.typeArgumentCount), new o.ExpressionType(o.literal(selectorForType)) ])); - return {expression, type}; + return {expression, type, statements}; } /** @@ -109,7 +109,7 @@ export function compileDirectiveFromMetadata( export function compileComponentFromMetadata( meta: R3ComponentMetadata, constantPool: ConstantPool, bindingParser: BindingParser): R3ComponentDef { - const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser); + const {definitionMap, statements} = baseDirectiveFields(meta, constantPool, bindingParser); const selector = meta.selector && CssSelector.parse(meta.selector); const firstSelector = selector && selector[0]; @@ -180,7 +180,7 @@ export function compileComponentFromMetadata( new o.ExpressionType(o.literal(selectorForType)) ])); - return {expression, type}; + return {expression, type, statements}; } /** diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index b34f8b9cff..c520218a8a 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -23,6 +23,8 @@ export { injectViewContainerRef as ɵinjectViewContainerRef, injectChangeDetectorRef as ɵinjectChangeDetectorRef, injectAttribute as ɵinjectAttribute, + getFactoryOf as ɵgetFactoryOf, + getInheritedFactory as ɵgetInheritedFactory, PublicFeature as ɵPublicFeature, InheritDefinitionFeature as ɵInheritDefinitionFeature, NgOnChangesFeature as ɵNgOnChangesFeature, diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index c0ac484353..c92f2b63f6 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -771,6 +771,26 @@ export function getOrCreateTemplateRef(di: LInjector): viewEngine.TemplateRef return di.templateRef; } +export function getFactoryOf(type: Type): ((type?: Type) => T)|null { + const typeAny = type as any; + const def = typeAny.ngComponentDef || typeAny.ngDirectiveDef || typeAny.ngPipeDef || + typeAny.ngInjectableDef || typeAny.ngInjectorDef; + if (def === undefined || def.factory === undefined) { + return null; + } + return def.factory; +} + +export function getInheritedFactory(type: Type): (type: Type) => T { + debugger; + const proto = Object.getPrototypeOf(type.prototype).constructor as Type; + const factory = getFactoryOf(proto); + if (factory === null) { + throw new Error(`Type ${proto.name} does not support inheritance`); + } + return factory; +} + class TemplateRef implements viewEngine.TemplateRef { constructor( private _declarationParentView: LViewData, readonly elementRef: viewEngine.ElementRef, diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 5f12ea33bb..841c7500b3 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -14,7 +14,7 @@ import {PublicFeature} from './features/public_feature'; import {ComponentDef, ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveDefInternal, DirectiveType, PipeDef} from './interfaces/definition'; export {ComponentFactory, ComponentFactoryResolver, ComponentRef} from './component_ref'; -export {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, directiveInject, injectAttribute, injectChangeDetectorRef, injectComponentFactoryResolver, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di'; +export {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, directiveInject, getFactoryOf, getInheritedFactory, injectAttribute, injectChangeDetectorRef, injectComponentFactoryResolver, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di'; export {RenderFlags} from './interfaces/definition'; export {CssSelectorList} from './interfaces/projection'; diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts index de24c9035d..21ba4ba1ba 100644 --- a/packages/core/src/render3/jit/directive.ts +++ b/packages/core/src/render3/jit/directive.ts @@ -75,9 +75,10 @@ export function compileComponent(type: Type, metadata: Component): void { viewQueries: [], }, constantPool, makeBindingParser()); + const preStatements = [...constantPool.statements, ...res.statements]; def = jitExpression( - res.expression, angularCoreEnv, `ng://${type.name}/ngComponentDef.js`, constantPool); + res.expression, angularCoreEnv, `ng://${type.name}/ngComponentDef.js`, preStatements); // If component compilation is async, then the @NgModule annotation which declares the // component may execute and set an ngSelectorScope property on the component type. This @@ -113,7 +114,8 @@ export function compileDirective(type: Type, directive: Directive): void { const sourceMapUrl = `ng://${type && type.name}/ngDirectiveDef.js`; const res = compileR3Directive( directiveMetadata(type, directive), constantPool, makeBindingParser()); - def = jitExpression(res.expression, angularCoreEnv, sourceMapUrl, constantPool); + const preStatements = [...constantPool.statements, ...res.statements]; + def = jitExpression(res.expression, angularCoreEnv, sourceMapUrl, preStatements); } return def; }, diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index 8e62112623..2c7abc0125 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -25,6 +25,8 @@ export const angularCoreEnv: {[name: string]: Function} = { 'ɵdefineNgModule': r3.defineNgModule, 'ɵdefinePipe': r3.definePipe, 'ɵdirectiveInject': r3.directiveInject, + 'ɵgetFactoryOf': r3.getFactoryOf, + 'ɵgetInheritedFactory': r3.getInheritedFactory, 'inject': inject, 'ɵinjectAttribute': r3.injectAttribute, 'ɵinjectChangeDetectorRef': r3.injectChangeDetectorRef, diff --git a/packages/core/src/render3/jit/injectable.ts b/packages/core/src/render3/jit/injectable.ts index 6c714b29f1..c2cdd0f44d 100644 --- a/packages/core/src/render3/jit/injectable.ts +++ b/packages/core/src/render3/jit/injectable.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Expression, LiteralExpr, R3DependencyMetadata, WrappedNodeExpr, compileInjectable as compileR3Injectable, jitExpression} from '@angular/compiler'; +import {Expression, LiteralExpr, R3DependencyMetadata, R3InjectableMetadata, WrappedNodeExpr, compileInjectable as compileR3Injectable, jitExpression} from '@angular/compiler'; import {Injectable} from '../../di/injectable'; import {ClassSansProvider, ExistingSansProvider, FactorySansProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from '../../di/provider'; @@ -18,6 +18,7 @@ import {NG_INJECTABLE_DEF} from './fields'; import {convertDependencies, reflectDependencies} from './util'; + /** * Compile an Angular injectable according to its `Injectable` metadata, and patch the resulting * `ngInjectableDef` onto the injectable type. @@ -34,13 +35,11 @@ export function compileInjectable(type: Type, srcMeta?: Injectable): void { 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) || []; + const ctorDeps = reflectDependencies(type); + + let userDeps: R3DependencyMetadata[]|undefined = undefined; + if ((isUseClassProvider(meta) || isUseFactoryProvider(meta)) && meta.deps !== undefined) { + userDeps = convertDependencies(meta.deps); } // Decide which flavor of factory to generate, based on the provider specified. @@ -73,7 +72,7 @@ export function compileInjectable(type: Type, srcMeta?: Injectable): void { throw new Error(`Unreachable state.`); } - const {expression} = compileR3Injectable({ + const {expression, statements} = compileR3Injectable({ name: type.name, type: new WrappedNodeExpr(type), providedIn: computeProvidedIn(meta.providedIn), @@ -81,10 +80,12 @@ export function compileInjectable(type: Type, srcMeta?: Injectable): void { useFactory, useValue, useExisting, - deps, + ctorDeps, + userDeps, }); - def = jitExpression(expression, angularCoreEnv, `ng://${type.name}/ngInjectableDef.js`); + def = jitExpression( + expression, angularCoreEnv, `ng://${type.name}/ngInjectableDef.js`, statements); } return def; }, diff --git a/packages/core/src/render3/jit/module.ts b/packages/core/src/render3/jit/module.ts index 91e1297306..309aa2e0d0 100644 --- a/packages/core/src/render3/jit/module.ts +++ b/packages/core/src/render3/jit/module.ts @@ -39,7 +39,7 @@ export function compileNgModule(type: Type, ngModule: NgModule): void { }; const res = compileR3NgModule(meta); ngModuleDef = - jitExpression(res.expression, angularCoreEnv, `ng://${type.name}/ngModuleDef.js`); + jitExpression(res.expression, angularCoreEnv, `ng://${type.name}/ngModuleDef.js`, []); } return ngModuleDef; }, @@ -60,8 +60,8 @@ export function compileNgModule(type: Type, ngModule: NgModule): void { ]), }; const res = compileInjector(meta); - ngInjectorDef = - jitExpression(res.expression, angularCoreEnv, `ng://${type.name}/ngInjectorDef.js`); + ngInjectorDef = jitExpression( + res.expression, angularCoreEnv, `ng://${type.name}/ngInjectorDef.js`, res.statements); } return ngInjectorDef; }, diff --git a/packages/core/src/render3/jit/pipe.ts b/packages/core/src/render3/jit/pipe.ts index 0fc32841da..a086f50e10 100644 --- a/packages/core/src/render3/jit/pipe.ts +++ b/packages/core/src/render3/jit/pipe.ts @@ -32,7 +32,7 @@ export function compilePipe(type: Type, meta: Pipe): void { pure: meta.pure !== undefined ? meta.pure : true, }); - def = jitExpression(res.expression, angularCoreEnv, sourceMapUrl); + def = jitExpression(res.expression, angularCoreEnv, sourceMapUrl, res.statements); } return def; } diff --git a/packages/core/test/render3/ivy/jit_spec.ts b/packages/core/test/render3/ivy/jit_spec.ts index 3029b2dda1..9018299f75 100644 --- a/packages/core/test/render3/ivy/jit_spec.ts +++ b/packages/core/test/render3/ivy/jit_spec.ts @@ -124,6 +124,23 @@ ivyEnabled && describe('render3 jit', () => { expect(injected.value).toBe(2); }); + it('compiles an injectable with an inherited constructor', () => { + @Injectable({providedIn: 'root'}) + class Dep { + } + + @Injectable() + class Base { + constructor(readonly dep: Dep) {} + } + + @Injectable({providedIn: 'root'}) + class Child extends Base { + } + + expect(inject(Child).dep instanceof Dep).toBe(true); + }); + it('compiles a module to a definition', () => { @Component({ template: 'foo',