diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index 41546a186b..6286ddc9f6 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -17,8 +17,8 @@ import {Type} from '../type'; import {BaseDef, ComponentDefFeature, ComponentDefInternal, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDefFeature, DirectiveDefInternal, DirectiveType, DirectiveTypesOrFactory, PipeDefInternal, PipeType, PipeTypesOrFactory} from './interfaces/definition'; import {CssSelectorList, SelectorFlags} from './interfaces/projection'; -const EMPTY: {} = {}; -const EMPTY_ARRAY: any[] = []; +export const EMPTY: {} = {}; +export const EMPTY_ARRAY: any[] = []; if (typeof ngDevMode !== 'undefined' && ngDevMode) { Object.freeze(EMPTY); Object.freeze(EMPTY_ARRAY); diff --git a/packages/core/src/render3/features/inherit_definition_feature.ts b/packages/core/src/render3/features/inherit_definition_feature.ts index f28d1bc200..87118d300f 100644 --- a/packages/core/src/render3/features/inherit_definition_feature.ts +++ b/packages/core/src/render3/features/inherit_definition_feature.ts @@ -8,6 +8,7 @@ import {Type} from '../../type'; import {fillProperties} from '../../util/property'; +import {EMPTY, EMPTY_ARRAY} from '../definition'; import {ComponentDefInternal, ComponentTemplate, DirectiveDefFeature, DirectiveDefInternal, RenderFlags} from '../interfaces/definition'; @@ -47,6 +48,16 @@ export function InheritDefinitionFeature( } 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) { + const writeableDef = definition as any; + writeableDef.inputs = maybeUnwrapEmpty(definition.inputs); + writeableDef.declaredInputs = maybeUnwrapEmpty(definition.declaredInputs); + writeableDef.outputs = maybeUnwrapEmpty(definition.outputs); + } + if (baseDef) { // Merge inputs and outputs fillProperties(definition.inputs, baseDef.inputs); @@ -162,3 +173,15 @@ export function InheritDefinitionFeature( superType = Object.getPrototypeOf(superType); } } + +function maybeUnwrapEmpty(value: T[]): T[]; +function maybeUnwrapEmpty(value: T): T; +function maybeUnwrapEmpty(value: any): any { + if (value === EMPTY) { + return {}; + } else if (Array.isArray(value) && value === EMPTY_ARRAY) { + return []; + } else { + return value; + } +} diff --git a/packages/core/test/render3/Inherit_definition_feature_spec.ts b/packages/core/test/render3/Inherit_definition_feature_spec.ts index 3cdf5fe902..29f234e7ed 100644 --- a/packages/core/test/render3/Inherit_definition_feature_spec.ts +++ b/packages/core/test/render3/Inherit_definition_feature_spec.ts @@ -7,6 +7,7 @@ */ import {EventEmitter, Output} from '../../src/core'; +import {EMPTY} from '../../src/render3/definition'; import {InheritDefinitionFeature} from '../../src/render3/features/inherit_definition_feature'; import {ComponentDefInternal, DirectiveDefInternal, RenderFlags, defineBase, defineComponent, defineDirective} from '../../src/render3/index'; @@ -127,6 +128,40 @@ describe('InheritDefinitionFeature', () => { }); }); + it('should detect EMPTY inputs and outputs', () => { + class SuperDirective { + static ngDirectiveDef = defineDirective({ + inputs: { + testIn: 'testIn', + }, + outputs: { + testOut: 'testOut', + }, + type: SuperDirective, + selectors: [['', 'superDir', '']], + factory: () => new SuperDirective(), + }); + } + + class SubDirective extends SuperDirective { + static ngDirectiveDef = defineDirective({ + type: SubDirective, + selectors: [['', 'subDir', '']], + factory: () => new SubDirective(), + features: [InheritDefinitionFeature] + }); + } + + const subDef = SubDirective.ngDirectiveDef as DirectiveDefInternal; + + expect(subDef.inputs).toEqual({ + testIn: 'testIn', + }); + expect(subDef.outputs).toEqual({ + testOut: 'testOut', + }); + }); + it('should inherit inputs from ngBaseDefs along the way', () => { class Class5 {