diff --git a/integration/ngcc/test.sh b/integration/ngcc/test.sh index cc321751d96b5..da360842fa109 100755 --- a/integration/ngcc/test.sh +++ b/integration/ngcc/test.sh @@ -95,12 +95,12 @@ assertSucceeded "Expected 'ngcc' to log 'Compiling'." assertSucceeded "Expected 'ngcc' to generate a base factory for 'MatTable' in '@angular/material' (esm5)." -# Did it generate a base definition for undecorated classes with inputs and view queries? - grep "_MatMenuBase.ngBaseDef = ɵngcc0.ɵɵdefineBase({ inputs: {" node_modules/@angular/material/esm2015/menu.js - assertSucceeded "Expected 'ngcc' to generate a base definition for 'MatMenuBase' in '@angular/material' (esm2015)." +# Did it generate an abstract directive definition for undecorated classes with inputs and view queries? + grep "_MatMenuBase.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: _MatMenuBase" node_modules/@angular/material/esm2015/menu.js + assertSucceeded "Expected 'ngcc' to generate an abstract directive definition for 'MatMenuBase' in '@angular/material' (esm2015)." - grep "_MatMenuBase.ngBaseDef = ɵngcc0.ɵɵdefineBase({ inputs: {" node_modules/@angular/material/esm5/menu.es5.js - assertSucceeded "Expected 'ngcc' to generate a base definition for 'MatMenuBase' in '@angular/material' (esm5)." + grep "_MatMenuBase.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: _MatMenuBase" node_modules/@angular/material/esm5/menu.es5.js + assertSucceeded "Expected 'ngcc' to generate an abstract directive definition for 'MatMenuBase' in '@angular/material' (esm5)." # Did it handle namespace imported decorators in UMD using `__decorate` syntax? diff --git a/packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts b/packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts index e2cc4086b6d05..5bf87431e614f 100644 --- a/packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts +++ b/packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts @@ -7,8 +7,7 @@ */ import {ConstantPool} from '@angular/compiler'; import * as ts from 'typescript'; - -import {BaseDefDecoratorHandler, ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ReferencesRegistry, ResourceLoader} from '../../../src/ngtsc/annotations'; +import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ReferencesRegistry, ResourceLoader} from '../../../src/ngtsc/annotations'; import {CycleAnalyzer, ImportGraph} from '../../../src/ngtsc/cycles'; import {isFatalDiagnosticError} from '../../../src/ngtsc/diagnostics'; import {FileSystem, LogicalFileSystem, absoluteFrom, dirname, resolve} from '../../../src/ngtsc/file_system'; @@ -85,7 +84,6 @@ export class DecorationAnalyzer { importGraph = new ImportGraph(this.moduleResolver); cycleAnalyzer = new CycleAnalyzer(this.importGraph); handlers: DecoratorHandler[] = [ - new BaseDefDecoratorHandler(this.reflectionHost, this.evaluator, this.isCore), new ComponentDecoratorHandler( this.reflectionHost, this.evaluator, this.fullRegistry, this.fullMetaReader, this.scopeRegistry, this.scopeRegistry, this.isCore, this.resourceManager, this.rootDirs, diff --git a/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts b/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts index de62445e34194..5d04b53e08843 100644 --- a/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts +++ b/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts @@ -284,7 +284,7 @@ A.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: A, selectors: [["", "a", ""]] }); .toBeGreaterThan(ngInjectorDef, 'setClassMetadata should follow ɵinj'); }); - it('should render classes without decorators if handler matches', () => { + it('should render classes without decorators if class fields are decorated', () => { const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses, testFormatter} = createTestRenderer('test-package', [{ @@ -309,7 +309,8 @@ A.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: A, selectors: [["", "a", ""]] }); const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy; expect(addDefinitionsSpy.calls.first().args[2]) .toEqual( - `UndecoratedBase.ngBaseDef = ɵngcc0.ɵɵdefineBase({ viewQuery: function (rf, ctx) { if (rf & 1) { + `UndecoratedBase.ɵfac = function UndecoratedBase_Factory(t) { return new (t || UndecoratedBase)(); }; +UndecoratedBase.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: UndecoratedBase, selectors: [], viewQuery: function UndecoratedBase_Query(rf, ctx) { if (rf & 1) { ɵngcc0.ɵɵstaticViewQuery(_c0, true); } if (rf & 2) { var _t; diff --git a/packages/compiler-cli/src/ngtsc/annotations/index.ts b/packages/compiler-cli/src/ngtsc/annotations/index.ts index 3d5ff3cb64f7f..d439994d26ac4 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/index.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/index.ts @@ -9,7 +9,6 @@ /// export {ResourceLoader} from './src/api'; -export {BaseDefDecoratorHandler} from './src/base_def'; export {ComponentDecoratorHandler} from './src/component'; export {DirectiveDecoratorHandler} from './src/directive'; export {InjectableDecoratorHandler} from './src/injectable'; diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/base_def.ts b/packages/compiler-cli/src/ngtsc/annotations/src/base_def.ts deleted file mode 100644 index 2e8e33b8b0342..0000000000000 --- a/packages/compiler-cli/src/ngtsc/annotations/src/base_def.ts +++ /dev/null @@ -1,173 +0,0 @@ -/** - * @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 {ConstantPool, EMPTY_SOURCE_SPAN, R3BaseRefMetaData, WrappedNodeExpr, compileBaseDefFromMetadata, makeBindingParser} from '@angular/compiler'; - -import {PartialEvaluator} from '../../partial_evaluator'; -import {ClassDeclaration, ClassMember, Decorator, ReflectionHost} from '../../reflection'; -import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform'; - -import {extractHostBindings, queriesFromFields} from './directive'; -import {isAngularDecorator} from './util'; - -function containsNgTopLevelDecorator(decorators: Decorator[] | null, isCore: boolean): boolean { - if (!decorators) { - return false; - } - return decorators.some( - decorator => isAngularDecorator(decorator, 'Component', isCore) || - isAngularDecorator(decorator, 'Directive', isCore) || - isAngularDecorator(decorator, 'NgModule', isCore)); -} - -export class BaseDefDecoratorHandler implements - DecoratorHandler { - constructor( - private reflector: ReflectionHost, private evaluator: PartialEvaluator, - private isCore: boolean) {} - - readonly precedence = HandlerPrecedence.WEAK; - - detect(node: ClassDeclaration, decorators: Decorator[]|null): - DetectResult|undefined { - if (containsNgTopLevelDecorator(decorators, this.isCore)) { - // If the class is already decorated by @Component or @Directive let that - // DecoratorHandler handle this. BaseDef is unnecessary. - return undefined; - } - - let result: R3BaseRefDecoratorDetection|undefined = undefined; - - this.reflector.getMembersOfClass(node).forEach(property => { - const {decorators} = property; - if (!decorators) { - return; - } - for (const decorator of decorators) { - if (isAngularDecorator(decorator, 'Input', this.isCore)) { - result = result || {}; - const inputs = result.inputs = result.inputs || []; - inputs.push({decorator, property}); - } else if (isAngularDecorator(decorator, 'Output', this.isCore)) { - result = result || {}; - const outputs = result.outputs = result.outputs || []; - outputs.push({decorator, property}); - } else if ( - isAngularDecorator(decorator, 'ViewChild', this.isCore) || - isAngularDecorator(decorator, 'ViewChildren', this.isCore)) { - result = result || {}; - const viewQueries = result.viewQueries = result.viewQueries || []; - viewQueries.push({member: property, decorators}); - } else if ( - isAngularDecorator(decorator, 'ContentChild', this.isCore) || - isAngularDecorator(decorator, 'ContentChildren', this.isCore)) { - result = result || {}; - const queries = result.queries = result.queries || []; - queries.push({member: property, decorators}); - } else if ( - isAngularDecorator(decorator, 'HostBinding', this.isCore) || - isAngularDecorator(decorator, 'HostListener', this.isCore)) { - result = result || {}; - const host = result.host = result.host || []; - host.push(property); - } - } - }); - - if (result !== undefined) { - return { - metadata: result, - trigger: null, - }; - } else { - return undefined; - } - } - - analyze(node: ClassDeclaration, metadata: R3BaseRefDecoratorDetection): - AnalysisOutput { - const analysis: R3BaseRefMetaData = { - name: node.name.text, - type: new WrappedNodeExpr(node.name), - typeSourceSpan: EMPTY_SOURCE_SPAN, - }; - - if (metadata.inputs) { - const inputs = analysis.inputs = {} as{[key: string]: string | [string, string]}; - metadata.inputs.forEach(({decorator, property}) => { - const propName = property.name; - const args = decorator.args; - let value: string|[string, string]; - if (args && args.length > 0) { - const resolvedValue = this.evaluator.evaluate(args[0]); - if (typeof resolvedValue !== 'string') { - throw new TypeError('Input alias does not resolve to a string value'); - } - value = [resolvedValue, propName]; - } else { - value = propName; - } - inputs[propName] = value; - }); - } - - if (metadata.outputs) { - const outputs = analysis.outputs = {} as{[key: string]: string}; - metadata.outputs.forEach(({decorator, property}) => { - const propName = property.name; - const args = decorator.args; - let value: string; - if (args && args.length > 0) { - const resolvedValue = this.evaluator.evaluate(args[0]); - if (typeof resolvedValue !== 'string') { - throw new TypeError('Output alias does not resolve to a string value'); - } - value = resolvedValue; - } else { - value = propName; - } - outputs[propName] = value; - }); - } - - if (metadata.viewQueries) { - analysis.viewQueries = - queriesFromFields(metadata.viewQueries, this.reflector, this.evaluator); - } - - if (metadata.queries) { - analysis.queries = queriesFromFields(metadata.queries, this.reflector, this.evaluator); - } - - if (metadata.host) { - analysis.host = extractHostBindings( - metadata.host, this.evaluator, this.isCore ? undefined : '@angular/core'); - } - - return {analysis}; - } - - compile(node: ClassDeclaration, analysis: R3BaseRefMetaData, pool: ConstantPool): - CompileResult[]|CompileResult { - const {expression, type} = compileBaseDefFromMetadata(analysis, pool, makeBindingParser()); - - return { - name: 'ngBaseDef', - initializer: expression, type, - statements: [], - }; - } -} - -export interface R3BaseRefDecoratorDetection { - inputs?: {property: ClassMember, decorator: Decorator}[]; - outputs?: {property: ClassMember, decorator: Decorator}[]; - viewQueries?: {member: ClassMember, decorators: Decorator[]}[]; - queries?: {member: ClassMember, decorators: Decorator[]}[]; - host?: ClassMember[]; -} diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts index b1e58f00b3a0f..efd45c5fb9053 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts @@ -19,16 +19,24 @@ import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFl import {compileNgFactoryDefField} from './factory'; import {generateSetClassMetadataCall} from './metadata'; -import {findAngularDecorator, getConstructorDependencies, readBaseClass, unwrapConstructorDependencies, unwrapExpression, unwrapForwardRef, validateConstructorDependencies} from './util'; +import {findAngularDecorator, getConstructorDependencies, isAngularDecorator, readBaseClass, unwrapConstructorDependencies, unwrapExpression, unwrapForwardRef, validateConstructorDependencies} from './util'; const EMPTY_OBJECT: {[key: string]: string} = {}; +const FIELD_DECORATORS = [ + 'Input', 'Output', 'ViewChild', 'ViewChildren', 'ContentChild', 'ContentChildren', 'HostBinding', + 'HostListener' +]; +const LIFECYCLE_HOOKS = new Set([ + 'ngOnChanges', 'ngOnInit', 'ngOnDestroy', 'ngDoCheck', 'ngAfterViewInit', 'ngAfterViewChecked', + 'ngAfterContentInit', 'ngAfterContentChecked' +]); export interface DirectiveHandlerData { meta: R3DirectiveMetadata; metadataStmt: Statement|null; } export class DirectiveDecoratorHandler implements - DecoratorHandler { + DecoratorHandler { constructor( private reflector: ReflectionHost, private evaluator: PartialEvaluator, private metaRegistry: MetadataRegistry, private defaultImportRecorder: DefaultImportRecorder, @@ -36,27 +44,41 @@ export class DirectiveDecoratorHandler implements readonly precedence = HandlerPrecedence.PRIMARY; - detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult|undefined { - if (!decorators) { + detect(node: ClassDeclaration, decorators: Decorator[]|null): + DetectResult|undefined { + // Compiling declaration files is invalid. + if (node.getSourceFile().isDeclarationFile) { return undefined; } - const decorator = findAngularDecorator(decorators, 'Directive', this.isCore); - if (decorator !== undefined) { - return { - trigger: decorator.node, - metadata: decorator, - }; + // If the class is undecorated, check if any of the fields have Angular decorators or lifecycle + // hooks, and if they do, label the class as an abstract directive. + if (!decorators) { + const angularField = this.reflector.getMembersOfClass(node).find(member => { + if (!member.isStatic && member.kind === ClassMemberKind.Method && + LIFECYCLE_HOOKS.has(member.name)) { + return true; + } + if (member.decorators) { + return member.decorators.some( + decorator => FIELD_DECORATORS.some( + decoratorName => isAngularDecorator(decorator, decoratorName, this.isCore))); + } + return false; + }); + return angularField ? {trigger: angularField.node, metadata: null} : undefined; } else { - return undefined; + const decorator = findAngularDecorator(decorators, 'Directive', this.isCore); + return decorator ? {trigger: decorator.node, metadata: decorator} : undefined; } } - analyze(node: ClassDeclaration, decorator: Decorator, flags = HandlerFlags.NONE): + analyze(node: ClassDeclaration, decorator: Decorator|null, flags = HandlerFlags.NONE): AnalysisOutput { const directiveResult = extractDirectiveMetadata( node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore, flags); const analysis = directiveResult && directiveResult.metadata; + if (analysis === undefined) { return {}; } @@ -112,15 +134,14 @@ export class DirectiveDecoratorHandler implements * the module. */ export function extractDirectiveMetadata( - clazz: ClassDeclaration, decorator: Decorator, reflector: ReflectionHost, + clazz: ClassDeclaration, decorator: Decorator | null, reflector: ReflectionHost, evaluator: PartialEvaluator, defaultImportRecorder: DefaultImportRecorder, isCore: boolean, flags: HandlerFlags, defaultSelector: string | null = null): { decorator: Map, metadata: R3DirectiveMetadata, - decoratedElements: ClassMember[], }|undefined { let directive: Map; - if (decorator.args === null || decorator.args.length === 0) { + if (decorator === null || decorator.args === null || decorator.args.length === 0) { directive = new Map(); } else if (decorator.args.length !== 1) { throw new FatalDiagnosticError( @@ -256,7 +277,7 @@ export function extractDirectiveMetadata( typeArgumentCount: reflector.getGenericArityOfClass(clazz) || 0, typeSourceSpan: EMPTY_SOURCE_SPAN, usesInheritance, exportAs, providers }; - return {decoratedElements, decorator: directive, metadata}; + return {decorator: directive, metadata}; } export function extractQueryMetadata( diff --git a/packages/compiler-cli/src/ngtsc/program.ts b/packages/compiler-cli/src/ngtsc/program.ts index d7cd49ea0bc52..96e571e81e781 100644 --- a/packages/compiler-cli/src/ngtsc/program.ts +++ b/packages/compiler-cli/src/ngtsc/program.ts @@ -14,7 +14,6 @@ import {nocollapseHack} from '../transformers/nocollapse_hack'; import {verifySupportedTypeScriptVersion} from '../typescript_support'; import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, NoopReferencesRegistry, PipeDecoratorHandler, ReferencesRegistry} from './annotations'; -import {BaseDefDecoratorHandler} from './annotations/src/base_def'; import {CycleAnalyzer, ImportGraph} from './cycles'; import {ErrorCode, ngErrorCode} from './diagnostics'; import {FlatIndexGenerator, ReferenceGraph, checkForPrivateExports, findFlatIndexEntryPoint} from './entry_point'; @@ -587,7 +586,6 @@ export class NgtscProgram implements api.Program { // Set up the IvyCompilation, which manages state for the Ivy transformer. const handlers = [ - new BaseDefDecoratorHandler(this.reflector, evaluator, this.isCore), new ComponentDecoratorHandler( this.reflector, evaluator, metaRegistry, this.metaReader !, scopeReader, scopeRegistry, this.isCore, this.resourceManager, this.rootDirs, diff --git a/packages/compiler-cli/src/transformers/nocollapse_hack.ts b/packages/compiler-cli/src/transformers/nocollapse_hack.ts index d889704f9da18..59cd5de80efe0 100644 --- a/packages/compiler-cli/src/transformers/nocollapse_hack.ts +++ b/packages/compiler-cli/src/transformers/nocollapse_hack.ts @@ -19,7 +19,6 @@ // Pattern matching all Render3 property names. const R3_DEF_NAME_PATTERN = [ - 'ngBaseDef', 'ɵcmp', 'ɵdir', 'ɵprov', 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 1bbc06126e581..35d449e44ec80 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -2973,7 +2973,7 @@ describe('compiler compliance', () => { ` }; - it('should add ngBaseDef if one or more @Input is present', () => { + it('should add an abstract directive if one or more @Input is present', () => { const files = { app: { 'spec.ts': ` @@ -3002,7 +3002,9 @@ describe('compiler compliance', () => { }; const expectedOutput = ` // ... - BaseClass.ngBaseDef = i0.ɵɵdefineBase({ + BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ + type: BaseClass, + selectors: [], inputs: { input1: "input1", input2: ["alias2", "input2"] @@ -3014,7 +3016,7 @@ describe('compiler compliance', () => { expectEmit(result.source, expectedOutput, 'Invalid base definition'); }); - it('should add ngBaseDef if one or more @Output is present', () => { + it('should add an abstract directive if one or more @Output is present', () => { const files = { app: { 'spec.ts': ` @@ -3048,7 +3050,9 @@ describe('compiler compliance', () => { }; const expectedOutput = ` // ... - BaseClass.ngBaseDef = i0.ɵɵdefineBase({ + BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ + type: BaseClass, + selectors: [], outputs: { output1: "output1", output2: "output2" @@ -3060,10 +3064,11 @@ describe('compiler compliance', () => { expectEmit(result.source, expectedOutput, 'Invalid base definition'); }); - it('should add ngBaseDef if a mixture of @Input and @Output props are present', () => { - const files = { - app: { - 'spec.ts': ` + it('should add an abstract directive if a mixture of @Input and @Output props are present', + () => { + const files = { + app: { + 'spec.ts': ` import {Component, NgModule, Input, Output, EventEmitter} from '@angular/core'; export class BaseClass { @Output() @@ -3096,11 +3101,13 @@ describe('compiler compliance', () => { }) export class MyModule {} ` - } - }; - const expectedOutput = ` + } + }; + const expectedOutput = ` // ... - BaseClass.ngBaseDef = i0.ɵɵdefineBase({ + BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ + type: BaseClass, + selectors: [], inputs: { input1: "input1", input2: ["whatever", "input2"] @@ -3112,11 +3119,11 @@ describe('compiler compliance', () => { }); // ... `; - const result = compile(files, angularFiles); - expectEmit(result.source, expectedOutput, 'Invalid base definition'); - }); + const result = compile(files, angularFiles); + expectEmit(result.source, expectedOutput, 'Invalid base definition'); + }); - it('should add ngBaseDef if a ViewChild query is present', () => { + it('should add an abstract directive if a ViewChild query is present', () => { const files = { app: { 'spec.ts': ` @@ -3142,8 +3149,10 @@ describe('compiler compliance', () => { const expectedOutput = ` const $e0_attrs$ = ["something"]; // ... - BaseClass.ngBaseDef = i0.ɵɵdefineBase({ - viewQuery: function (rf, ctx) { + BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ + type: BaseClass, + selectors: [], + viewQuery: function BaseClass_Query(rf, ctx) { if (rf & 1) { $r3$.ɵɵviewQuery($e0_attrs$, true); } @@ -3159,7 +3168,7 @@ describe('compiler compliance', () => { expectEmit(result.source, expectedOutput, 'Invalid base definition'); }); - it('should add ngBaseDef if a ViewChildren query is present', () => { + it('should add an abstract directive if a ViewChildren query is present', () => { const files = { app: { ...directive, @@ -3187,8 +3196,10 @@ describe('compiler compliance', () => { }; const expectedOutput = ` // ... - BaseClass.ngBaseDef = i0.ɵɵdefineBase({ - viewQuery: function (rf, ctx) { + BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ + type: BaseClass, + selectors: [], + viewQuery: function BaseClass_Query(rf, ctx) { if (rf & 1) { $r3$.ɵɵviewQuery(SomeDirective, true); } @@ -3204,7 +3215,7 @@ describe('compiler compliance', () => { expectEmit(result.source, expectedOutput, 'Invalid base definition'); }); - it('should add ngBaseDef if a ContentChild query is present', () => { + it('should add an abstract directive if a ContentChild query is present', () => { const files = { app: { 'spec.ts': ` @@ -3230,8 +3241,10 @@ describe('compiler compliance', () => { const expectedOutput = ` const $e0_attrs$ = ["something"]; // ... - BaseClass.ngBaseDef = i0.ɵɵdefineBase({ - contentQueries: function (rf, ctx, dirIndex) { + BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ + type: BaseClass, + selectors: [], + contentQueries: function BaseClass_ContentQueries(rf, ctx, dirIndex) { if (rf & 1) { $r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, true); } @@ -3247,7 +3260,7 @@ describe('compiler compliance', () => { expectEmit(result.source, expectedOutput, 'Invalid base definition'); }); - it('should add ngBaseDef if a ContentChildren query is present', () => { + it('should add an abstract directive if a ContentChildren query is present', () => { const files = { app: { ...directive, @@ -3275,8 +3288,10 @@ describe('compiler compliance', () => { }; const expectedOutput = ` // ... - BaseClass.ngBaseDef = i0.ɵɵdefineBase({ - contentQueries: function (rf, ctx, dirIndex) { + BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ + type: BaseClass, + selectors: [], + contentQueries: function BaseClass_ContentQueries(rf, ctx, dirIndex) { if (rf & 1) { $r3$.ɵɵcontentQuery(dirIndex, SomeDirective, false); } @@ -3292,7 +3307,7 @@ describe('compiler compliance', () => { expectEmit(result.source, expectedOutput, 'Invalid base definition'); }); - it('should add ngBaseDef if a host binding is present', () => { + it('should add an abstract directive if a host binding is present', () => { const files = { app: { 'spec.ts': ` @@ -3318,8 +3333,10 @@ describe('compiler compliance', () => { }; const expectedOutput = ` // ... - BaseClass.ngBaseDef = $r3$.ɵɵdefineBase({ - hostBindings: function (rf, ctx, elIndex) { + BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ + type: BaseClass, + selectors: [], + hostBindings: function BaseClass_HostBindings(rf, ctx, elIndex) { if (rf & 1) { $r3$.ɵɵallocHostVars(1); } @@ -3334,7 +3351,7 @@ describe('compiler compliance', () => { expectEmit(result.source, expectedOutput, 'Invalid base definition'); }); - it('should add ngBaseDef if a host listener is present', () => { + it('should add an abstract directive if a host listener is present', () => { const files = { app: { 'spec.ts': ` @@ -3360,10 +3377,12 @@ describe('compiler compliance', () => { }; const expectedOutput = ` // ... - BaseClass.ngBaseDef = $r3$.ɵɵdefineBase({ - hostBindings: function (rf, ctx, elIndex) { + BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ + type: BaseClass, + selectors: [], + hostBindings: function BaseClass_HostBindings(rf, ctx, elIndex) { if (rf & 1) { - $r3$.ɵɵlistener("mousedown", function ($event) { + $r3$.ɵɵlistener("mousedown", function BaseClass_mousedown_HostBindingHandler($event) { return ctx.handleMousedown($event); }); } @@ -3375,7 +3394,79 @@ describe('compiler compliance', () => { expectEmit(result.source, expectedOutput, 'Invalid base definition'); }); - it('should NOT add ngBaseDef if @Component is present', () => { + it('should add an abstract directive when using any lifecycle hook', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule, Input} from '@angular/core'; + export class BaseClass { + ngAfterContentChecked() {} + } + + @Component({ + selector: 'my-component', + template: \`
{{input1}} {{input2}}
\` + }) + export class MyComponent extends BaseClass { + } + + @NgModule({ + declarations: [MyComponent] + }) + export class MyModule {} + ` + } + }; + const expectedOutput = ` + // ... + BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ + type: BaseClass, + selectors: [] + }); + // ... + `; + const result = compile(files, angularFiles); + expectEmit(result.source, expectedOutput, 'Invalid base definition'); + }); + + + it('should add an abstract directive when using ngOnChanges', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule, Input} from '@angular/core'; + export class BaseClass { + ngOnChanges() {} + } + + @Component({ + selector: 'my-component', + template: \`
{{input1}} {{input2}}
\` + }) + export class MyComponent extends BaseClass { + } + + @NgModule({ + declarations: [MyComponent] + }) + export class MyModule {} + ` + } + }; + const expectedOutput = ` + // ... + BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ + type: BaseClass, + selectors: [], + features: [$r3$.ɵɵNgOnChangesFeature()] + }); + // ... + `; + const result = compile(files, angularFiles); + expectEmit(result.source, expectedOutput, 'Invalid base definition'); + }); + + it('should NOT add an abstract directive if @Component is present', () => { const files = { app: { 'spec.ts': ` @@ -3411,10 +3502,10 @@ describe('compiler compliance', () => { } }; const result = compile(files, angularFiles); - expect(result.source).not.toContain('ngBaseDef'); + expect(result.source).not.toContain('ɵdir'); }); - it('should NOT add ngBaseDef if @Directive is present', () => { + it('should NOT add an abstract directive if @Directive is present', () => { const files = { app: { 'spec.ts': ` @@ -3449,7 +3540,7 @@ describe('compiler compliance', () => { } }; const result = compile(files, angularFiles); - expect(result.source).not.toContain('ngBaseDef'); + expect(result.source.match(/ɵdir/g) !.length).toBe(1); }); }); }); diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_input_outputs_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_input_outputs_spec.ts index 7c577c8fb8187..b77b35449eae3 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_input_outputs_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_input_outputs_spec.ts @@ -16,7 +16,7 @@ describe('compiler compliance: listen()', () => { compileAnimations: false, }); - it('should create declare inputs/outputs', () => { + it('should declare inputs/outputs', () => { const files = { app: { 'spec.ts': ` diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 39e1daef3f3f1..c203010f4f60b 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -416,7 +416,7 @@ runInEachFileSystem(os => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain('TestBase.ngBaseDef = i0.ɵɵdefineBase'); + expect(jsContents).toContain('TestBase.ɵdir = i0.ɵɵdefineDirective'); expect(jsContents).toContain('TestComponent.ɵcmp = i0.ɵɵdefineComponent'); expect(jsContents).toContain('TestDirective.ɵdir = i0.ɵɵdefineDirective'); expect(jsContents).toContain('TestPipe.ɵpipe = i0.ɵɵdefinePipe'); @@ -503,7 +503,7 @@ runInEachFileSystem(os => { expect(jsContents).toContain('background-color: blue'); }); - it('should include generic type for ngBaseDef declarations', () => { + it('should include generic type for undecorated class declarations', () => { env.write('test.ts', ` import {Component, Input, NgModule} from '@angular/core'; @@ -515,10 +515,14 @@ runInEachFileSystem(os => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain('i0.ɵɵdefineBase({ inputs: { input: "input" } });'); + expect(jsContents) + .toContain( + 'i0.ɵɵdefineDirective({ type: TestBase, selectors: [], inputs: { input: "input" } });'); const dtsContents = env.getContents('test.d.ts'); - expect(dtsContents).toContain('static ngBaseDef: i0.ɵɵBaseDef'); + expect(dtsContents) + .toContain( + `static ɵdir: i0.ɵɵDirectiveDefWithMeta;`); }); it('should compile NgModules without errors', () => { @@ -1104,7 +1108,7 @@ runInEachFileSystem(os => { })); env.write('index.ts', ` import {Directive} from '@angular/core'; - + @Directive() export class BaseClass {} `); @@ -1115,10 +1119,10 @@ runInEachFileSystem(os => { env.write('index.ts', ` import {NgModule, Directive} from '@angular/core'; import {BaseClass} from 'lib1_built'; - + @Directive({selector: 'my-dir'}) export class MyDirective extends BaseClass {} - + @NgModule({declarations: [MyDirective]}) export class AppModule {} `); @@ -2615,7 +2619,7 @@ runInEachFileSystem(os => { env.write('test.ts', `/** I am a top-level comment. */ import {NgModule} from '@angular/core'; - + @NgModule({}) export class TestModule {} `); @@ -2634,7 +2638,7 @@ runInEachFileSystem(os => { env.write('my-module.ts', ` import {NgModule} from '@angular/core'; - + @NgModule({}) export class MyModule {} `); @@ -2647,7 +2651,7 @@ runInEachFileSystem(os => { env.write('test.ts', ` import {NgModule} from '@angular/core'; - + @NgModule({}) export class TestModule {} `); @@ -2673,9 +2677,9 @@ runInEachFileSystem(os => { it('should generate a summary stub for decorated classes in the input file only', () => { env.write('test.ts', ` import {Injectable, NgModule} from '@angular/core'; - + export class NotAModule {} - + @NgModule({}) export class TestModule {} `); @@ -2689,10 +2693,10 @@ runInEachFileSystem(os => { it('should generate a summary stub for classes exported via exports', () => { env.write('test.ts', ` import {Injectable, NgModule} from '@angular/core'; - + @NgModule({}) class NotDirectlyExported {} - + export {NotDirectlyExported}; `); @@ -3597,7 +3601,7 @@ runInEachFileSystem(os => { it('should not re-export a directive that\'s not exported from the NgModule', () => { env.write('dir.ts', ` import {Directive} from '@angular/core'; - + @Directive({ selector: 'dir', }) @@ -3606,7 +3610,7 @@ runInEachFileSystem(os => { env.write('module.ts', ` import {NgModule} from '@angular/core'; import {Dir} from './dir'; - + @NgModule({ declarations: [Dir], exports: [], @@ -3655,11 +3659,11 @@ runInEachFileSystem(os => { it('should not re-export a directive from an exported, external NgModule', () => { env.write(`node_modules/external/index.d.ts`, ` import {ɵɵDirectiveDefWithMeta, ɵɵNgModuleDefWithMeta} from '@angular/core'; - + export declare class ExternalDir { static ɵdir: ɵɵDirectiveDefWithMeta; } - + export declare class ExternalModule { static ɵmod: ɵɵNgModuleDefWithMeta; } @@ -3719,7 +3723,7 @@ runInEachFileSystem(os => { () => { env.write('dir.ts', ` import {Directive} from '@angular/core'; - + @Directive({ selector: 'dir', }) @@ -3727,7 +3731,7 @@ runInEachFileSystem(os => { `); env.write('dir2.ts', ` import {Directive} from '@angular/core'; - + @Directive({ selector: 'dir', }) @@ -3737,7 +3741,7 @@ runInEachFileSystem(os => { import {NgModule} from '@angular/core'; import {Dir} from './dir'; import {Dir as Dir2} from './dir2'; - + @NgModule({ declarations: [Dir, Dir2], exports: [Dir, Dir2], @@ -3755,7 +3759,7 @@ runInEachFileSystem(os => { it('should choose a re-exported symbol if one is present', () => { env.write(`node_modules/external/dir.d.ts`, ` import {ɵɵDirectiveDefWithMeta} from '@angular/core'; - + export declare class ExternalDir { static ɵdir: ɵɵDirectiveDefWithMeta; } @@ -3763,23 +3767,23 @@ runInEachFileSystem(os => { env.write('node_modules/external/module.d.ts', ` import {ɵɵNgModuleDefWithMeta} from '@angular/core'; import {ExternalDir} from './dir'; - + export declare class ExternalModule { static ɵmod: ɵɵNgModuleDefWithMeta; } - + export {ExternalDir as ɵngExportɵExternalModuleɵExternalDir}; `); env.write('test.ts', ` import {Component, Directive, NgModule} from '@angular/core'; import {ExternalModule} from 'external/module'; - + @Component({ selector: 'test-cmp', template: '
', }) class Cmp {} - + @NgModule({ declarations: [Cmp], imports: [ExternalModule], diff --git a/packages/compiler/src/compiler.ts b/packages/compiler/src/compiler.ts index d26b1c65ad5f8..e286091e74399 100644 --- a/packages/compiler/src/compiler.ts +++ b/packages/compiler/src/compiler.ts @@ -101,7 +101,7 @@ export {compileInjector, compileNgModule, R3InjectorMetadata, R3NgModuleMetadata export {compilePipeFromMetadata, R3PipeMetadata} from './render3/r3_pipe_compiler'; export {makeBindingParser, parseTemplate, ParseTemplateOptions} from './render3/view/template'; export {R3Reference} from './render3/util'; -export {compileBaseDefFromMetadata, R3BaseRefMetaData, compileComponentFromMetadata, compileDirectiveFromMetadata, parseHostBindings, ParsedHostBindings, verifyHostBindings} from './render3/view/compiler'; +export {compileComponentFromMetadata, compileDirectiveFromMetadata, parseHostBindings, ParsedHostBindings, verifyHostBindings} from './render3/view/compiler'; export {publishFacade} from './jit_compiler_facade'; // This file only reexports content of the `src` folder. Keep it that way. diff --git a/packages/compiler/src/compiler_facade_interface.ts b/packages/compiler/src/compiler_facade_interface.ts index f8231fccf46c2..c084bed77b443 100644 --- a/packages/compiler/src/compiler_facade_interface.ts +++ b/packages/compiler/src/compiler_facade_interface.ts @@ -37,8 +37,6 @@ export interface CompilerFacade { angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3DirectiveMetadataFacade): any; compileComponent( angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3ComponentMetadataFacade): any; - compileBase(angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3BaseMetadataFacade): - any; compileFactory( angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3FactoryDefMetadataFacade): any; @@ -160,16 +158,6 @@ export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade { changeDetection?: ChangeDetectionStrategy; } -export interface R3BaseMetadataFacade { - name: string; - type: any; - propMetadata: {[key: string]: any[]}; - inputs?: {[key: string]: string | [string, string]}; - outputs?: {[key: string]: string}; - queries?: R3QueryMetadataFacade[]; - viewQueries?: R3QueryMetadataFacade[]; -} - export interface R3FactoryDefMetadataFacade { name: string; type: any; diff --git a/packages/compiler/src/jit_compiler_facade.ts b/packages/compiler/src/jit_compiler_facade.ts index f01c9af701316..38ab7dba31e4f 100644 --- a/packages/compiler/src/jit_compiler_facade.ts +++ b/packages/compiler/src/jit_compiler_facade.ts @@ -7,7 +7,7 @@ */ -import {CompilerFacade, CoreEnvironment, ExportedCompilerFacade, R3BaseMetadataFacade, R3ComponentMetadataFacade, R3DependencyMetadataFacade, R3DirectiveMetadataFacade, R3FactoryDefMetadataFacade, R3InjectableMetadataFacade, R3InjectorMetadataFacade, R3NgModuleMetadataFacade, R3PipeMetadataFacade, R3QueryMetadataFacade, StringMap, StringMapWithRename} from './compiler_facade_interface'; +import {CompilerFacade, CoreEnvironment, ExportedCompilerFacade, R3ComponentMetadataFacade, R3DependencyMetadataFacade, R3DirectiveMetadataFacade, R3FactoryDefMetadataFacade, R3InjectableMetadataFacade, R3InjectorMetadataFacade, R3NgModuleMetadataFacade, R3PipeMetadataFacade, R3QueryMetadataFacade, StringMap, StringMapWithRename} from './compiler_facade_interface'; import {ConstantPool} from './constant_pool'; import {HostBinding, HostListener, Input, Output, Type} from './core'; import {Identifiers} from './identifiers'; @@ -22,7 +22,7 @@ import {R3InjectorMetadata, R3NgModuleMetadata, compileInjector, compileNgModule import {compilePipeFromMetadata} from './render3/r3_pipe_compiler'; import {R3Reference} from './render3/util'; import {R3DirectiveMetadata, R3QueryMetadata} from './render3/view/api'; -import {ParsedHostBindings, compileBaseDefFromMetadata, compileComponentFromMetadata, compileDirectiveFromMetadata, parseHostBindings, verifyHostBindings} from './render3/view/compiler'; +import {ParsedHostBindings, compileComponentFromMetadata, compileDirectiveFromMetadata, parseHostBindings, verifyHostBindings} from './render3/view/compiler'; import {makeBindingParser, parseTemplate} from './render3/view/template'; import {ResourceLoader} from './resource_loader'; import {DomElementSchemaRegistry} from './schema/dom_element_schema_registry'; @@ -169,24 +169,6 @@ export class CompilerFacadeImpl implements CompilerFacade { factoryRes.factory, angularCoreEnv, sourceMapUrl, factoryRes.statements); } - compileBase(angularCoreEnv: CoreEnvironment, sourceMapUrl: string, facade: R3BaseMetadataFacade): - any { - const constantPool = new ConstantPool(); - const typeSourceSpan = - this.createParseSourceSpan('Base', facade.name, `ng:///${facade.name}.js`); - const meta = { - ...facade, - typeSourceSpan, - viewQueries: facade.viewQueries ? facade.viewQueries.map(convertToR3QueryMetadata) : - facade.viewQueries, - queries: facade.queries ? facade.queries.map(convertToR3QueryMetadata) : facade.queries, - host: extractHostBindings(facade.propMetadata, typeSourceSpan) - }; - const res = compileBaseDefFromMetadata(meta, constantPool, makeBindingParser()); - return this.jitExpression( - res.expression, angularCoreEnv, sourceMapUrl, constantPool.statements); - } - createParseSourceSpan(kind: string, typeName: string, sourceUrl: string): ParseSourceSpan { return r3JitTypeSourceSpan(kind, typeName, sourceUrl); } diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index b525b14ec22c6..63f22321a5ba3 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -220,13 +220,6 @@ export class Identifiers { static resolveDocument: o.ExternalReference = {name: 'ɵɵresolveDocument', moduleName: CORE}; static resolveBody: o.ExternalReference = {name: 'ɵɵresolveBody', moduleName: CORE}; - static defineBase: o.ExternalReference = {name: 'ɵɵdefineBase', moduleName: CORE}; - - static BaseDef: o.ExternalReference = { - name: 'ɵɵBaseDef', - moduleName: CORE, - }; - static defineComponent: o.ExternalReference = {name: 'ɵɵdefineComponent', moduleName: CORE}; static setComponentScope: o.ExternalReference = {name: 'ɵɵsetComponentScope', moduleName: CORE}; diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 186eb8e0d7bbd..15880b8f2df78 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -129,61 +129,6 @@ export function compileDirectiveFromMetadata( return {expression, type}; } -export interface R3BaseRefMetaData { - name: string; - type: o.Expression; - typeSourceSpan: ParseSourceSpan; - inputs?: {[key: string]: string | [string, string]}; - outputs?: {[key: string]: string}; - viewQueries?: R3QueryMetadata[]; - queries?: R3QueryMetadata[]; - host?: R3HostMetadata; -} - -/** - * Compile a base definition for the render3 runtime as defined by {@link R3BaseRefMetadata} - * @param meta the metadata used for compilation. - */ -export function compileBaseDefFromMetadata( - meta: R3BaseRefMetaData, constantPool: ConstantPool, bindingParser: BindingParser) { - const definitionMap = new DefinitionMap(); - if (meta.inputs) { - const inputs = meta.inputs; - const inputsMap = Object.keys(inputs).map(key => { - const v = inputs[key]; - const value = Array.isArray(v) ? o.literalArr(v.map(vx => o.literal(vx))) : o.literal(v); - return {key, value, quoted: false}; - }); - definitionMap.set('inputs', o.literalMap(inputsMap)); - } - if (meta.outputs) { - const outputs = meta.outputs; - const outputsMap = Object.keys(outputs).map(key => { - const value = o.literal(outputs[key]); - return {key, value, quoted: false}; - }); - definitionMap.set('outputs', o.literalMap(outputsMap)); - } - if (meta.viewQueries && meta.viewQueries.length > 0) { - definitionMap.set('viewQuery', createViewQueriesFunction(meta.viewQueries, constantPool)); - } - if (meta.queries && meta.queries.length > 0) { - definitionMap.set('contentQueries', createContentQueriesFunction(meta.queries, constantPool)); - } - if (meta.host) { - definitionMap.set( - 'hostBindings', - createHostBindingsFunction( - meta.host, meta.typeSourceSpan, bindingParser, constantPool, meta.name)); - } - - const expression = o.importExpr(R3.defineBase).callFn([definitionMap.toLiteralMap()]); - const type = new o.ExpressionType( - o.importExpr(R3.BaseDef), /* modifiers */ null, [o.expressionType(meta.type)]); - - return {expression, type}; -} - /** * Compile a component for the render3 runtime as defined by the `R3ComponentMetadata`. */ diff --git a/packages/compiler/test/compiler_facade_interface_spec.ts b/packages/compiler/test/compiler_facade_interface_spec.ts index 506c14274aa5a..dcb97afd31932 100644 --- a/packages/compiler/test/compiler_facade_interface_spec.ts +++ b/packages/compiler/test/compiler_facade_interface_spec.ts @@ -103,10 +103,6 @@ const coreR3ComponentMetadataFacade: core.R3ComponentMetadataFacade = const compilerR3ComponentMetadataFacade: compiler.R3ComponentMetadataFacade = null !as core.R3ComponentMetadataFacade; -const coreR3BaseMetadataFacade: core.R3BaseMetadataFacade = null !as compiler.R3BaseMetadataFacade; -const compilerR3BaseMetadataFacade: compiler.R3BaseMetadataFacade = - null !as core.R3BaseMetadataFacade; - const coreViewEncapsulation: core.ViewEncapsulation = null !as compiler.ViewEncapsulation; const compilerViewEncapsulation: compiler.ViewEncapsulation = null !as core.ViewEncapsulation; diff --git a/packages/core/src/compiler/compiler_facade_interface.ts b/packages/core/src/compiler/compiler_facade_interface.ts index f8231fccf46c2..c084bed77b443 100644 --- a/packages/core/src/compiler/compiler_facade_interface.ts +++ b/packages/core/src/compiler/compiler_facade_interface.ts @@ -37,8 +37,6 @@ export interface CompilerFacade { angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3DirectiveMetadataFacade): any; compileComponent( angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3ComponentMetadataFacade): any; - compileBase(angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3BaseMetadataFacade): - any; compileFactory( angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3FactoryDefMetadataFacade): any; @@ -160,16 +158,6 @@ export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade { changeDetection?: ChangeDetectionStrategy; } -export interface R3BaseMetadataFacade { - name: string; - type: any; - propMetadata: {[key: string]: any[]}; - inputs?: {[key: string]: string | [string, string]}; - outputs?: {[key: string]: string}; - queries?: R3QueryMetadataFacade[]; - viewQueries?: R3QueryMetadataFacade[]; -} - export interface R3FactoryDefMetadataFacade { name: string; type: any; diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 39f8469bf195c..46198607fe6ec 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -18,7 +18,6 @@ export { ɵɵattributeInterpolate7, ɵɵattributeInterpolate8, ɵɵattributeInterpolateV, - ɵɵdefineBase, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵdefinePipe, @@ -153,7 +152,6 @@ export { ɵɵembeddedViewEnd, store as ɵstore, ɵɵpipe, - ɵɵBaseDef, ComponentDef as ɵComponentDef, ɵɵComponentDefWithMeta, ɵɵFactoryDef, @@ -227,7 +225,6 @@ export { NG_DIR_DEF as ɵNG_DIR_DEF, NG_PIPE_DEF as ɵNG_PIPE_DEF, NG_MOD_DEF as ɵNG_MOD_DEF, - NG_BASE_DEF as ɵNG_BASE_DEF } from './render3/fields'; export { diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index dd38d83e0d6dc..5ac3be85e8c9d 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -16,8 +16,8 @@ import {initNgDevMode} from '../util/ng_dev_mode'; import {stringify} from '../util/stringify'; import {EMPTY_ARRAY, EMPTY_OBJ} from './empty'; -import {NG_BASE_DEF, NG_COMP_DEF, NG_DIR_DEF, NG_FACTORY_DEF, NG_LOC_ID_DEF, NG_MOD_DEF, NG_PIPE_DEF} from './fields'; -import {ComponentDef, ComponentDefFeature, ComponentTemplate, ComponentType, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, DirectiveType, DirectiveTypesOrFactory, FactoryFn, HostBindingsFunction, PipeDef, PipeType, PipeTypesOrFactory, ViewQueriesFunction, ɵɵBaseDef} from './interfaces/definition'; +import {NG_COMP_DEF, NG_DIR_DEF, NG_FACTORY_DEF, NG_LOC_ID_DEF, NG_MOD_DEF, NG_PIPE_DEF} from './fields'; +import {ComponentDef, ComponentDefFeature, ComponentTemplate, ComponentType, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, DirectiveTypesOrFactory, FactoryFn, HostBindingsFunction, PipeDef, PipeType, PipeTypesOrFactory, ViewQueriesFunction} from './interfaces/definition'; import {TAttributes} from './interfaces/node'; // while SelectorFlags is unused here, it's required so that types don't get resolved lazily // see: https://github.com/Microsoft/web-build-tools/issues/1050 @@ -484,107 +484,6 @@ function invertObject( return newLookup; } -/** - * Create a base definition - * - * # Example - * ```ts - * class ShouldBeInherited { - * static ngBaseDef = ɵɵdefineBase({ - * ... - * }) - * } - * ``` - * - * @param baseDefinition The base definition parameters - * - * @codeGenApi - */ -export function ɵɵdefineBase(baseDefinition: { - /** - * A map of input names. - * - * The format is in: `{[actualPropertyName: string]:(string|[string, string])}`. - * - * Given: - * ``` - * class MyComponent { - * @Input() - * publicInput1: string; - * - * @Input('publicInput2') - * declaredInput2: string; - * } - * ``` - * - * is described as: - * ``` - * { - * publicInput1: 'publicInput1', - * declaredInput2: ['declaredInput2', 'publicInput2'], - * } - * ``` - * - * Which the minifier may translate to: - * ``` - * { - * minifiedPublicInput1: 'publicInput1', - * minifiedDeclaredInput2: [ 'declaredInput2', 'publicInput2'], - * } - * ``` - * - * This allows the render to re-construct the minified, public, and declared names - * of properties. - * - * NOTE: - * - Because declared and public name are usually same we only generate the array - * `['declared', 'public']` format when they differ. - * - The reason why this API and `outputs` API is not the same is that `NgOnChanges` has - * inconsistent behavior in that it uses declared names rather than minified or public. For - * this reason `NgOnChanges` will be deprecated and removed in future version and this - * API will be simplified to be consistent with `outputs`. - */ - inputs?: {[P in keyof T]?: string | [string, string]}; - - /** - * A map of output names. - * - * The format is in: `{[actualPropertyName: string]:string}`. - * - * Which the minifier may translate to: `{[minifiedPropertyName: string]:string}`. - * - * This allows the render to re-construct the minified and non-minified names - * of properties. - */ - outputs?: {[P in keyof T]?: string}; - - /** - * Function to create instances of content queries associated with a given directive. - */ - contentQueries?: ContentQueriesFunction| null; - - /** - * Additional set of instructions specific to view query processing. This could be seen as a - * set of instructions to be inserted into the template function. - */ - viewQuery?: ViewQueriesFunction| null; - - /** - * Function executed by the parent template to allow children to apply host bindings. - */ - hostBindings?: HostBindingsFunction; -}): ɵɵBaseDef { - const declaredInputs: {[P in keyof T]: string} = {} as any; - return { - inputs: invertObject(baseDefinition.inputs as any, declaredInputs), - declaredInputs: declaredInputs, - outputs: invertObject(baseDefinition.outputs as any), - viewQuery: baseDefinition.viewQuery || null, - contentQueries: baseDefinition.contentQueries || null, - hostBindings: baseDefinition.hostBindings || null - }; -} - /** * Create a directive definition object. * @@ -751,10 +650,6 @@ export function getPipeDef(type: any): PipeDef|null { return type[NG_PIPE_DEF] || null; } -export function getBaseDef(type: any): ɵɵBaseDef|null { - return type[NG_BASE_DEF] || null; -} - export function getFactoryDef(type: any, throwNotFound: true): FactoryFn; export function getFactoryDef(type: any): FactoryFn|null; export function getFactoryDef(type: any, throwNotFound?: boolean): FactoryFn|null { diff --git a/packages/core/src/render3/features/inherit_definition_feature.ts b/packages/core/src/render3/features/inherit_definition_feature.ts index 87c35e370b64c..faab82aab63d5 100644 --- a/packages/core/src/render3/features/inherit_definition_feature.ts +++ b/packages/core/src/render3/features/inherit_definition_feature.ts @@ -12,8 +12,6 @@ import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty'; import {ComponentDef, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, HostBindingsFunction, RenderFlags, ViewQueriesFunction} from '../interfaces/definition'; import {isComponentDef} from '../interfaces/type_checks'; -import {ɵɵNgOnChangesFeature} from './ng_onchanges_feature'; - export function getSuperType(type: Type): Type& {ɵcmp?: ComponentDef, ɵdir?: DirectiveDef} { return Object.getPrototypeOf(type.prototype).constructor; @@ -41,30 +39,14 @@ export function ɵɵInheritDefinitionFeature(definition: DirectiveDef| Comp superDef = superType.ɵdir; } - const baseDef = (superType as any).ngBaseDef; - - // Some fields in the definition may be empty, if there were no values to put in them that - // would've justified object creation. Unwrap them if necessary. - if (baseDef || superDef) { + if (superDef) { + // Some fields in the definition may be empty, if there were no values to put in them that + // would've justified object creation. Unwrap them if necessary. const writeableDef = definition as any; writeableDef.inputs = maybeUnwrapEmpty(definition.inputs); writeableDef.declaredInputs = maybeUnwrapEmpty(definition.declaredInputs); writeableDef.outputs = maybeUnwrapEmpty(definition.outputs); - } - - if (baseDef) { - const baseViewQuery = baseDef.viewQuery; - const baseContentQueries = baseDef.contentQueries; - const baseHostBindings = baseDef.hostBindings; - baseHostBindings && inheritHostBindings(definition, baseHostBindings); - baseViewQuery && inheritViewQuery(definition, baseViewQuery); - baseContentQueries && inheritContentQueries(definition, baseContentQueries); - fillProperties(definition.inputs, baseDef.inputs); - fillProperties(definition.declaredInputs, baseDef.declaredInputs); - fillProperties(definition.outputs, baseDef.outputs); - } - if (superDef) { // Merge hostBindings const superHostBindings = superDef.hostBindings; superHostBindings && inheritHostBindings(definition, superHostBindings); @@ -101,25 +83,6 @@ export function ɵɵInheritDefinitionFeature(definition: DirectiveDef| Comp } } } - } else { - // Even if we don't have a definition, check the type for the hooks and use those if need be - const superPrototype = superType.prototype; - if (superPrototype) { - definition.afterContentChecked = - definition.afterContentChecked || superPrototype.ngAfterContentChecked; - definition.afterContentInit = - definition.afterContentInit || superPrototype.ngAfterContentInit; - definition.afterViewChecked = - definition.afterViewChecked || superPrototype.ngAfterViewChecked; - definition.afterViewInit = definition.afterViewInit || superPrototype.ngAfterViewInit; - definition.doCheck = definition.doCheck || superPrototype.ngDoCheck; - definition.onDestroy = definition.onDestroy || superPrototype.ngOnDestroy; - definition.onInit = definition.onInit || superPrototype.ngOnInit; - - if (superPrototype.ngOnChanges) { - ɵɵNgOnChangesFeature()(definition); - } - } } superType = Object.getPrototypeOf(superType); diff --git a/packages/core/src/render3/fields.ts b/packages/core/src/render3/fields.ts index 299ab5b0358e6..4bc5e8031d98b 100644 --- a/packages/core/src/render3/fields.ts +++ b/packages/core/src/render3/fields.ts @@ -13,7 +13,6 @@ export const NG_DIR_DEF = getClosureSafeProperty({ɵdir: getClosureSafeProperty} export const NG_PIPE_DEF = getClosureSafeProperty({ɵpipe: getClosureSafeProperty}); export const NG_MOD_DEF = getClosureSafeProperty({ɵmod: getClosureSafeProperty}); export const NG_LOC_ID_DEF = getClosureSafeProperty({ɵloc: getClosureSafeProperty}); -export const NG_BASE_DEF = getClosureSafeProperty({ngBaseDef: getClosureSafeProperty}); export const NG_FACTORY_DEF = getClosureSafeProperty({ɵfac: getClosureSafeProperty}); /** diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index f63bbefaeb277..7baacc216e499 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ import {LifecycleHooksFeature, renderComponent, whenRendered} from './component'; -import {ɵɵdefineBase, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵdefineNgModule, ɵɵdefinePipe, ɵɵsetComponentScope, ɵɵsetNgModuleScope} from './definition'; +import {ɵɵdefineComponent, ɵɵdefineDirective, ɵɵdefineNgModule, ɵɵdefinePipe, ɵɵsetComponentScope, ɵɵsetNgModuleScope} from './definition'; import {ɵɵCopyDefinitionFeature} from './features/copy_definition_feature'; import {ɵɵInheritDefinitionFeature} from './features/inherit_definition_feature'; import {ɵɵNgOnChangesFeature} from './features/ng_onchanges_feature'; import {ɵɵProvidersFeature} from './features/providers_feature'; -import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, PipeDef, ɵɵBaseDef, ɵɵComponentDefWithMeta, ɵɵDirectiveDefWithMeta, ɵɵFactoryDef, ɵɵPipeDefWithMeta} from './interfaces/definition'; +import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, PipeDef, ɵɵComponentDefWithMeta, ɵɵDirectiveDefWithMeta, ɵɵFactoryDef, ɵɵPipeDefWithMeta} from './interfaces/definition'; import {getComponent, getDirectives, getHostElement, getRenderedText} from './util/discovery_utils'; export {ComponentFactory, ComponentFactoryResolver, ComponentRef, injectComponentFactoryResolver} from './component_ref'; @@ -99,7 +99,7 @@ export { ɵɵreference, - // TODO: remove `select` once we're refactored all of the tests not to use it. + // TODO: remove `select` once we've refactored all of the tests not to use it. ɵɵselect, ɵɵadvance, ɵɵstyleMap, @@ -202,7 +202,6 @@ export {ɵɵresolveWindow, ɵɵresolveDocument, ɵɵresolveBody} from './util/mi // clang-format on export { - ɵɵBaseDef, ComponentDef, ɵɵComponentDefWithMeta, ɵɵFactoryDef, @@ -221,7 +220,6 @@ export { ɵɵdefineComponent, ɵɵdefineDirective, ɵɵdefineNgModule, - ɵɵdefineBase, ɵɵdefinePipe, getHostElement, getComponent, diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index 676a7d17f57eb..89f0633eab5f5 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -97,17 +97,20 @@ export type ɵɵDirectiveDefWithMeta< OutputMap extends{[key: string]: string}, QueryFields extends string[]> = DirectiveDef; /** - * Runtime information for classes that are inherited by components or directives - * that aren't defined as components or directives. + * Runtime link information for Directives. * - * This is an internal data structure used by the renderer to determine what inputs - * and outputs should be inherited. + * This is an internal data structure used by the render to link + * directives into templates. * - * See: {@link defineBase} + * NOTE: Always use `defineDirective` function to create this object, + * never create the object directly since the shape of this object + * can change between versions. * - * @codeGenApi + * @param Selector type metadata specifying the selector of the directive or component + * + * See: {@link defineDirective} */ -export interface ɵɵBaseDef { +export interface DirectiveDef { /** * A dictionary mapping the inputs' minified property names to their public API names, which * are their aliases if any, or their original unminified property names @@ -144,23 +147,7 @@ export interface ɵɵBaseDef { * Refreshes host bindings on the associated directive. */ hostBindings: HostBindingsFunction|null; -} -/** - * Runtime link information for Directives. - * - * This is internal data structure used by the render to link - * directives into templates. - * - * NOTE: Always use `defineDirective` function to create this object, - * never create the object directly since the shape of this object - * can change between versions. - * - * @param Selector type metadata specifying the selector of the directive or component - * - * See: {@link defineDirective} - */ -export interface DirectiveDef extends ɵɵBaseDef { /** Token representing the directive. Used by DI. */ type: Type; @@ -219,7 +206,7 @@ export type ɵɵFactoryDef = () => T; /** * Runtime link information for Components. * - * This is internal data structure used by the render to link + * This is an internal data structure used by the render to link * components into templates. * * NOTE: Always use `defineComponent` function to create this object, @@ -331,7 +318,7 @@ export interface ComponentDef extends DirectiveDef { /** * Runtime link information for Pipes. * - * This is internal data structure used by the renderer to link + * This is an internal data structure used by the renderer to link * pipes into templates. * * NOTE: Always use `definePipe` function to create this object, diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts index 3948e5ea7787a..facb233292e39 100644 --- a/packages/core/src/render3/jit/directive.ts +++ b/packages/core/src/render3/jit/directive.ts @@ -7,7 +7,7 @@ */ import {R3DirectiveMetadataFacade, getCompilerFacade} from '../../compiler/compiler_facade'; -import {R3BaseMetadataFacade, R3ComponentMetadataFacade, R3QueryMetadataFacade} from '../../compiler/compiler_facade_interface'; +import {R3ComponentMetadataFacade, R3QueryMetadataFacade} from '../../compiler/compiler_facade_interface'; import {resolveForwardRef} from '../../di/forward_ref'; import {getReflect, reflectDependencies} from '../../di/jit/util'; import {Type} from '../../interface/type'; @@ -16,9 +16,9 @@ import {Component, Directive, Input} from '../../metadata/directives'; import {componentNeedsResolution, maybeQueueResolutionOfComponentResources} from '../../metadata/resource_loading'; import {ViewEncapsulation} from '../../metadata/view'; import {initNgDevMode} from '../../util/ng_dev_mode'; -import {getBaseDef, getComponentDef, getDirectiveDef} from '../definition'; +import {getComponentDef, getDirectiveDef} from '../definition'; import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty'; -import {NG_BASE_DEF, NG_COMP_DEF, NG_DIR_DEF, NG_FACTORY_DEF} from '../fields'; +import {NG_COMP_DEF, NG_DIR_DEF, NG_FACTORY_DEF} from '../fields'; import {ComponentType} from '../interfaces/definition'; import {stringifyForError} from '../util/misc_utils'; @@ -84,7 +84,7 @@ export function compileComponent(type: Type, metadata: Component): void { viewProviders: metadata.viewProviders || null, }; if (meta.usesInheritance) { - addBaseDefToUndecoratedParents(type); + addDirectiveDefToUndecoratedParents(type); } ngComponentDef = compiler.compileComponent(angularCoreEnv, templateUrl, meta); @@ -153,7 +153,7 @@ function getDirectiveMetadata(type: Type, metadata: Directive) { const facade = directiveMetadata(type as ComponentType, metadata); facade.typeSourceSpan = compiler.createParseSourceSpan('Directive', name, sourceMapUrl); if (facade.usesInheritance) { - addBaseDefToUndecoratedParents(type); + addDirectiveDefToUndecoratedParents(type); } return {metadata: facade, sourceMapUrl}; } @@ -202,7 +202,7 @@ export function directiveMetadata(type: Type, metadata: Directive): R3Direc inputs: metadata.inputs || EMPTY_ARRAY, outputs: metadata.outputs || EMPTY_ARRAY, queries: extractQueriesMetadata(type, propMetadata, isContentQuery), - lifecycle: {usesOnChanges: type.prototype.hasOwnProperty('ngOnChanges')}, + lifecycle: {usesOnChanges: usesLifecycleHook(type, 'ngOnChanges')}, typeSourceSpan: null !, usesInheritance: !extendsDirectlyFromObject(type), exportAs: extractExportAs(metadata.exportAs), @@ -212,76 +212,24 @@ export function directiveMetadata(type: Type, metadata: Directive): R3Direc } /** - * Adds an `ngBaseDef` to all parent classes of a type that don't have an Angular decorator. + * Adds a directive definition to all parent classes of a type that don't have an Angular decorator. */ -function addBaseDefToUndecoratedParents(type: Type) { +function addDirectiveDefToUndecoratedParents(type: Type) { const objPrototype = Object.prototype; let parent = Object.getPrototypeOf(type); // Go up the prototype until we hit `Object`. while (parent && parent !== objPrototype) { // Since inheritance works if the class was annotated already, we only need to add - // the base def if there are no annotations and the base def hasn't been created already. - if (!getDirectiveDef(parent) && !getComponentDef(parent) && !getBaseDef(parent)) { - const facade = extractBaseDefMetadata(parent); - facade && compileBase(parent, facade); + // the def if there are no annotations and the def hasn't been created already. + if (!getDirectiveDef(parent) && !getComponentDef(parent) && + shouldAddAbstractDirective(parent)) { + compileDirective(parent, null); } parent = Object.getPrototypeOf(parent); } } -/** Compiles the base metadata into a base definition. */ -function compileBase(type: Type, facade: R3BaseMetadataFacade): void { - let ngBaseDef: any = null; - Object.defineProperty(type, NG_BASE_DEF, { - get: () => { - if (ngBaseDef === null) { - const name = type && type.name; - const sourceMapUrl = `ng://${name}/ngBaseDef.js`; - const compiler = getCompilerFacade(); - ngBaseDef = compiler.compileBase(angularCoreEnv, sourceMapUrl, facade); - } - return ngBaseDef; - }, - // Make the property configurable in dev mode to allow overriding in tests - configurable: !!ngDevMode, - }); -} - -/** Extracts the metadata necessary to construct an `ngBaseDef` from a class. */ -function extractBaseDefMetadata(type: Type): R3BaseMetadataFacade|null { - const propMetadata = getReflect().ownPropMetadata(type); - const viewQueries = extractQueriesMetadata(type, propMetadata, isViewQuery); - const queries = extractQueriesMetadata(type, propMetadata, isContentQuery); - let inputs: {[key: string]: string | [string, string]}|undefined; - let outputs: {[key: string]: string}|undefined; - // We only need to know whether there are any HostListener or HostBinding - // decorators present, the parsing logic is in the compiler already. - let hasHostDecorators = false; - - for (const field in propMetadata) { - propMetadata[field].forEach(ann => { - const metadataName = ann.ngMetadataName; - if (metadataName === 'Input') { - inputs = inputs || {}; - inputs[field] = ann.bindingPropertyName ? [ann.bindingPropertyName, field] : field; - } else if (metadataName === 'Output') { - outputs = outputs || {}; - outputs[field] = ann.bindingPropertyName || field; - } else if (metadataName === 'HostBinding' || metadataName === 'HostListener') { - hasHostDecorators = true; - } - }); - } - - // Only generate the base def if there's any info inside it. - if (inputs || outputs || viewQueries.length || queries.length || hasHostDecorators) { - return {name: type.name, type, inputs, outputs, viewQueries, queries, propMetadata}; - } - - return null; -} - function convertToR3QueryPredicate(selector: any): any|string[] { return typeof selector === 'string' ? splitByComma(selector) : resolveForwardRef(selector); } @@ -310,7 +258,7 @@ function extractQueriesMetadata( `Can't construct a query for the property "${field}" of ` + `"${stringifyForError(type)}" since the query selector wasn't defined.`); } - if (annotations.some(isInputAnn)) { + if (annotations.some(isInputAnnotation)) { throw new Error(`Cannot combine @Input decorators with query decorators`); } queriesMeta.push(convertToR3QueryMetadata(field, ann)); @@ -322,11 +270,7 @@ function extractQueriesMetadata( } function extractExportAs(exportAs: string | undefined): string[]|null { - if (exportAs === undefined) { - return null; - } - - return exportAs.split(',').map(part => part.trim()); + return exportAs === undefined ? null : splitByComma(exportAs); } function isContentQuery(value: any): value is Query { @@ -339,10 +283,45 @@ function isViewQuery(value: any): value is Query { return name === 'ViewChild' || name === 'ViewChildren'; } -function isInputAnn(value: any): value is Input { +function isInputAnnotation(value: any): value is Input { return value.ngMetadataName === 'Input'; } function splitByComma(value: string): string[] { return value.split(',').map(piece => piece.trim()); } + +function usesLifecycleHook(type: Type, name: string): boolean { + const prototype = type.prototype; + return prototype && prototype.hasOwnProperty(name); +} + +const LIFECYCLE_HOOKS = [ + 'ngOnChanges', 'ngOnInit', 'ngOnDestroy', 'ngDoCheck', 'ngAfterViewInit', 'ngAfterViewChecked', + 'ngAfterContentInit', 'ngAfterContentChecked' +]; + +function shouldAddAbstractDirective(type: Type): boolean { + if (LIFECYCLE_HOOKS.some(hookName => usesLifecycleHook(type, hookName))) { + return true; + } + + const propMetadata = getReflect().ownPropMetadata(type); + + for (const field in propMetadata) { + const annotations = propMetadata[field]; + + for (let i = 0; i < annotations.length; i++) { + const current = annotations[i]; + const metadataName = current.ngMetadataName; + + if (isInputAnnotation(current) || isContentQuery(current) || isViewQuery(current) || + metadataName === 'Output' || metadataName === 'HostBinding' || + metadataName === 'HostListener') { + return true; + } + } + } + + return false; +} diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index 6503cd3fefe0a..bab9d77657dde 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -30,7 +30,6 @@ export const angularCoreEnv: {[name: string]: Function} = 'ɵɵattributeInterpolate7': r3.ɵɵattributeInterpolate7, 'ɵɵattributeInterpolate8': r3.ɵɵattributeInterpolate8, 'ɵɵattributeInterpolateV': r3.ɵɵattributeInterpolateV, - 'ɵɵdefineBase': r3.ɵɵdefineBase, 'ɵɵdefineComponent': r3.ɵɵdefineComponent, 'ɵɵdefineDirective': r3.ɵɵdefineDirective, 'ɵɵdefineInjectable': ɵɵdefineInjectable, diff --git a/packages/core/test/acceptance/inherit_definition_feature_spec.ts b/packages/core/test/acceptance/inherit_definition_feature_spec.ts index 682cde4e34e6d..e963049643ab9 100644 --- a/packages/core/test/acceptance/inherit_definition_feature_spec.ts +++ b/packages/core/test/acceptance/inherit_definition_feature_spec.ts @@ -214,6 +214,37 @@ describe('inheritance', () => { expect(log).toEqual(['on changes!']); }); + + it('should be inherited from undecorated super class which inherits from decorated one', () => { + let changes = 0; + + abstract class Base { + // Add an Input so that we have at least one Angular decorator on a class field. + @Input() inputBase: any; + abstract input: any; + } + + abstract class UndecoratedBase extends Base { + abstract input: any; + ngOnChanges() { changes++; } + } + + @Component({selector: 'my-comp', template: ''}) + class MyComp extends UndecoratedBase { + @Input() input: any; + } + + @Component({template: ''}) + class App { + value = 'hello'; + } + + TestBed.configureTestingModule({declarations: [MyComp, App]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(changes).toBe(1); + }); }); describe('of bare super class by a directive', () => { diff --git a/packages/core/test/render3/jit_environment_spec.ts b/packages/core/test/render3/jit_environment_spec.ts index 22af48df2720e..fbc1f984ab677 100644 --- a/packages/core/test/render3/jit_environment_spec.ts +++ b/packages/core/test/render3/jit_environment_spec.ts @@ -12,7 +12,6 @@ import {Identifiers} from '@angular/compiler/src/render3/r3_identifiers'; import {angularCoreEnv} from '../../src/render3/jit/environment'; const INTERFACE_EXCEPTIONS = new Set([ - 'ɵɵBaseDef', 'ɵɵComponentDefWithMeta', 'ɵɵDirectiveDefWithMeta', 'ɵɵInjectorDef', diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index cbb3f6e4b4e12..2b75ccae93f33 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -701,21 +701,6 @@ export declare function ɵɵattributeInterpolate8(attrName: string, prefix: stri export declare function ɵɵattributeInterpolateV(attrName: string, values: any[], sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009; -export interface ɵɵBaseDef { - contentQueries: ContentQueriesFunction | null; - /** @deprecated */ readonly declaredInputs: { - [P in keyof T]: string; - }; - hostBindings: HostBindingsFunction | null; - readonly inputs: { - [P in keyof T]: string; - }; - readonly outputs: { - [P in keyof T]: string; - }; - viewQuery: ViewQueriesFunction | null; -} - export declare function ɵɵclassMap(classes: { [className: string]: any; } | NO_CHANGE | string | null): void; @@ -760,18 +745,6 @@ export declare function ɵɵCopyDefinitionFeature(definition: DirectiveDef export declare const ɵɵdefaultStyleSanitizer: StyleSanitizeFn; -export declare function ɵɵdefineBase(baseDefinition: { - inputs?: { - [P in keyof T]?: string | [string, string]; - }; - outputs?: { - [P in keyof T]?: string; - }; - contentQueries?: ContentQueriesFunction | null; - viewQuery?: ViewQueriesFunction | null; - hostBindings?: HostBindingsFunction; -}): ɵɵBaseDef; - export declare function ɵɵdefineComponent(componentDefinition: { type: Type; selectors: CssSelectorList;