feat(ivy): support inheriting input/output from bare base class (#25094)
PR Close #25094
This commit is contained in:
parent
6e2a1877ab
commit
64516da6b0
|
@ -11,6 +11,7 @@ import {Provider} from '../di';
|
|||
import {R3_COMPILE_COMPONENT, R3_COMPILE_DIRECTIVE, R3_COMPILE_PIPE} from '../ivy_switch';
|
||||
import {Type} from '../type';
|
||||
import {TypeDecorator, makeDecorator, makePropDecorator} from '../util/decorators';
|
||||
import {fillProperties} from '../util/property';
|
||||
import {ViewEncapsulation} from './view';
|
||||
|
||||
|
||||
|
@ -734,7 +735,7 @@ export interface Input {
|
|||
* selector: 'bank-account',
|
||||
* template: `
|
||||
* Bank Name: {{bankName}}
|
||||
* Account Id: {{id}}
|
||||
* Account Id: {{id}}
|
||||
* `
|
||||
* })
|
||||
* class BankAccount {
|
||||
|
@ -761,12 +762,47 @@ export interface Input {
|
|||
bindingPropertyName?: string;
|
||||
}
|
||||
|
||||
const initializeBaseDef = (target: any): void => {
|
||||
const constructor = target.constructor;
|
||||
const inheritedBaseDef = constructor.ngBaseDef;
|
||||
|
||||
const baseDef = constructor.ngBaseDef = {
|
||||
inputs: {},
|
||||
outputs: {},
|
||||
declaredInputs: {},
|
||||
};
|
||||
|
||||
if (inheritedBaseDef) {
|
||||
fillProperties(baseDef.inputs, inheritedBaseDef.inputs);
|
||||
fillProperties(baseDef.outputs, inheritedBaseDef.outputs);
|
||||
fillProperties(baseDef.declaredInputs, inheritedBaseDef.declaredInputs);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Does the work of creating the `ngBaseDef` property for the @Input and @Output decorators.
|
||||
* @param key "inputs" or "outputs"
|
||||
*/
|
||||
const updateBaseDefFromIOProp = (getProp: (baseDef: {inputs?: any, outputs?: any}) => any) =>
|
||||
(target: any, name: string, ...args: any[]) => {
|
||||
const constructor = target.constructor;
|
||||
|
||||
if (!constructor.hasOwnProperty('ngBaseDef')) {
|
||||
initializeBaseDef(target);
|
||||
}
|
||||
|
||||
const baseDef = constructor.ngBaseDef;
|
||||
const defProp = getProp(baseDef);
|
||||
defProp[name] = args[0];
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
export const Input: InputDecorator =
|
||||
makePropDecorator('Input', (bindingPropertyName?: string) => ({bindingPropertyName}));
|
||||
export const Input: InputDecorator = makePropDecorator(
|
||||
'Input', (bindingPropertyName?: string) => ({bindingPropertyName}), undefined,
|
||||
updateBaseDefFromIOProp(baseDef => baseDef.inputs || {}));
|
||||
|
||||
/**
|
||||
* Type of the Output decorator / constructor function.
|
||||
|
@ -800,8 +836,10 @@ export interface Output { bindingPropertyName?: string; }
|
|||
*
|
||||
* @Annotation
|
||||
*/
|
||||
export const Output: OutputDecorator =
|
||||
makePropDecorator('Output', (bindingPropertyName?: string) => ({bindingPropertyName}));
|
||||
export const Output: OutputDecorator = makePropDecorator(
|
||||
'Output', (bindingPropertyName?: string) => ({bindingPropertyName}), undefined,
|
||||
updateBaseDefFromIOProp(baseDef => baseDef.outputs || {}));
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,7 +16,7 @@ import {Type} from '../type';
|
|||
import {resolveRendererType2} from '../view/util';
|
||||
|
||||
import {diPublic} from './di';
|
||||
import {ComponentDefFeature, ComponentDefInternal, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDefFeature, DirectiveDefInternal, DirectiveDefListOrFactory, DirectiveType, DirectiveTypesOrFactory, PipeDefInternal, PipeType, PipeTypesOrFactory} from './interfaces/definition';
|
||||
import {BaseDef, ComponentDefFeature, ComponentDefInternal, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDefFeature, DirectiveDefInternal, DirectiveDefListOrFactory, DirectiveType, DirectiveTypesOrFactory, PipeDefInternal, PipeType, PipeTypesOrFactory} from './interfaces/definition';
|
||||
import {CssSelectorList, SelectorFlags} from './interfaces/projection';
|
||||
|
||||
|
||||
|
@ -353,6 +353,84 @@ function invertObject(obj: any, secondary?: any): any {
|
|||
return newLookup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a base definition
|
||||
*
|
||||
* # Example
|
||||
* ```
|
||||
* class ShouldBeInherited {
|
||||
* static ngBaseDef = defineBase({
|
||||
* ...
|
||||
* })
|
||||
* }
|
||||
* @param baseDefinition The base definition parameters
|
||||
*/
|
||||
export function defineBase<T>(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};
|
||||
}): BaseDef<T> {
|
||||
const declaredInputs: {[P in keyof T]: P} = {} as any;
|
||||
return {
|
||||
inputs: invertObject(baseDefinition.inputs, declaredInputs),
|
||||
declaredInputs: declaredInputs,
|
||||
outputs: invertObject(baseDefinition.outputs),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a directive definition object.
|
||||
*
|
||||
|
|
|
@ -7,22 +7,10 @@
|
|||
*/
|
||||
|
||||
import {Type} from '../../type';
|
||||
import {ComponentDefInternal, ComponentType, DirectiveDefFeature, DirectiveDefInternal} from '../interfaces/definition';
|
||||
import {fillProperties} from '../../util/property';
|
||||
import {ComponentDefInternal, DirectiveDefFeature, DirectiveDefInternal} from '../interfaces/definition';
|
||||
|
||||
|
||||
/**
|
||||
* Sets properties on a target object from a source object, but only if
|
||||
* the property doesn't already exist on the target object.
|
||||
* @param target The target to set properties on
|
||||
* @param source The source of the property keys and values to set
|
||||
*/
|
||||
function fillProperties(target: {[key: string]: string}, source: {[key: string]: string}) {
|
||||
for (const key in source) {
|
||||
if (source.hasOwnProperty(key) && !target.hasOwnProperty(key)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Determines if a definition is a {@link ComponentDefInternal} or a {@link DirectiveDefInternal}
|
||||
* @param definition The definition to examine
|
||||
|
@ -45,9 +33,9 @@ function getSuperType(type: Type<any>): Type<any>&
|
|||
export function InheritDefinitionFeature(
|
||||
definition: DirectiveDefInternal<any>| ComponentDefInternal<any>): void {
|
||||
let superType = getSuperType(definition.type);
|
||||
let superDef: DirectiveDefInternal<any>|ComponentDefInternal<any>|undefined = undefined;
|
||||
|
||||
while (superType && !superDef) {
|
||||
while (superType) {
|
||||
let superDef: DirectiveDefInternal<any>|ComponentDefInternal<any>|undefined = undefined;
|
||||
if (isComponentDef(definition)) {
|
||||
superDef = superType.ngComponentDef || superType.ngDirectiveDef;
|
||||
} else {
|
||||
|
@ -57,12 +45,15 @@ export function InheritDefinitionFeature(
|
|||
superDef = superType.ngDirectiveDef;
|
||||
}
|
||||
|
||||
if (superDef) {
|
||||
const baseDef = (superType as any).ngBaseDef;
|
||||
if (baseDef) {
|
||||
// Merge inputs and outputs
|
||||
fillProperties(definition.inputs, superDef.inputs);
|
||||
fillProperties(definition.declaredInputs, superDef.declaredInputs);
|
||||
fillProperties(definition.outputs, superDef.outputs);
|
||||
fillProperties(definition.inputs, baseDef.inputs);
|
||||
fillProperties(definition.declaredInputs, baseDef.declaredInputs);
|
||||
fillProperties(definition.outputs, baseDef.outputs);
|
||||
}
|
||||
|
||||
if (superDef) {
|
||||
// Merge hostBindings
|
||||
const prevHostBindings = definition.hostBindings;
|
||||
const superHostBindings = superDef.hostBindings;
|
||||
|
@ -77,6 +68,11 @@ export function InheritDefinitionFeature(
|
|||
}
|
||||
}
|
||||
|
||||
// Merge inputs and outputs
|
||||
fillProperties(definition.inputs, superDef.inputs);
|
||||
fillProperties(definition.declaredInputs, superDef.declaredInputs);
|
||||
fillProperties(definition.outputs, superDef.outputs);
|
||||
|
||||
// Inherit hooks
|
||||
// Assume super class inheritance feature has already run.
|
||||
definition.afterContentChecked =
|
||||
|
@ -97,6 +93,8 @@ export function InheritDefinitionFeature(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
} 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;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import {LifecycleHooksFeature, getHostElement, getRenderedText, renderComponent, whenRendered} from './component';
|
||||
import {defineComponent, defineDirective, defineNgModule, definePipe} from './definition';
|
||||
import {defineBase, defineComponent, defineDirective, defineNgModule, definePipe} from './definition';
|
||||
import {InheritDefinitionFeature} from './features/inherit_definition_feature';
|
||||
import {NgOnChangesFeature} from './features/ng_onchanges_feature';
|
||||
import {PublicFeature} from './features/public_feature';
|
||||
|
@ -164,6 +164,7 @@ export {
|
|||
defineComponent,
|
||||
defineDirective,
|
||||
defineNgModule,
|
||||
defineBase,
|
||||
definePipe,
|
||||
getHostElement,
|
||||
getRenderedText,
|
||||
|
|
|
@ -67,29 +67,15 @@ export interface PipeType<T> extends Type<T> { ngPipeDef: never; }
|
|||
export type DirectiveDefInternal<T> = DirectiveDef<T, string>;
|
||||
|
||||
/**
|
||||
* Runtime link information for Directives.
|
||||
* Runtime information for classes that are inherited by components or directives
|
||||
* that aren't defined as components or directives.
|
||||
*
|
||||
* This is internal data structure used by the render to link
|
||||
* directives into templates.
|
||||
* This is an internal data structure used by the render to determine what inputs
|
||||
* and outputs should be inherited.
|
||||
*
|
||||
* 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}
|
||||
* See: {@link defineBase}
|
||||
*/
|
||||
export interface DirectiveDef<T, Selector extends string> {
|
||||
/** Token representing the directive. Used by DI. */
|
||||
type: Type<T>;
|
||||
|
||||
/** Function that makes a directive public to the DI system. */
|
||||
diPublic: ((def: DirectiveDef<T, string>) => void)|null;
|
||||
|
||||
/** The selectors that will be used to match nodes to this directive. */
|
||||
selectors: CssSelectorList;
|
||||
|
||||
export interface BaseDef<T> {
|
||||
/**
|
||||
* 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
|
||||
|
@ -109,6 +95,31 @@ export interface DirectiveDef<T, Selector extends string> {
|
|||
* (as in `@Output('alias') propertyName: any;`).
|
||||
*/
|
||||
readonly outputs: {[P in keyof T]: P};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<T, Selector extends string> extends BaseDef<T> {
|
||||
/** Token representing the directive. Used by DI. */
|
||||
type: Type<T>;
|
||||
|
||||
/** Function that makes a directive public to the DI system. */
|
||||
diPublic: ((def: DirectiveDef<T, string>) => void)|null;
|
||||
|
||||
/** The selectors that will be used to match nodes to this directive. */
|
||||
selectors: CssSelectorList;
|
||||
|
||||
/**
|
||||
* Name under which the directive is exported (for use with local references in template)
|
||||
|
|
|
@ -41,31 +41,34 @@ export const PROP_METADATA = '__prop__metadata__';
|
|||
/**
|
||||
* @suppress {globalThis}
|
||||
*/
|
||||
export function makeDecorator(
|
||||
export function makeDecorator<T>(
|
||||
name: string, props?: (...args: any[]) => any, parentClass?: any,
|
||||
chainFn?: (fn: Function) => void, typeFn?: (type: Type<any>, ...args: any[]) => void):
|
||||
additionalProcessing?: (type: Type<T>) => void,
|
||||
typeFn?: (type: Type<T>, ...args: any[]) => void):
|
||||
{new (...args: any[]): any; (...args: any[]): any; (...args: any[]): (cls: any) => any;} {
|
||||
const metaCtor = makeMetadataCtor(props);
|
||||
|
||||
function DecoratorFactory(...args: any[]): (cls: any) => any {
|
||||
function DecoratorFactory(...args: any[]): (cls: Type<T>) => any {
|
||||
if (this instanceof DecoratorFactory) {
|
||||
metaCtor.call(this, ...args);
|
||||
return this;
|
||||
}
|
||||
|
||||
const annotationInstance = new (<any>DecoratorFactory)(...args);
|
||||
const TypeDecorator: TypeDecorator = <TypeDecorator>function TypeDecorator(cls: Type<any>) {
|
||||
typeFn && typeFn(cls, ...args);
|
||||
const annotationInstance = new (DecoratorFactory as any)(...args);
|
||||
return function TypeDecorator(cls: Type<T>) {
|
||||
if (typeFn) typeFn(cls, ...args);
|
||||
// Use of Object.defineProperty is important since it creates non-enumerable property which
|
||||
// prevents the property is copied during subclassing.
|
||||
const annotations = cls.hasOwnProperty(ANNOTATIONS) ?
|
||||
(cls as any)[ANNOTATIONS] :
|
||||
Object.defineProperty(cls, ANNOTATIONS, {value: []})[ANNOTATIONS];
|
||||
annotations.push(annotationInstance);
|
||||
|
||||
|
||||
if (additionalProcessing) additionalProcessing(cls);
|
||||
|
||||
return cls;
|
||||
};
|
||||
if (chainFn) chainFn(TypeDecorator);
|
||||
return TypeDecorator;
|
||||
}
|
||||
|
||||
if (parentClass) {
|
||||
|
@ -73,7 +76,7 @@ export function makeDecorator(
|
|||
}
|
||||
|
||||
DecoratorFactory.prototype.ngMetadataName = name;
|
||||
(<any>DecoratorFactory).annotationCls = DecoratorFactory;
|
||||
(DecoratorFactory as any).annotationCls = DecoratorFactory;
|
||||
return DecoratorFactory as any;
|
||||
}
|
||||
|
||||
|
@ -127,7 +130,8 @@ export function makeParamDecorator(
|
|||
}
|
||||
|
||||
export function makePropDecorator(
|
||||
name: string, props?: (...args: any[]) => any, parentClass?: any): any {
|
||||
name: string, props?: (...args: any[]) => any, parentClass?: any,
|
||||
additionalProcessing?: (target: any, name: string, ...args: any[]) => void): any {
|
||||
const metaCtor = makeMetadataCtor(props);
|
||||
|
||||
function PropDecoratorFactory(...args: any[]): any {
|
||||
|
@ -138,7 +142,7 @@ export function makePropDecorator(
|
|||
|
||||
const decoratorInstance = new (<any>PropDecoratorFactory)(...args);
|
||||
|
||||
return function PropDecorator(target: any, name: string) {
|
||||
function PropDecorator(target: any, name: string) {
|
||||
const constructor = target.constructor;
|
||||
// Use of Object.defineProperty is important since it creates non-enumerable property which
|
||||
// prevents the property is copied during subclassing.
|
||||
|
@ -147,7 +151,11 @@ export function makePropDecorator(
|
|||
Object.defineProperty(constructor, PROP_METADATA, {value: {}})[PROP_METADATA];
|
||||
meta[name] = meta.hasOwnProperty(name) && meta[name] || [];
|
||||
meta[name].unshift(decoratorInstance);
|
||||
};
|
||||
|
||||
if (additionalProcessing) additionalProcessing(target, name, ...args);
|
||||
}
|
||||
|
||||
return PropDecorator;
|
||||
}
|
||||
|
||||
if (parentClass) {
|
||||
|
|
|
@ -14,3 +14,17 @@ export function getClosureSafeProperty<T>(objWithPropertyToExtract: T, target: a
|
|||
}
|
||||
throw Error('Could not find renamed property on target object.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets properties on a target object from a source object, but only if
|
||||
* the property doesn't already exist on the target object.
|
||||
* @param target The target to set properties on
|
||||
* @param source The source of the property keys and values to set
|
||||
*/
|
||||
export function fillProperties(target: {[key: string]: string}, source: {[key: string]: string}) {
|
||||
for (const key in source) {
|
||||
if (source.hasOwnProperty(key) && !target.hasOwnProperty(key)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {DoCheck, EventEmitter, Input, OnChanges, Output, SimpleChange, SimpleChanges} from '../../src/core';
|
||||
import {EventEmitter, Output} from '../../src/core';
|
||||
import {InheritDefinitionFeature} from '../../src/render3/features/inherit_definition_feature';
|
||||
import {DirectiveDefInternal, NgOnChangesFeature, defineComponent, defineDirective} from '../../src/render3/index';
|
||||
import {DirectiveDefInternal, defineBase, defineComponent, defineDirective} from '../../src/render3/index';
|
||||
|
||||
describe('InheritDefinitionFeature', () => {
|
||||
it('should inherit lifecycle hooks', () => {
|
||||
|
@ -131,6 +131,147 @@ describe('InheritDefinitionFeature', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should inherit inputs from ngBaseDefs along the way', () => {
|
||||
|
||||
class Class5 {
|
||||
input5 = 'data, so data';
|
||||
|
||||
static ngBaseDef = defineBase({
|
||||
inputs: {
|
||||
input5: 'input5',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:class-as-namespace
|
||||
class Class4 extends Class5 {
|
||||
input4 = 'hehe';
|
||||
|
||||
static ngDirectiveDef = defineDirective({
|
||||
inputs: {
|
||||
input4: 'input4',
|
||||
},
|
||||
type: Class4,
|
||||
selectors: [['', 'superDir', '']],
|
||||
factory: () => new Class4(),
|
||||
features: [InheritDefinitionFeature],
|
||||
});
|
||||
}
|
||||
|
||||
class Class3 extends Class4 {}
|
||||
|
||||
class Class2 extends Class3 {
|
||||
input3 = 'wee';
|
||||
|
||||
static ngBaseDef = defineBase({
|
||||
inputs: {
|
||||
input3: ['alias3', 'input3'],
|
||||
}
|
||||
}) as any;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:class-as-namespace
|
||||
class Class1 extends Class2 {
|
||||
input1 = 'test';
|
||||
input2 = 'whatever';
|
||||
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: Class1,
|
||||
inputs: {
|
||||
input1: 'input1',
|
||||
input2: 'input2',
|
||||
},
|
||||
selectors: [['', 'subDir', '']],
|
||||
factory: () => new Class1(),
|
||||
features: [InheritDefinitionFeature],
|
||||
});
|
||||
}
|
||||
|
||||
const subDef = Class1.ngDirectiveDef as DirectiveDefInternal<any>;
|
||||
|
||||
expect(subDef.inputs).toEqual({
|
||||
input1: 'input1',
|
||||
input2: 'input2',
|
||||
alias3: 'input3',
|
||||
input4: 'input4',
|
||||
input5: 'input5',
|
||||
});
|
||||
expect(subDef.declaredInputs).toEqual({
|
||||
input1: 'input1',
|
||||
input2: 'input2',
|
||||
input3: 'input3',
|
||||
input4: 'input4',
|
||||
input5: 'input5',
|
||||
});
|
||||
});
|
||||
|
||||
it('should inherit outputs from ngBaseDefs along the way', () => {
|
||||
|
||||
class Class5 {
|
||||
output5 = 'data, so data';
|
||||
|
||||
static ngBaseDef = defineBase({
|
||||
outputs: {
|
||||
output5: 'alias5',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:class-as-namespace
|
||||
class Class4 extends Class5 {
|
||||
output4 = 'hehe';
|
||||
|
||||
static ngDirectiveDef = defineDirective({
|
||||
outputs: {
|
||||
output4: 'alias4',
|
||||
},
|
||||
type: Class4,
|
||||
selectors: [['', 'superDir', '']],
|
||||
factory: () => new Class4(),
|
||||
features: [InheritDefinitionFeature],
|
||||
});
|
||||
}
|
||||
|
||||
class Class3 extends Class4 {}
|
||||
|
||||
class Class2 extends Class3 {
|
||||
output3 = 'wee';
|
||||
|
||||
static ngBaseDef = defineBase({
|
||||
outputs: {
|
||||
output3: 'alias3',
|
||||
}
|
||||
}) as any;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:class-as-namespace
|
||||
class Class1 extends Class2 {
|
||||
output1 = 'test';
|
||||
output2 = 'whatever';
|
||||
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: Class1,
|
||||
outputs: {
|
||||
output1: 'alias1',
|
||||
output2: 'alias2',
|
||||
},
|
||||
selectors: [['', 'subDir', '']],
|
||||
factory: () => new Class1(),
|
||||
features: [InheritDefinitionFeature],
|
||||
});
|
||||
}
|
||||
|
||||
const subDef = Class1.ngDirectiveDef as DirectiveDefInternal<any>;
|
||||
|
||||
expect(subDef.outputs).toEqual({
|
||||
alias1: 'output1',
|
||||
alias2: 'output2',
|
||||
alias3: 'output3',
|
||||
alias4: 'output4',
|
||||
alias5: 'output5',
|
||||
});
|
||||
});
|
||||
|
||||
it('should compose hostBindings', () => {
|
||||
const log: Array<[string, number, number]> = [];
|
||||
|
||||
|
|
|
@ -12,10 +12,11 @@ import {InjectorDef, defineInjectable} from '@angular/core/src/di/defs';
|
|||
import {Injectable} from '@angular/core/src/di/injectable';
|
||||
import {inject, setCurrentInjector} from '@angular/core/src/di/injector';
|
||||
import {ivyEnabled} from '@angular/core/src/ivy_switch';
|
||||
import {Component, HostBinding, HostListener, Pipe} from '@angular/core/src/metadata/directives';
|
||||
import {Component, HostBinding, HostListener, Input, Output, Pipe} from '@angular/core/src/metadata/directives';
|
||||
import {NgModule, NgModuleDefInternal} from '@angular/core/src/metadata/ng_module';
|
||||
import {ComponentDefInternal, PipeDefInternal} from '@angular/core/src/render3/interfaces/definition';
|
||||
|
||||
|
||||
ivyEnabled && describe('render3 jit', () => {
|
||||
let injector: any;
|
||||
beforeAll(() => { injector = setCurrentInjector(null); });
|
||||
|
@ -233,6 +234,32 @@ ivyEnabled && describe('render3 jit', () => {
|
|||
const pipeDef = (P as any).ngPipeDef as PipeDefInternal<P>;
|
||||
expect(pipeDef.pure).toBe(true, 'pipe should be pure');
|
||||
});
|
||||
|
||||
it('should add ngBaseDef to types with @Input properties', () => {
|
||||
class C {
|
||||
@Input('alias1')
|
||||
prop1 = 'test';
|
||||
|
||||
@Input('alias2')
|
||||
prop2 = 'test';
|
||||
}
|
||||
|
||||
expect((C as any).ngBaseDef).toBeDefined();
|
||||
expect((C as any).ngBaseDef.inputs).toEqual({prop1: 'alias1', prop2: 'alias2'});
|
||||
});
|
||||
|
||||
it('should add ngBaseDef to types with @Output properties', () => {
|
||||
class C {
|
||||
@Output('alias1')
|
||||
prop1 = 'test';
|
||||
|
||||
@Output('alias2')
|
||||
prop2 = 'test';
|
||||
}
|
||||
|
||||
expect((C as any).ngBaseDef).toBeDefined();
|
||||
expect((C as any).ngBaseDef.outputs).toEqual({prop1: 'alias1', prop2: 'alias2'});
|
||||
});
|
||||
});
|
||||
|
||||
it('ensure at least one spec exists', () => {});
|
||||
|
|
Loading…
Reference in New Issue