feat(ivy): Add InheritanceDefinitionFeature to support directive inheritance (#24570)
- Adds InheritanceDefinitionFeature to ivy - Ensures that lifecycle hooks are inherited from super classes whether they are defined as directives or not - Directives cannot inherit from Components - Components can inherit from Directives or Components - Ensures that Inputs, Outputs, and Host Bindings are inherited - Ensures that super class Features are run PR Close #24570
This commit is contained in:
@ -108,6 +108,9 @@ export function extractDirectiveMetadata(
|
|||||||
member => member.isStatic && member.kind === ClassMemberKind.Method &&
|
member => member.isStatic && member.kind === ClassMemberKind.Method &&
|
||||||
member.name === 'ngOnChanges') !== undefined;
|
member.name === 'ngOnChanges') !== undefined;
|
||||||
|
|
||||||
|
// Detect if the component inherits from another class
|
||||||
|
const usesInheritance = clazz.heritageClauses !== undefined &&
|
||||||
|
clazz.heritageClauses.some(hc => hc.token === ts.SyntaxKind.ExtendsKeyword);
|
||||||
return {
|
return {
|
||||||
name: clazz.name !.text,
|
name: clazz.name !.text,
|
||||||
deps: getConstructorDependencies(clazz, reflector, isCore),
|
deps: getConstructorDependencies(clazz, reflector, isCore),
|
||||||
@ -123,7 +126,7 @@ export function extractDirectiveMetadata(
|
|||||||
outputs: {...outputsFromMeta, ...outputsFromFields},
|
outputs: {...outputsFromMeta, ...outputsFromFields},
|
||||||
queries: [], selector,
|
queries: [], selector,
|
||||||
type: new WrappedNodeExpr(clazz.name !),
|
type: new WrappedNodeExpr(clazz.name !),
|
||||||
typeSourceSpan: null !,
|
typeSourceSpan: null !, usesInheritance,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +138,9 @@ export class Identifiers {
|
|||||||
|
|
||||||
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
|
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
|
||||||
|
|
||||||
|
static InheritDefinitionFeature:
|
||||||
|
o.ExternalReference = {name: 'ɵInheritDefinitionFeature', moduleName: CORE};
|
||||||
|
|
||||||
static listener: o.ExternalReference = {name: 'ɵL', moduleName: CORE};
|
static listener: o.ExternalReference = {name: 'ɵL', moduleName: CORE};
|
||||||
|
|
||||||
// Reserve slots for pure functions
|
// Reserve slots for pure functions
|
||||||
|
@ -86,6 +86,11 @@ export interface R3DirectiveMetadata {
|
|||||||
* A mapping of output field names to the property names.
|
* A mapping of output field names to the property names.
|
||||||
*/
|
*/
|
||||||
outputs: {[field: string]: string};
|
outputs: {[field: string]: string};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the component or directive inherits from another class
|
||||||
|
*/
|
||||||
|
usesInheritance: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -64,10 +64,14 @@ function baseDirectiveFields(
|
|||||||
// e.g 'outputs: {a: 'a'}`
|
// e.g 'outputs: {a: 'a'}`
|
||||||
definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs));
|
definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs));
|
||||||
|
|
||||||
// e.g. `features: [NgOnChangesFeature(MyComponent)]`
|
// e.g. `features: [NgOnChangesFeature]`
|
||||||
const features: o.Expression[] = [];
|
const features: o.Expression[] = [];
|
||||||
|
|
||||||
|
if (meta.usesInheritance) {
|
||||||
|
features.push(o.importExpr(R3.InheritDefinitionFeature));
|
||||||
|
}
|
||||||
if (meta.lifecycle.usesOnChanges) {
|
if (meta.lifecycle.usesOnChanges) {
|
||||||
features.push(o.importExpr(R3.NgOnChangesFeature, null, null).callFn([meta.type]));
|
features.push(o.importExpr(R3.NgOnChangesFeature));
|
||||||
}
|
}
|
||||||
if (features.length) {
|
if (features.length) {
|
||||||
definitionMap.set('features', o.literalArr(features));
|
definitionMap.set('features', o.literalArr(features));
|
||||||
@ -255,6 +259,7 @@ function directiveMetadataFromGlobalMetadata(
|
|||||||
},
|
},
|
||||||
inputs: directive.inputs,
|
inputs: directive.inputs,
|
||||||
outputs: directive.outputs,
|
outputs: directive.outputs,
|
||||||
|
usesInheritance: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1212,7 +1212,7 @@ describe('compiler compliance', () => {
|
|||||||
selectors: [['lifecycle-comp']],
|
selectors: [['lifecycle-comp']],
|
||||||
factory: function LifecycleComp_Factory() { return new LifecycleComp(); },
|
factory: function LifecycleComp_Factory() { return new LifecycleComp(); },
|
||||||
inputs: {nameMin: 'name'},
|
inputs: {nameMin: 'name'},
|
||||||
features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)],
|
features: [$r3$.ɵNgOnChangesFeature],
|
||||||
template: function LifecycleComp_Template(rf: IDENT, ctx: IDENT) {}
|
template: function LifecycleComp_Template(rf: IDENT, ctx: IDENT) {}
|
||||||
});`;
|
});`;
|
||||||
|
|
||||||
@ -1328,7 +1328,7 @@ describe('compiler compliance', () => {
|
|||||||
factory: function ForOfDirective_Factory() {
|
factory: function ForOfDirective_Factory() {
|
||||||
return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
|
return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
|
||||||
},
|
},
|
||||||
features: [$r3$.ɵNgOnChangesFeature(NgForOf)],
|
features: [$r3$.ɵNgOnChangesFeature],
|
||||||
inputs: {forOf: 'forOf'}
|
inputs: {forOf: 'forOf'}
|
||||||
});
|
});
|
||||||
`;
|
`;
|
||||||
@ -1401,7 +1401,7 @@ describe('compiler compliance', () => {
|
|||||||
factory: function ForOfDirective_Factory() {
|
factory: function ForOfDirective_Factory() {
|
||||||
return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
|
return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
|
||||||
},
|
},
|
||||||
features: [$r3$.ɵNgOnChangesFeature(NgForOf)],
|
features: [$r3$.ɵNgOnChangesFeature],
|
||||||
inputs: {forOf: 'forOf'}
|
inputs: {forOf: 'forOf'}
|
||||||
});
|
});
|
||||||
`;
|
`;
|
||||||
|
@ -61,14 +61,47 @@ export function defineComponent<T>(componentDefinition: {
|
|||||||
/**
|
/**
|
||||||
* A map of input names.
|
* A map of input names.
|
||||||
*
|
*
|
||||||
* The format is in: `{[actualPropertyName: string]:string}`.
|
* The format is in: `{[actualPropertyName: string]:(string|[string, string])}`.
|
||||||
*
|
*
|
||||||
* Which the minifier may translate to: `{[minifiedPropertyName: string]:string}`.
|
* Given:
|
||||||
|
* ```
|
||||||
|
* class MyComponent {
|
||||||
|
* @Input()
|
||||||
|
* publicInput1: string;
|
||||||
*
|
*
|
||||||
* This allows the render to re-construct the minified and non-minified names
|
* @Input('publicInput2')
|
||||||
|
* declaredInput2: string;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* is described as:
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* publicInput1: 'publicInput1',
|
||||||
|
* declaredInput2: ['declaredInput2', 'publicInput2'],
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Which the minifier may translate to:
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* minifiedPublicInput1: 'publicInput1',
|
||||||
|
* minifiedDeclaredInput2: [ 'publicInput2', 'declaredInput2'],
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This allows the render to re-construct the minified, public, and declared names
|
||||||
* of properties.
|
* 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 `output`.
|
||||||
*/
|
*/
|
||||||
inputs?: {[P in keyof T]?: string};
|
inputs?: {[P in keyof T]?: string | [string, string]};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map of output names.
|
* A map of output names.
|
||||||
@ -176,6 +209,7 @@ export function defineComponent<T>(componentDefinition: {
|
|||||||
const type = componentDefinition.type;
|
const type = componentDefinition.type;
|
||||||
const pipeTypes = componentDefinition.pipes !;
|
const pipeTypes = componentDefinition.pipes !;
|
||||||
const directiveTypes = componentDefinition.directives !;
|
const directiveTypes = componentDefinition.directives !;
|
||||||
|
const declaredInputs: {[P in keyof T]: P} = {} as any;
|
||||||
const def: ComponentDefInternal<any> = {
|
const def: ComponentDefInternal<any> = {
|
||||||
type: type,
|
type: type,
|
||||||
diPublic: null,
|
diPublic: null,
|
||||||
@ -183,7 +217,8 @@ export function defineComponent<T>(componentDefinition: {
|
|||||||
template: componentDefinition.template || null !,
|
template: componentDefinition.template || null !,
|
||||||
hostBindings: componentDefinition.hostBindings || null,
|
hostBindings: componentDefinition.hostBindings || null,
|
||||||
attributes: componentDefinition.attributes || null,
|
attributes: componentDefinition.attributes || null,
|
||||||
inputs: invertObject(componentDefinition.inputs),
|
inputs: invertObject(componentDefinition.inputs, declaredInputs),
|
||||||
|
declaredInputs: declaredInputs,
|
||||||
outputs: invertObject(componentDefinition.outputs),
|
outputs: invertObject(componentDefinition.outputs),
|
||||||
rendererType: resolveRendererType2(componentDefinition.rendererType) || null,
|
rendererType: resolveRendererType2(componentDefinition.rendererType) || null,
|
||||||
exportAs: componentDefinition.exportAs || null,
|
exportAs: componentDefinition.exportAs || null,
|
||||||
@ -204,6 +239,7 @@ export function defineComponent<T>(componentDefinition: {
|
|||||||
null,
|
null,
|
||||||
selectors: componentDefinition.selectors,
|
selectors: componentDefinition.selectors,
|
||||||
viewQuery: componentDefinition.viewQuery || null,
|
viewQuery: componentDefinition.viewQuery || null,
|
||||||
|
features: componentDefinition.features || null,
|
||||||
};
|
};
|
||||||
const feature = componentDefinition.features;
|
const feature = componentDefinition.features;
|
||||||
feature && feature.forEach((fn) => fn(def));
|
feature && feature.forEach((fn) => fn(def));
|
||||||
@ -239,115 +275,72 @@ export function defineNgModule<T>(def: {type: T} & Partial<NgModuleDef<T, any, a
|
|||||||
return res as never;
|
return res as never;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PRIVATE_PREFIX = '__ngOnChanges_';
|
|
||||||
|
|
||||||
type OnChangesExpando = OnChanges & {
|
|
||||||
__ngOnChanges_: SimpleChanges|null|undefined;
|
|
||||||
[key: string]: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an NgOnChangesFeature function for a component's features list.
|
|
||||||
*
|
|
||||||
* It accepts an optional map of minified input property names to original property names,
|
|
||||||
* if any input properties have a public alias.
|
|
||||||
*
|
|
||||||
* The NgOnChangesFeature function that is returned decorates a component with support for
|
|
||||||
* the ngOnChanges lifecycle hook, so it should be included in any component that implements
|
|
||||||
* that hook.
|
|
||||||
*
|
|
||||||
* Example usage:
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* static ngComponentDef = defineComponent({
|
|
||||||
* ...
|
|
||||||
* inputs: {name: 'publicName'},
|
|
||||||
* features: [NgOnChangesFeature({name: 'name'})]
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param inputPropertyNames Map of input property names, if they are aliased
|
|
||||||
* @returns DirectiveDefFeature
|
|
||||||
*/
|
|
||||||
export function NgOnChangesFeature(inputPropertyNames?: {[key: string]: string}):
|
|
||||||
DirectiveDefFeature {
|
|
||||||
return function(definition: DirectiveDefInternal<any>): void {
|
|
||||||
const inputs = definition.inputs;
|
|
||||||
const proto = definition.type.prototype;
|
|
||||||
for (let pubKey in inputs) {
|
|
||||||
const minKey = inputs[pubKey];
|
|
||||||
const propertyName = inputPropertyNames && inputPropertyNames[minKey] || pubKey;
|
|
||||||
const privateMinKey = PRIVATE_PREFIX + minKey;
|
|
||||||
const originalProperty = Object.getOwnPropertyDescriptor(proto, minKey);
|
|
||||||
const getter = originalProperty && originalProperty.get;
|
|
||||||
const setter = originalProperty && originalProperty.set;
|
|
||||||
// create a getter and setter for property
|
|
||||||
Object.defineProperty(proto, minKey, {
|
|
||||||
get: getter ||
|
|
||||||
(setter ? undefined : function(this: OnChangesExpando) { return this[privateMinKey]; }),
|
|
||||||
set: function(this: OnChangesExpando, value: any) {
|
|
||||||
let simpleChanges = this[PRIVATE_PREFIX];
|
|
||||||
if (!simpleChanges) {
|
|
||||||
// Place where we will store SimpleChanges if there is a change
|
|
||||||
Object.defineProperty(
|
|
||||||
this, PRIVATE_PREFIX, {value: simpleChanges = {}, writable: true});
|
|
||||||
}
|
|
||||||
const isFirstChange = !this.hasOwnProperty(privateMinKey);
|
|
||||||
const currentChange: SimpleChange|undefined = simpleChanges[propertyName];
|
|
||||||
if (currentChange) {
|
|
||||||
currentChange.currentValue = value;
|
|
||||||
} else {
|
|
||||||
simpleChanges[propertyName] =
|
|
||||||
new SimpleChange(this[privateMinKey], value, isFirstChange);
|
|
||||||
}
|
|
||||||
if (isFirstChange) {
|
|
||||||
// Create a place where the actual value will be stored and make it non-enumerable
|
|
||||||
Object.defineProperty(this, privateMinKey, {value, writable: true});
|
|
||||||
} else {
|
|
||||||
this[privateMinKey] = value;
|
|
||||||
}
|
|
||||||
setter && setter.call(this, value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// If an onInit hook is defined, it will need to wrap the ngOnChanges call
|
|
||||||
// so the call order is changes-init-check in creation mode. In subsequent
|
|
||||||
// change detection runs, only the check wrapper will be called.
|
|
||||||
if (definition.onInit != null) {
|
|
||||||
definition.onInit = onChangesWrapper(definition.onInit);
|
|
||||||
}
|
|
||||||
|
|
||||||
definition.doCheck = onChangesWrapper(definition.doCheck);
|
|
||||||
};
|
|
||||||
|
|
||||||
function onChangesWrapper(delegateHook: (() => void) | null) {
|
|
||||||
return function(this: OnChangesExpando) {
|
|
||||||
let simpleChanges = this[PRIVATE_PREFIX];
|
|
||||||
if (simpleChanges != null) {
|
|
||||||
this.ngOnChanges(simpleChanges);
|
|
||||||
this[PRIVATE_PREFIX] = null;
|
|
||||||
}
|
|
||||||
delegateHook && delegateHook.apply(this);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function PublicFeature<T>(definition: DirectiveDefInternal<T>) {
|
|
||||||
definition.diPublic = diPublic;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EMPTY = {};
|
const EMPTY = {};
|
||||||
|
|
||||||
/** Swaps the keys and values of an object. */
|
/**
|
||||||
function invertObject(obj: any): any {
|
* Inverts an inputs or outputs lookup such that the keys, which were the
|
||||||
|
* minified keys, are part of the values, and the values are parsed so that
|
||||||
|
* the publicName of the property is the new key
|
||||||
|
*
|
||||||
|
* e.g. for
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* class Comp {
|
||||||
|
* @Input()
|
||||||
|
* propName1: string;
|
||||||
|
*
|
||||||
|
* @Input('publicName')
|
||||||
|
* propName2: number;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* will be serialized as
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* a0: 'propName1',
|
||||||
|
* b1: ['publicName', 'propName2'],
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* becomes
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* 'propName1': 'a0',
|
||||||
|
* 'publicName': 'b1'
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Optionally the function can take `secondary` which will result in:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* 'propName1': 'a0',
|
||||||
|
* 'propName2': 'b1'
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
|
||||||
|
*/
|
||||||
|
function invertObject(obj: any, secondary?: any): any {
|
||||||
if (obj == null) return EMPTY;
|
if (obj == null) return EMPTY;
|
||||||
const newObj: any = {};
|
const newLookup: any = {};
|
||||||
for (let minifiedKey in obj) {
|
for (const minifiedKey in obj) {
|
||||||
newObj[obj[minifiedKey]] = minifiedKey;
|
if (obj.hasOwnProperty(minifiedKey)) {
|
||||||
|
let publicName = obj[minifiedKey];
|
||||||
|
let declaredName = publicName;
|
||||||
|
if (Array.isArray(publicName)) {
|
||||||
|
declaredName = publicName[1];
|
||||||
|
publicName = publicName[0];
|
||||||
|
}
|
||||||
|
newLookup[publicName] = minifiedKey;
|
||||||
|
if (secondary) {
|
||||||
|
(secondary[declaredName] = minifiedKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return newObj;
|
return newLookup;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -389,14 +382,47 @@ export const defineDirective = defineComponent as any as<T>(directiveDefinition:
|
|||||||
/**
|
/**
|
||||||
* A map of input names.
|
* A map of input names.
|
||||||
*
|
*
|
||||||
* The format is in: `{[actualPropertyName: string]:string}`.
|
* The format is in: `{[actualPropertyName: string]:(string|[string, string])}`.
|
||||||
*
|
*
|
||||||
* Which the minifier may translate to: `{[minifiedPropertyName: string]:string}`.
|
* Given:
|
||||||
|
* ```
|
||||||
|
* class MyComponent {
|
||||||
|
* @Input()
|
||||||
|
* publicInput1: string;
|
||||||
*
|
*
|
||||||
* This allows the render to re-construct the minified and non-minified names
|
* @Input('publicInput2')
|
||||||
|
* declaredInput2: string;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* is described as:
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* publicInput1: 'publicInput1',
|
||||||
|
* declaredInput2: ['declaredInput2', 'publicInput2'],
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Which the minifier may translate to:
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* minifiedPublicInput1: 'publicInput1',
|
||||||
|
* minifiedDeclaredInput2: [ 'publicInput2', 'declaredInput2'],
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This allows the render to re-construct the minified, public, and declared names
|
||||||
* of properties.
|
* 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 `output`.
|
||||||
*/
|
*/
|
||||||
inputs?: {[P in keyof T]?: string};
|
inputs?: {[P in keyof T]?: string | [string, string]};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map of output names.
|
* A map of output names.
|
||||||
@ -413,7 +439,7 @@ export const defineDirective = defineComponent as any as<T>(directiveDefinition:
|
|||||||
/**
|
/**
|
||||||
* A list of optional features to apply.
|
* A list of optional features to apply.
|
||||||
*
|
*
|
||||||
* See: {@link NgOnChangesFeature}, {@link PublicFeature}
|
* See: {@link NgOnChangesFeature}, {@link PublicFeature}, {@link InheritDefinitionFeature}
|
||||||
*/
|
*/
|
||||||
features?: DirectiveDefFeature[];
|
features?: DirectiveDefFeature[];
|
||||||
|
|
||||||
|
120
packages/core/src/render3/features/inherit_definition_feature.ts
Normal file
120
packages/core/src/render3/features/inherit_definition_feature.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
/**
|
||||||
|
* @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 {Type} from '../../type';
|
||||||
|
import {ComponentDefInternal, ComponentType, 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
|
||||||
|
*/
|
||||||
|
function isComponentDef<T>(definition: ComponentDefInternal<T>| DirectiveDefInternal<T>):
|
||||||
|
definition is ComponentDefInternal<T> {
|
||||||
|
const def = definition as ComponentDefInternal<T>;
|
||||||
|
return typeof def.template === 'function';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSuperType(type: Type<any>): Type<any>&
|
||||||
|
{ngComponentDef?: ComponentDefInternal<any>, ngDirectiveDef?: DirectiveDefInternal<any>} {
|
||||||
|
return Object.getPrototypeOf(type.prototype).constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges the definition from a super class to a sub class.
|
||||||
|
* @param definition The definition that is a SubClass of another directive of component
|
||||||
|
*/
|
||||||
|
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) {
|
||||||
|
if (isComponentDef(definition)) {
|
||||||
|
superDef = superType.ngComponentDef || superType.ngDirectiveDef;
|
||||||
|
} else {
|
||||||
|
if (superType.ngComponentDef) {
|
||||||
|
throw new Error('Directives cannot inherit Components');
|
||||||
|
}
|
||||||
|
superDef = superType.ngDirectiveDef;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (superDef) {
|
||||||
|
// Merge inputs and outputs
|
||||||
|
fillProperties(definition.inputs, superDef.inputs);
|
||||||
|
fillProperties(definition.declaredInputs, superDef.declaredInputs);
|
||||||
|
fillProperties(definition.outputs, superDef.outputs);
|
||||||
|
|
||||||
|
// Merge hostBindings
|
||||||
|
const prevHostBindings = definition.hostBindings;
|
||||||
|
const superHostBindings = superDef.hostBindings;
|
||||||
|
if (superHostBindings) {
|
||||||
|
if (prevHostBindings) {
|
||||||
|
definition.hostBindings = (directiveIndex: number, elementIndex: number) => {
|
||||||
|
superHostBindings(directiveIndex, elementIndex);
|
||||||
|
prevHostBindings(directiveIndex, elementIndex);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
definition.hostBindings = superHostBindings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inherit hooks
|
||||||
|
// Assume super class inheritance feature has already run.
|
||||||
|
definition.afterContentChecked =
|
||||||
|
definition.afterContentChecked || superDef.afterContentChecked;
|
||||||
|
definition.afterContentInit = definition.afterContentInit || superDef.afterContentInit;
|
||||||
|
definition.afterViewChecked = definition.afterViewChecked || superDef.afterViewChecked;
|
||||||
|
definition.afterViewInit = definition.afterViewInit || superDef.afterViewInit;
|
||||||
|
definition.doCheck = definition.doCheck || superDef.doCheck;
|
||||||
|
definition.onDestroy = definition.onDestroy || superDef.onDestroy;
|
||||||
|
definition.onInit = definition.onInit || superDef.onInit;
|
||||||
|
|
||||||
|
// Run parent features
|
||||||
|
const features = superDef.features;
|
||||||
|
if (features) {
|
||||||
|
for (const feature of features) {
|
||||||
|
if (feature && feature !== InheritDefinitionFeature) {
|
||||||
|
(feature as DirectiveDefFeature)(definition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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.afterContentChecked;
|
||||||
|
definition.afterContentInit =
|
||||||
|
definition.afterContentInit || superPrototype.afterContentInit;
|
||||||
|
definition.afterViewChecked =
|
||||||
|
definition.afterViewChecked || superPrototype.afterViewChecked;
|
||||||
|
definition.afterViewInit = definition.afterViewInit || superPrototype.afterViewInit;
|
||||||
|
definition.doCheck = definition.doCheck || superPrototype.doCheck;
|
||||||
|
definition.onDestroy = definition.onDestroy || superPrototype.onDestroy;
|
||||||
|
definition.onInit = definition.onInit || superPrototype.onInit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
superType = Object.getPrototypeOf(superType);
|
||||||
|
}
|
||||||
|
}
|
116
packages/core/src/render3/features/ng_onchanges_feature.ts
Normal file
116
packages/core/src/render3/features/ng_onchanges_feature.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
/**
|
||||||
|
* @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 {SimpleChange} from '../../change_detection/change_detection_util';
|
||||||
|
import {OnChanges, SimpleChanges} from '../../metadata/lifecycle_hooks';
|
||||||
|
import {DirectiveDefInternal} from '../interfaces/definition';
|
||||||
|
|
||||||
|
const PRIVATE_PREFIX = '__ngOnChanges_';
|
||||||
|
|
||||||
|
type OnChangesExpando = OnChanges & {
|
||||||
|
__ngOnChanges_: SimpleChanges|null|undefined;
|
||||||
|
// tslint:disable-next-line:no-any Can hold any value
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The NgOnChangesFeature decorates a component with support for the ngOnChanges
|
||||||
|
* lifecycle hook, so it should be included in any component that implements
|
||||||
|
* that hook.
|
||||||
|
*
|
||||||
|
* If the component or directive uses inheritance, the NgOnChangesFeature MUST
|
||||||
|
* be included as a feature AFTER {@link InheritDefinitionFeature}, otherwise
|
||||||
|
* inherited properties will not be propagated to the ngOnChanges lifecycle
|
||||||
|
* hook.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* static ngComponentDef = defineComponent({
|
||||||
|
* ...
|
||||||
|
* inputs: {name: 'publicName'},
|
||||||
|
* features: [NgOnChangesFeature]
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function NgOnChangesFeature<T>(definition: DirectiveDefInternal<T>): void {
|
||||||
|
const declaredToMinifiedInputs = definition.declaredInputs;
|
||||||
|
const proto = definition.type.prototype;
|
||||||
|
for (const declaredName in declaredToMinifiedInputs) {
|
||||||
|
if (declaredToMinifiedInputs.hasOwnProperty(declaredName)) {
|
||||||
|
const minifiedKey = declaredToMinifiedInputs[declaredName];
|
||||||
|
const privateMinKey = PRIVATE_PREFIX + minifiedKey;
|
||||||
|
|
||||||
|
// Walk the prototype chain to see if we find a property descriptor
|
||||||
|
// That way we can honor setters and getters that were inherited.
|
||||||
|
let originalProperty: PropertyDescriptor|undefined = undefined;
|
||||||
|
let checkProto = proto;
|
||||||
|
while (!originalProperty && checkProto &&
|
||||||
|
Object.getPrototypeOf(checkProto) !== Object.getPrototypeOf(Object.prototype)) {
|
||||||
|
originalProperty = Object.getOwnPropertyDescriptor(checkProto, minifiedKey);
|
||||||
|
checkProto = Object.getPrototypeOf(checkProto);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getter = originalProperty && originalProperty.get;
|
||||||
|
const setter = originalProperty && originalProperty.set;
|
||||||
|
|
||||||
|
// create a getter and setter for property
|
||||||
|
Object.defineProperty(proto, minifiedKey, {
|
||||||
|
get: getter ||
|
||||||
|
(setter ? undefined : function(this: OnChangesExpando) { return this[privateMinKey]; }),
|
||||||
|
set<T>(this: OnChangesExpando, value: T) {
|
||||||
|
let simpleChanges = this[PRIVATE_PREFIX];
|
||||||
|
if (!simpleChanges) {
|
||||||
|
simpleChanges = {};
|
||||||
|
// Place where we will store SimpleChanges if there is a change
|
||||||
|
Object.defineProperty(this, PRIVATE_PREFIX, {value: simpleChanges, writable: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFirstChange = !this.hasOwnProperty(privateMinKey);
|
||||||
|
const currentChange = simpleChanges[declaredName];
|
||||||
|
|
||||||
|
if (currentChange) {
|
||||||
|
currentChange.currentValue = value;
|
||||||
|
} else {
|
||||||
|
simpleChanges[declaredName] =
|
||||||
|
new SimpleChange(this[privateMinKey], value, isFirstChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFirstChange) {
|
||||||
|
// Create a place where the actual value will be stored and make it non-enumerable
|
||||||
|
Object.defineProperty(this, privateMinKey, {value, writable: true});
|
||||||
|
} else {
|
||||||
|
this[privateMinKey] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setter) setter.call(this, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an onInit hook is defined, it will need to wrap the ngOnChanges call
|
||||||
|
// so the call order is changes-init-check in creation mode. In subsequent
|
||||||
|
// change detection runs, only the check wrapper will be called.
|
||||||
|
if (definition.onInit != null) {
|
||||||
|
definition.onInit = onChangesWrapper(definition.onInit);
|
||||||
|
}
|
||||||
|
|
||||||
|
definition.doCheck = onChangesWrapper(definition.doCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChangesWrapper(delegateHook: (() => void) | null) {
|
||||||
|
return function(this: OnChangesExpando) {
|
||||||
|
const simpleChanges = this[PRIVATE_PREFIX];
|
||||||
|
if (simpleChanges != null) {
|
||||||
|
this.ngOnChanges(simpleChanges);
|
||||||
|
this[PRIVATE_PREFIX] = null;
|
||||||
|
}
|
||||||
|
if (delegateHook) delegateHook.apply(this);
|
||||||
|
};
|
||||||
|
}
|
19
packages/core/src/render3/features/public_feature.ts
Normal file
19
packages/core/src/render3/features/public_feature.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* @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 {diPublic} from '../di';
|
||||||
|
import {DirectiveDefInternal} from '../interfaces/definition';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This feature publishes the directive (or component) into the DI system, making it visible to
|
||||||
|
* others for injection.
|
||||||
|
*
|
||||||
|
* @param definition
|
||||||
|
*/
|
||||||
|
export function PublicFeature<T>(definition: DirectiveDefInternal<T>) {
|
||||||
|
definition.diPublic = diPublic;
|
||||||
|
}
|
@ -7,9 +7,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {LifecycleHooksFeature, getHostElement, getRenderedText, renderComponent, whenRendered} from './component';
|
import {LifecycleHooksFeature, getHostElement, getRenderedText, renderComponent, whenRendered} from './component';
|
||||||
import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, defineNgModule, definePipe} from './definition';
|
import {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';
|
||||||
import {I18nExpInstruction, I18nInstruction, i18nExpMapping, i18nInterpolation, i18nInterpolationV} from './i18n';
|
import {I18nExpInstruction, I18nInstruction, i18nExpMapping, i18nInterpolation, i18nInterpolationV} from './i18n';
|
||||||
import {ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDefFlags, DirectiveDefInternal, DirectiveType, PipeDef} from './interfaces/definition';
|
import {ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDefFlags, DirectiveDefInternal, DirectiveType, PipeDef} from './interfaces/definition';
|
||||||
|
|
||||||
export {ComponentFactory, ComponentFactoryResolver, ComponentRef} from './component_ref';
|
export {ComponentFactory, ComponentFactoryResolver, ComponentRef} from './component_ref';
|
||||||
export {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, directiveInject, injectAttribute, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di';
|
export {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, directiveInject, injectAttribute, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di';
|
||||||
export {RenderFlags} from './interfaces/definition';
|
export {RenderFlags} from './interfaces/definition';
|
||||||
@ -133,6 +137,7 @@ export {
|
|||||||
DirectiveDefFlags,
|
DirectiveDefFlags,
|
||||||
DirectiveType,
|
DirectiveType,
|
||||||
NgOnChangesFeature,
|
NgOnChangesFeature,
|
||||||
|
InheritDefinitionFeature,
|
||||||
PublicFeature,
|
PublicFeature,
|
||||||
PipeDef,
|
PipeDef,
|
||||||
LifecycleHooksFeature,
|
LifecycleHooksFeature,
|
||||||
|
@ -96,6 +96,12 @@ export interface DirectiveDef<T, Selector extends string> {
|
|||||||
*/
|
*/
|
||||||
readonly inputs: {[P in keyof T]: P};
|
readonly inputs: {[P in keyof T]: P};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated This is only here because `NgOnChanges` incorrectly uses declared name instead of
|
||||||
|
* public or minified name.
|
||||||
|
*/
|
||||||
|
readonly declaredInputs: {[P in keyof T]: P};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A dictionary mapping the outputs' minified property names to their public API names, which
|
* A dictionary mapping the outputs' minified property names to their public API names, which
|
||||||
* are their aliases if any, or their original unminified property names
|
* are their aliases if any, or their original unminified property names
|
||||||
@ -135,6 +141,11 @@ export interface DirectiveDef<T, Selector extends string> {
|
|||||||
afterViewInit: (() => void)|null;
|
afterViewInit: (() => void)|null;
|
||||||
afterViewChecked: (() => void)|null;
|
afterViewChecked: (() => void)|null;
|
||||||
onDestroy: (() => void)|null;
|
onDestroy: (() => void)|null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The features applied to this directive
|
||||||
|
*/
|
||||||
|
features: DirectiveDefFeature[]|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -121,6 +121,10 @@ export function compileDirective(type: Type<any>, directive: Directive): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function extendsDirectlyFromObject(type: Type<any>): boolean {
|
||||||
|
return Object.getPrototypeOf(type.prototype) === Object.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the `R3DirectiveMetadata` for a particular directive (either a `Directive` or a
|
* Extract the `R3DirectiveMetadata` for a particular directive (either a `Directive` or a
|
||||||
* `Component`).
|
* `Component`).
|
||||||
@ -136,14 +140,16 @@ function directiveMetadata(type: Type<any>, metadata: Directive): R3DirectiveMet
|
|||||||
|
|
||||||
const inputsFromType: StringMap = {};
|
const inputsFromType: StringMap = {};
|
||||||
const outputsFromType: StringMap = {};
|
const outputsFromType: StringMap = {};
|
||||||
for (let field in propMetadata) {
|
for (const field in propMetadata) {
|
||||||
propMetadata[field].forEach(ann => {
|
if (propMetadata.hasOwnProperty(field)) {
|
||||||
if (isInput(ann)) {
|
propMetadata[field].forEach(ann => {
|
||||||
inputsFromType[field] = ann.bindingPropertyName || field;
|
if (isInput(ann)) {
|
||||||
} else if (isOutput(ann)) {
|
inputsFromType[field] = ann.bindingPropertyName || field;
|
||||||
outputsFromType[field] = ann.bindingPropertyName || field;
|
} else if (isOutput(ann)) {
|
||||||
}
|
outputsFromType[field] = ann.bindingPropertyName || field;
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -158,6 +164,7 @@ function directiveMetadata(type: Type<any>, metadata: Directive): R3DirectiveMet
|
|||||||
usesOnChanges: type.prototype.ngOnChanges !== undefined,
|
usesOnChanges: type.prototype.ngOnChanges !== undefined,
|
||||||
},
|
},
|
||||||
typeSourceSpan: null !,
|
typeSourceSpan: null !,
|
||||||
|
usesInheritance: !extendsDirectlyFromObject(type),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,14 +181,16 @@ function extractHostBindings(metadata: Directive, propMetadata: {[key: string]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Next, loop over the properties of the object, looking for @HostBinding and @HostListener.
|
// Next, loop over the properties of the object, looking for @HostBinding and @HostListener.
|
||||||
for (let field in propMetadata) {
|
for (const field in propMetadata) {
|
||||||
propMetadata[field].forEach(ann => {
|
if (propMetadata.hasOwnProperty(field)) {
|
||||||
if (isHostBinding(ann)) {
|
propMetadata[field].forEach(ann => {
|
||||||
properties[ann.hostPropertyName || field] = field;
|
if (isHostBinding(ann)) {
|
||||||
} else if (isHostListener(ann)) {
|
properties[ann.hostPropertyName || field] = field;
|
||||||
listeners[ann.eventName || field] = `${field}(${(ann.args || []).join(',')})`;
|
} else if (isHostListener(ann)) {
|
||||||
}
|
listeners[ann.eventName || field] = `${field}(${(ann.args || []).join(',')})`;
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {attributes, listeners, properties};
|
return {attributes, listeners, properties};
|
||||||
|
@ -31,6 +31,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
|
|||||||
'ɵinjectTemplateRef': r3.injectTemplateRef,
|
'ɵinjectTemplateRef': r3.injectTemplateRef,
|
||||||
'ɵinjectViewContainerRef': r3.injectViewContainerRef,
|
'ɵinjectViewContainerRef': r3.injectViewContainerRef,
|
||||||
'ɵNgOnChangesFeature': r3.NgOnChangesFeature,
|
'ɵNgOnChangesFeature': r3.NgOnChangesFeature,
|
||||||
|
'ɵInheritDefinitionFeature': r3.InheritDefinitionFeature,
|
||||||
'ɵa': r3.a,
|
'ɵa': r3.a,
|
||||||
'ɵb': r3.b,
|
'ɵb': r3.b,
|
||||||
'ɵC': r3.C,
|
'ɵC': r3.C,
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
import '@angular/core/test/bundling/util/src/reflect_metadata';
|
import '@angular/core/test/bundling/util/src/reflect_metadata';
|
||||||
|
|
||||||
import {CommonModule, NgForOf, NgIf} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {Component, Injectable, IterableDiffers, NgModule, defineInjector, ɵNgOnChangesFeature as NgOnChangesFeature, ɵdefineDirective as defineDirective, ɵdirectiveInject as directiveInject, ɵinjectTemplateRef as injectTemplateRef, ɵinjectViewContainerRef as injectViewContainerRef, ɵrenderComponent as renderComponent} from '@angular/core';
|
import {Component, Injectable, NgModule, ɵrenderComponent as renderComponent} from '@angular/core';
|
||||||
|
|
||||||
class Todo {
|
class Todo {
|
||||||
editing: boolean;
|
editing: boolean;
|
||||||
@ -63,32 +63,32 @@ class TodoStore {
|
|||||||
<section class="todoapp">
|
<section class="todoapp">
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<h1>todos</h1>
|
<h1>todos</h1>
|
||||||
<input class="new-todo" placeholder="What needs to be done?" autofocus=""
|
<input class="new-todo" placeholder="What needs to be done?" autofocus=""
|
||||||
[value]="newTodoText"
|
[value]="newTodoText"
|
||||||
(keyup)="$event.code == 'Enter' ? addTodo() : newTodoText = $event.target.value">
|
(keyup)="$event.code == 'Enter' ? addTodo() : newTodoText = $event.target.value">
|
||||||
</header>
|
</header>
|
||||||
<section *ngIf="todoStore.todos.length > 0" class="main">
|
<section *ngIf="todoStore.todos.length > 0" class="main">
|
||||||
<input *ngIf="todoStore.todos.length"
|
<input *ngIf="todoStore.todos.length"
|
||||||
#toggleall class="toggle-all" type="checkbox"
|
#toggleall class="toggle-all" type="checkbox"
|
||||||
[checked]="todoStore.allCompleted()"
|
[checked]="todoStore.allCompleted()"
|
||||||
(click)="todoStore.setAllTo(toggleall.checked)">
|
(click)="todoStore.setAllTo(toggleall.checked)">
|
||||||
<ul class="todo-list">
|
<ul class="todo-list">
|
||||||
<li *ngFor="let todo of todoStore.todos"
|
<li *ngFor="let todo of todoStore.todos"
|
||||||
[class.completed]="todo.completed"
|
[class.completed]="todo.completed"
|
||||||
[class.editing]="todo.editing">
|
[class.editing]="todo.editing">
|
||||||
<div class="view">
|
<div class="view">
|
||||||
<input class="toggle" type="checkbox"
|
<input class="toggle" type="checkbox"
|
||||||
(click)="toggleCompletion(todo)"
|
(click)="toggleCompletion(todo)"
|
||||||
[checked]="todo.completed">
|
[checked]="todo.completed">
|
||||||
<label (dblclick)="editTodo(todo)">{{todo.title}}</label>
|
<label (dblclick)="editTodo(todo)">{{todo.title}}</label>
|
||||||
<button class="destroy" (click)="remove(todo)"></button>
|
<button class="destroy" (click)="remove(todo)"></button>
|
||||||
</div>
|
</div>
|
||||||
<input *ngIf="todo.editing"
|
<input *ngIf="todo.editing"
|
||||||
class="edit" #editedtodo
|
class="edit" #editedtodo
|
||||||
[value]="todo.title"
|
[value]="todo.title"
|
||||||
(blur)="stopEditing(todo, editedtodo.value)"
|
(blur)="stopEditing(todo, editedtodo.value)"
|
||||||
(keyup)="todo.title = $event.target.value"
|
(keyup)="todo.title = $event.target.value"
|
||||||
(keyup)="$event.code == 'Enter' && updateEditingTodo(todo, editedtodo.value)"
|
(keyup)="$event.code == 'Enter' && updateEditingTodo(todo, editedtodo.value)"
|
||||||
(keyup)="$event.code == 'Escape' && cancelEditingTodo(todo)">
|
(keyup)="$event.code == 'Escape' && cancelEditingTodo(todo)">
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -98,8 +98,8 @@ class TodoStore {
|
|||||||
<strong>{{todoStore.getRemaining().length}}</strong>
|
<strong>{{todoStore.getRemaining().length}}</strong>
|
||||||
{{todoStore.getRemaining().length == 1 ? 'item' : 'items'}} left
|
{{todoStore.getRemaining().length == 1 ? 'item' : 'items'}} left
|
||||||
</span>
|
</span>
|
||||||
<button *ngIf="todoStore.getCompleted().length > 0"
|
<button *ngIf="todoStore.getCompleted().length > 0"
|
||||||
class="clear-completed"
|
class="clear-completed"
|
||||||
(click)="removeCompleted()">
|
(click)="removeCompleted()">
|
||||||
Clear completed
|
Clear completed
|
||||||
</button>
|
</button>
|
||||||
|
238
packages/core/test/render3/Inherit_definition_feature_spec.ts
Normal file
238
packages/core/test/render3/Inherit_definition_feature_spec.ts
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
/**
|
||||||
|
* @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 {DoCheck, EventEmitter, Input, OnChanges, Output, SimpleChange, SimpleChanges} from '../../src/core';
|
||||||
|
import {InheritDefinitionFeature} from '../../src/render3/features/inherit_definition_feature';
|
||||||
|
import {DirectiveDefInternal, NgOnChangesFeature, defineComponent, defineDirective} from '../../src/render3/index';
|
||||||
|
|
||||||
|
describe('InheritDefinitionFeature', () => {
|
||||||
|
it('should inherit lifecycle hooks', () => {
|
||||||
|
class SuperDirective {
|
||||||
|
ngOnInit() {}
|
||||||
|
ngOnDestroy() {}
|
||||||
|
ngAfterContentInit() {}
|
||||||
|
ngAfterContentChecked() {}
|
||||||
|
ngAfterViewInit() {}
|
||||||
|
ngAfterViewChecked() {}
|
||||||
|
ngDoCheck() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SubDirective extends SuperDirective {
|
||||||
|
ngAfterViewInit() {}
|
||||||
|
ngAfterViewChecked() {}
|
||||||
|
ngDoCheck() {}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: SubDirective,
|
||||||
|
selectors: [['', 'subDir', '']],
|
||||||
|
factory: () => new SubDirective(),
|
||||||
|
features: [InheritDefinitionFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalDef = SubDirective.ngDirectiveDef as DirectiveDefInternal<any>;
|
||||||
|
|
||||||
|
|
||||||
|
expect(finalDef.onInit).toBe(SuperDirective.prototype.ngOnInit);
|
||||||
|
expect(finalDef.onDestroy).toBe(SuperDirective.prototype.ngOnDestroy);
|
||||||
|
expect(finalDef.afterContentChecked).toBe(SuperDirective.prototype.ngAfterContentChecked);
|
||||||
|
expect(finalDef.afterContentInit).toBe(SuperDirective.prototype.ngAfterContentInit);
|
||||||
|
expect(finalDef.afterViewChecked).toBe(SubDirective.prototype.ngAfterViewChecked);
|
||||||
|
expect(finalDef.afterViewInit).toBe(SubDirective.prototype.ngAfterViewInit);
|
||||||
|
expect(finalDef.doCheck).toBe(SubDirective.prototype.ngDoCheck);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inherit inputs', () => {
|
||||||
|
// tslint:disable-next-line:class-as-namespace
|
||||||
|
class SuperDirective {
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
inputs: {
|
||||||
|
superFoo: ['foo', 'declaredFoo'],
|
||||||
|
superBar: 'bar',
|
||||||
|
superBaz: 'baz',
|
||||||
|
},
|
||||||
|
type: SuperDirective,
|
||||||
|
selectors: [['', 'superDir', '']],
|
||||||
|
factory: () => new SuperDirective(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// tslint:disable-next-line:class-as-namespace
|
||||||
|
class SubDirective extends SuperDirective {
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: SubDirective,
|
||||||
|
inputs: {
|
||||||
|
subBaz: 'baz',
|
||||||
|
subQux: 'qux',
|
||||||
|
},
|
||||||
|
selectors: [['', 'subDir', '']],
|
||||||
|
factory: () => new SubDirective(),
|
||||||
|
features: [InheritDefinitionFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const subDef = SubDirective.ngDirectiveDef as DirectiveDefInternal<any>;
|
||||||
|
|
||||||
|
expect(subDef.inputs).toEqual({
|
||||||
|
foo: 'superFoo',
|
||||||
|
bar: 'superBar',
|
||||||
|
baz: 'subBaz',
|
||||||
|
qux: 'subQux',
|
||||||
|
});
|
||||||
|
expect(subDef.declaredInputs).toEqual({
|
||||||
|
declaredFoo: 'superFoo',
|
||||||
|
bar: 'superBar',
|
||||||
|
baz: 'subBaz',
|
||||||
|
qux: 'subQux',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inherit outputs', () => {
|
||||||
|
// tslint:disable-next-line:class-as-namespace
|
||||||
|
class SuperDirective {
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
outputs: {
|
||||||
|
superFoo: 'foo',
|
||||||
|
superBar: 'bar',
|
||||||
|
superBaz: 'baz',
|
||||||
|
},
|
||||||
|
type: SuperDirective,
|
||||||
|
selectors: [['', 'superDir', '']],
|
||||||
|
factory: () => new SuperDirective(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// tslint:disable-next-line:class-as-namespace
|
||||||
|
class SubDirective extends SuperDirective {
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: SubDirective,
|
||||||
|
outputs: {
|
||||||
|
subBaz: 'baz',
|
||||||
|
subQux: 'qux',
|
||||||
|
},
|
||||||
|
selectors: [['', 'subDir', '']],
|
||||||
|
factory: () => new SubDirective(),
|
||||||
|
features: [InheritDefinitionFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const subDef = SubDirective.ngDirectiveDef as DirectiveDefInternal<any>;
|
||||||
|
|
||||||
|
expect(subDef.outputs).toEqual({
|
||||||
|
foo: 'superFoo',
|
||||||
|
bar: 'superBar',
|
||||||
|
baz: 'subBaz',
|
||||||
|
qux: 'subQux',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should compose hostBindings', () => {
|
||||||
|
const log: Array<[string, number, number]> = [];
|
||||||
|
|
||||||
|
// tslint:disable-next-line:class-as-namespace
|
||||||
|
class SuperDirective {
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: SuperDirective,
|
||||||
|
selectors: [['', 'superDir', '']],
|
||||||
|
hostBindings: (directiveIndex: number, elementIndex: number) => {
|
||||||
|
log.push(['super', directiveIndex, elementIndex]);
|
||||||
|
},
|
||||||
|
factory: () => new SuperDirective(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// tslint:disable-next-line:class-as-namespace
|
||||||
|
class SubDirective extends SuperDirective {
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: SubDirective,
|
||||||
|
selectors: [['', 'subDir', '']],
|
||||||
|
hostBindings: (directiveIndex: number, elementIndex: number) => {
|
||||||
|
log.push(['sub', directiveIndex, elementIndex]);
|
||||||
|
},
|
||||||
|
factory: () => new SubDirective(),
|
||||||
|
features: [InheritDefinitionFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const subDef = SubDirective.ngDirectiveDef as DirectiveDefInternal<any>;
|
||||||
|
|
||||||
|
subDef.hostBindings !(1, 2);
|
||||||
|
|
||||||
|
expect(log).toEqual([['super', 1, 2], ['sub', 1, 2]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if inheriting a component from a directive', () => {
|
||||||
|
// tslint:disable-next-line:class-as-namespace
|
||||||
|
class SuperComponent {
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: SuperComponent,
|
||||||
|
template: () => {},
|
||||||
|
selectors: [['', 'superDir', '']],
|
||||||
|
factory: () => new SuperComponent()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
// tslint:disable-next-line:class-as-namespace
|
||||||
|
class SubDirective extends SuperComponent{static ngDirectiveDef = defineDirective({
|
||||||
|
type: SubDirective,
|
||||||
|
selectors: [['', 'subDir', '']],
|
||||||
|
factory: () => new SubDirective(),
|
||||||
|
features: [InheritDefinitionFeature]
|
||||||
|
});}
|
||||||
|
}).toThrowError('Directives cannot inherit Components');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should run inherited features', () => {
|
||||||
|
const log: any[] = [];
|
||||||
|
|
||||||
|
// tslint:disable-next-line:class-as-namespace
|
||||||
|
class SuperDirective {
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: SuperDirective,
|
||||||
|
selectors: [['', 'superDir', '']],
|
||||||
|
factory: () => new SuperDirective(),
|
||||||
|
features: [
|
||||||
|
(arg: any) => { log.push('super1', arg); },
|
||||||
|
(arg: any) => { log.push('super2', arg); },
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class SubDirective extends SuperDirective {
|
||||||
|
@Output()
|
||||||
|
baz = new EventEmitter();
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
qux = new EventEmitter();
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: SubDirective,
|
||||||
|
selectors: [['', 'subDir', '']],
|
||||||
|
factory: () => new SubDirective(),
|
||||||
|
features: [InheritDefinitionFeature, (arg: any) => { log.push('sub1', arg); }]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const superDef = SuperDirective.ngDirectiveDef as DirectiveDefInternal<any>;
|
||||||
|
const subDef = SubDirective.ngDirectiveDef as DirectiveDefInternal<any>;
|
||||||
|
|
||||||
|
expect(log).toEqual([
|
||||||
|
'super1',
|
||||||
|
superDef,
|
||||||
|
'super2',
|
||||||
|
superDef,
|
||||||
|
'super1',
|
||||||
|
subDef,
|
||||||
|
'super2',
|
||||||
|
subDef,
|
||||||
|
'sub1',
|
||||||
|
subDef,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
@ -38,8 +38,7 @@ NgForOf.ngDirectiveDef = defineDirective({
|
|||||||
type: NgTemplateOutletDef,
|
type: NgTemplateOutletDef,
|
||||||
selectors: [['', 'ngTemplateOutlet', '']],
|
selectors: [['', 'ngTemplateOutlet', '']],
|
||||||
factory: () => new NgTemplateOutletDef(injectViewContainerRef()),
|
factory: () => new NgTemplateOutletDef(injectViewContainerRef()),
|
||||||
features: [NgOnChangesFeature(
|
features: [NgOnChangesFeature],
|
||||||
{ngTemplateOutlet: 'ngTemplateOutlet', ngTemplateOutletContext: 'ngTemplateOutletContext'})],
|
|
||||||
inputs:
|
inputs:
|
||||||
{ngTemplateOutlet: 'ngTemplateOutlet', ngTemplateOutletContext: 'ngTemplateOutletContext'}
|
{ngTemplateOutlet: 'ngTemplateOutlet', ngTemplateOutletContext: 'ngTemplateOutletContext'}
|
||||||
});
|
});
|
||||||
|
@ -47,8 +47,8 @@ describe('lifecycle hooks', () => {
|
|||||||
selectors: [['lifecycle-comp']],
|
selectors: [['lifecycle-comp']],
|
||||||
factory: function LifecycleComp_Factory() { return new LifecycleComp(); },
|
factory: function LifecycleComp_Factory() { return new LifecycleComp(); },
|
||||||
template: function LifecycleComp_Template(rf: $RenderFlags$, ctx: $LifecycleComp$) {},
|
template: function LifecycleComp_Template(rf: $RenderFlags$, ctx: $LifecycleComp$) {},
|
||||||
inputs: {nameMin: 'name'},
|
inputs: {nameMin: ['name', 'nameMin']},
|
||||||
features: [$r3$.ɵNgOnChangesFeature({nameMin: 'nameMin'})]
|
features: [$r3$.ɵNgOnChangesFeature]
|
||||||
});
|
});
|
||||||
// /NORMATIVE
|
// /NORMATIVE
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ describe('template variables', () => {
|
|||||||
return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
|
return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
|
||||||
},
|
},
|
||||||
// TODO(chuckj): Enable when ngForOf enabling lands.
|
// TODO(chuckj): Enable when ngForOf enabling lands.
|
||||||
// features: [NgOnChangesFeature(NgForOf)],
|
// features: [NgOnChangesFeature],
|
||||||
inputs: {forOf: 'forOf'}
|
inputs: {forOf: 'forOf'}
|
||||||
});
|
});
|
||||||
// /NORMATIVE
|
// /NORMATIVE
|
||||||
|
@ -1,120 +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 {DoCheck, OnChanges, SimpleChange, SimpleChanges} from '../../src/core';
|
|
||||||
import {DirectiveDefInternal, NgOnChangesFeature, defineDirective} from '../../src/render3/index';
|
|
||||||
|
|
||||||
describe('define', () => {
|
|
||||||
describe('component', () => {
|
|
||||||
describe('NgOnChangesFeature', () => {
|
|
||||||
it('should patch class', () => {
|
|
||||||
class MyDirective implements OnChanges, DoCheck {
|
|
||||||
public log: Array<string|SimpleChange> = [];
|
|
||||||
public valA: string = 'initValue';
|
|
||||||
public set valB(value: string) { this.log.push(value); }
|
|
||||||
|
|
||||||
public get valB() { return 'works'; }
|
|
||||||
|
|
||||||
ngDoCheck(): void { this.log.push('ngDoCheck'); }
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
|
||||||
this.log.push('ngOnChanges');
|
|
||||||
this.log.push('valA', changes['valA']);
|
|
||||||
this.log.push('valB', changes['valB']);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ngDirectiveDef = defineDirective({
|
|
||||||
type: MyDirective,
|
|
||||||
selectors: [['', 'myDir', '']],
|
|
||||||
factory: () => new MyDirective(),
|
|
||||||
features: [NgOnChangesFeature()],
|
|
||||||
inputs: {valA: 'valA', valB: 'valB'}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const myDir = (MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>)
|
|
||||||
.factory() as MyDirective;
|
|
||||||
myDir.valA = 'first';
|
|
||||||
expect(myDir.valA).toEqual('first');
|
|
||||||
myDir.valB = 'second';
|
|
||||||
expect(myDir.log).toEqual(['second']);
|
|
||||||
expect(myDir.valB).toEqual('works');
|
|
||||||
myDir.log.length = 0;
|
|
||||||
(MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>).doCheck !.call(myDir);
|
|
||||||
const changeA = new SimpleChange(undefined, 'first', true);
|
|
||||||
const changeB = new SimpleChange(undefined, 'second', true);
|
|
||||||
expect(myDir.log).toEqual(['ngOnChanges', 'valA', changeA, 'valB', changeB, 'ngDoCheck']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('correctly computes firstChange', () => {
|
|
||||||
class MyDirective implements OnChanges {
|
|
||||||
public log: Array<string|SimpleChange> = [];
|
|
||||||
public valA: string = 'initValue';
|
|
||||||
// TODO(issue/24571): remove '!'.
|
|
||||||
public valB !: string;
|
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
|
||||||
this.log.push('valA', changes['valA']);
|
|
||||||
this.log.push('valB', changes['valB']);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ngDirectiveDef = defineDirective({
|
|
||||||
type: MyDirective,
|
|
||||||
selectors: [['', 'myDir', '']],
|
|
||||||
factory: () => new MyDirective(),
|
|
||||||
features: [NgOnChangesFeature()],
|
|
||||||
inputs: {valA: 'valA', valB: 'valB'}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const myDir = (MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>)
|
|
||||||
.factory() as MyDirective;
|
|
||||||
myDir.valA = 'first';
|
|
||||||
myDir.valB = 'second';
|
|
||||||
(MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>).doCheck !.call(myDir);
|
|
||||||
const changeA1 = new SimpleChange(undefined, 'first', true);
|
|
||||||
const changeB1 = new SimpleChange(undefined, 'second', true);
|
|
||||||
expect(myDir.log).toEqual(['valA', changeA1, 'valB', changeB1]);
|
|
||||||
|
|
||||||
myDir.log.length = 0;
|
|
||||||
myDir.valA = 'third';
|
|
||||||
(MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>).doCheck !.call(myDir);
|
|
||||||
const changeA2 = new SimpleChange('first', 'third', false);
|
|
||||||
expect(myDir.log).toEqual(['valA', changeA2, 'valB', undefined]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not create a getter when only a setter is originally defined', () => {
|
|
||||||
class MyDirective implements OnChanges {
|
|
||||||
public log: Array<string|SimpleChange> = [];
|
|
||||||
|
|
||||||
public set onlySetter(value: string) { this.log.push(value); }
|
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
|
||||||
this.log.push('ngOnChanges');
|
|
||||||
this.log.push('onlySetter', changes['onlySetter']);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ngDirectiveDef = defineDirective({
|
|
||||||
type: MyDirective,
|
|
||||||
selectors: [['', 'myDir', '']],
|
|
||||||
factory: () => new MyDirective(),
|
|
||||||
features: [NgOnChangesFeature()],
|
|
||||||
inputs: {onlySetter: 'onlySetter'}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const myDir = (MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>)
|
|
||||||
.factory() as MyDirective;
|
|
||||||
myDir.onlySetter = 'someValue';
|
|
||||||
expect(myDir.onlySetter).toBeUndefined();
|
|
||||||
(MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>).doCheck !.call(myDir);
|
|
||||||
const changeSetter = new SimpleChange(undefined, 'someValue', true);
|
|
||||||
expect(myDir.log).toEqual(['someValue', 'ngOnChanges', 'onlySetter', changeSetter]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -995,7 +995,7 @@ describe('di', () => {
|
|||||||
selectors: [['', 'myIf', '']],
|
selectors: [['', 'myIf', '']],
|
||||||
factory: () => new IfDirective(injectTemplateRef(), injectViewContainerRef()),
|
factory: () => new IfDirective(injectTemplateRef(), injectViewContainerRef()),
|
||||||
inputs: {myIf: 'myIf'},
|
inputs: {myIf: 'myIf'},
|
||||||
features: [PublicFeature, NgOnChangesFeature()]
|
features: [PublicFeature, NgOnChangesFeature]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
39
packages/core/test/render3/jit/directive_spec.ts
Normal file
39
packages/core/test/render3/jit/directive_spec.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* @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 {extendsDirectlyFromObject} from '../../../src/render3/jit/directive';
|
||||||
|
|
||||||
|
describe('extendsDirectlyFromObject', () => {
|
||||||
|
it('should correctly behave with instanceof', () => {
|
||||||
|
expect(new Child() instanceof Object).toBeTruthy();
|
||||||
|
expect(new Child() instanceof Parent).toBeTruthy();
|
||||||
|
expect(new Parent() instanceof Child).toBeFalsy();
|
||||||
|
|
||||||
|
expect(new Child5() instanceof Object).toBeTruthy();
|
||||||
|
expect(new Child5() instanceof Parent5).toBeTruthy();
|
||||||
|
expect(new Parent5() instanceof Child5).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect direct inheritance form Object', () => {
|
||||||
|
expect(extendsDirectlyFromObject(Parent)).toBeTruthy();
|
||||||
|
expect(extendsDirectlyFromObject(Child)).toBeFalsy();
|
||||||
|
|
||||||
|
expect(extendsDirectlyFromObject(Parent5)).toBeTruthy();
|
||||||
|
expect(extendsDirectlyFromObject(Child5)).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Inheritance Example using Classes
|
||||||
|
class Parent {}
|
||||||
|
class Child extends Parent {}
|
||||||
|
|
||||||
|
// Inheritance Example using Function
|
||||||
|
const Parent5 = function Parent5() {} as any as{new (): {}};
|
||||||
|
const Child5 = function Child5() {} as any as{new (): {}};
|
||||||
|
Child5.prototype = new Parent5;
|
||||||
|
Child5.prototype.constructor = Child5;
|
@ -1932,8 +1932,8 @@ describe('lifecycles', () => {
|
|||||||
type: Component,
|
type: Component,
|
||||||
selectors: [[name]],
|
selectors: [[name]],
|
||||||
factory: () => new Component(),
|
factory: () => new Component(),
|
||||||
features: [NgOnChangesFeature({b: 'val2'})],
|
features: [NgOnChangesFeature],
|
||||||
inputs: {a: 'val1', b: 'publicName'}, template,
|
inputs: {a: 'val1', b: ['publicName', 'val2']}, template,
|
||||||
directives: directives
|
directives: directives
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -1953,8 +1953,8 @@ describe('lifecycles', () => {
|
|||||||
type: Directive,
|
type: Directive,
|
||||||
selectors: [['', 'dir', '']],
|
selectors: [['', 'dir', '']],
|
||||||
factory: () => new Directive(),
|
factory: () => new Directive(),
|
||||||
features: [NgOnChangesFeature({b: 'val2'})],
|
features: [NgOnChangesFeature],
|
||||||
inputs: {a: 'val1', b: 'publicName'}
|
inputs: {a: 'val1', b: ['publicName', 'val2']}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1976,11 +1976,10 @@ describe('lifecycles', () => {
|
|||||||
renderToHtml(Template, {val1: '1', val2: 'a'}, defs);
|
renderToHtml(Template, {val1: '1', val2: 'a'}, defs);
|
||||||
expect(events).toEqual(['comp=comp val1=1 val2=a - changed=[val1,val2]']);
|
expect(events).toEqual(['comp=comp val1=1 val2=a - changed=[val1,val2]']);
|
||||||
|
|
||||||
|
events.length = 0;
|
||||||
|
|
||||||
renderToHtml(Template, {val1: '2', val2: 'b'}, defs);
|
renderToHtml(Template, {val1: '2', val2: 'b'}, defs);
|
||||||
expect(events).toEqual([
|
expect(events).toEqual(['comp=comp val1=2 val2=b - changed=[val1,val2]']);
|
||||||
'comp=comp val1=1 val2=a - changed=[val1,val2]',
|
|
||||||
'comp=comp val1=2 val2=b - changed=[val1,val2]'
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call parent onChanges before child onChanges', () => {
|
it('should call parent onChanges before child onChanges', () => {
|
||||||
@ -2336,7 +2335,7 @@ describe('lifecycles', () => {
|
|||||||
selectors: [[name]],
|
selectors: [[name]],
|
||||||
factory: () => new Component(),
|
factory: () => new Component(),
|
||||||
inputs: {val: 'val'}, template,
|
inputs: {val: 'val'}, template,
|
||||||
features: [NgOnChangesFeature()],
|
features: [NgOnChangesFeature],
|
||||||
directives: directives
|
directives: directives
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
325
packages/core/test/render3/ng_on_changes_feature_spec.ts
Normal file
325
packages/core/test/render3/ng_on_changes_feature_spec.ts
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
/**
|
||||||
|
* @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 {DoCheck, EventEmitter, Input, OnChanges, Output, SimpleChange, SimpleChanges} from '../../src/core';
|
||||||
|
import {InheritDefinitionFeature} from '../../src/render3/features/inherit_definition_feature';
|
||||||
|
import {DirectiveDefInternal, NgOnChangesFeature, defineComponent, defineDirective} from '../../src/render3/index';
|
||||||
|
|
||||||
|
describe('NgOnChangesFeature', () => {
|
||||||
|
it('should patch class', () => {
|
||||||
|
class MyDirective implements OnChanges, DoCheck {
|
||||||
|
public log: Array<string|SimpleChange> = [];
|
||||||
|
public valA: string = 'initValue';
|
||||||
|
public set valB(value: string) { this.log.push(value); }
|
||||||
|
|
||||||
|
public get valB() { return 'works'; }
|
||||||
|
|
||||||
|
ngDoCheck(): void { this.log.push('ngDoCheck'); }
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
this.log.push('ngOnChanges');
|
||||||
|
this.log.push('valA', changes['valA']);
|
||||||
|
this.log.push('valB', changes['valB']);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: MyDirective,
|
||||||
|
selectors: [['', 'myDir', '']],
|
||||||
|
factory: () => new MyDirective(),
|
||||||
|
features: [NgOnChangesFeature],
|
||||||
|
inputs: {valA: 'valA', valB: 'valB'}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const myDir =
|
||||||
|
(MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>).factory() as MyDirective;
|
||||||
|
myDir.valA = 'first';
|
||||||
|
expect(myDir.valA).toEqual('first');
|
||||||
|
myDir.valB = 'second';
|
||||||
|
expect(myDir.log).toEqual(['second']);
|
||||||
|
expect(myDir.valB).toEqual('works');
|
||||||
|
myDir.log.length = 0;
|
||||||
|
(MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>).doCheck !.call(myDir);
|
||||||
|
const changeA = new SimpleChange(undefined, 'first', true);
|
||||||
|
const changeB = new SimpleChange(undefined, 'second', true);
|
||||||
|
expect(myDir.log).toEqual(['ngOnChanges', 'valA', changeA, 'valB', changeB, 'ngDoCheck']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inherit the behavior from super class', () => {
|
||||||
|
const log: any[] = [];
|
||||||
|
|
||||||
|
class SuperDirective implements OnChanges, DoCheck {
|
||||||
|
valA = 'initValue';
|
||||||
|
|
||||||
|
set valB(value: string) { log.push(value); }
|
||||||
|
|
||||||
|
get valB() { return 'works'; }
|
||||||
|
|
||||||
|
ngDoCheck(): void { log.push('ngDoCheck'); }
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
log.push('ngOnChanges');
|
||||||
|
log.push('valA', changes['valA']);
|
||||||
|
log.push('valB', changes['valB']);
|
||||||
|
log.push('valC', changes['valC']);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: SuperDirective,
|
||||||
|
selectors: [['', 'superDir', '']],
|
||||||
|
factory: () => new SuperDirective(),
|
||||||
|
features: [NgOnChangesFeature],
|
||||||
|
inputs: {valA: 'valA', valB: 'valB'},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class SubDirective extends SuperDirective {
|
||||||
|
valC = 'initValue';
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: SubDirective,
|
||||||
|
selectors: [['', 'subDir', '']],
|
||||||
|
factory: () => new SubDirective(),
|
||||||
|
features: [InheritDefinitionFeature],
|
||||||
|
inputs: {valC: 'valC'},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const myDir = (SubDirective.ngDirectiveDef as DirectiveDefInternal<SubDirective>)
|
||||||
|
.factory() as SubDirective;
|
||||||
|
myDir.valA = 'first';
|
||||||
|
expect(myDir.valA).toEqual('first');
|
||||||
|
|
||||||
|
myDir.valB = 'second';
|
||||||
|
expect(myDir.valB).toEqual('works');
|
||||||
|
|
||||||
|
myDir.valC = 'third';
|
||||||
|
expect(myDir.valC).toEqual('third');
|
||||||
|
|
||||||
|
log.length = 0;
|
||||||
|
(SubDirective.ngDirectiveDef as DirectiveDefInternal<SubDirective>).doCheck !.call(myDir);
|
||||||
|
const changeA = new SimpleChange(undefined, 'first', true);
|
||||||
|
const changeB = new SimpleChange(undefined, 'second', true);
|
||||||
|
const changeC = new SimpleChange(undefined, 'third', true);
|
||||||
|
|
||||||
|
expect(log).toEqual(
|
||||||
|
['ngOnChanges', 'valA', changeA, 'valB', changeB, 'valC', changeC, 'ngDoCheck']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not run the parent doCheck if it is not called explicitly on super class', () => {
|
||||||
|
const log: any[] = [];
|
||||||
|
|
||||||
|
class SuperDirective implements OnChanges, DoCheck {
|
||||||
|
valA = 'initValue';
|
||||||
|
|
||||||
|
ngDoCheck(): void { log.push('ERROR: Child overrides it without super call'); }
|
||||||
|
ngOnChanges(changes: SimpleChanges): void { log.push(changes.valA, changes.valB); }
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: SuperDirective,
|
||||||
|
selectors: [['', 'superDir', '']],
|
||||||
|
factory: () => new SuperDirective(),
|
||||||
|
features: [NgOnChangesFeature],
|
||||||
|
inputs: {valA: 'valA'},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class SubDirective extends SuperDirective implements DoCheck {
|
||||||
|
valB = 'initValue';
|
||||||
|
|
||||||
|
ngDoCheck(): void { log.push('sub ngDoCheck'); }
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: SubDirective,
|
||||||
|
selectors: [['', 'subDir', '']],
|
||||||
|
factory: () => new SubDirective(),
|
||||||
|
features: [InheritDefinitionFeature],
|
||||||
|
inputs: {valB: 'valB'},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const myDir = (SubDirective.ngDirectiveDef as DirectiveDefInternal<SubDirective>)
|
||||||
|
.factory() as SubDirective;
|
||||||
|
myDir.valA = 'first';
|
||||||
|
myDir.valB = 'second';
|
||||||
|
|
||||||
|
(SubDirective.ngDirectiveDef as DirectiveDefInternal<SubDirective>).doCheck !.call(myDir);
|
||||||
|
const changeA = new SimpleChange(undefined, 'first', true);
|
||||||
|
const changeB = new SimpleChange(undefined, 'second', true);
|
||||||
|
expect(log).toEqual([changeA, changeB, 'sub ngDoCheck']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should run the parent doCheck if it is inherited from super class', () => {
|
||||||
|
const log: any[] = [];
|
||||||
|
|
||||||
|
class SuperDirective implements OnChanges, DoCheck {
|
||||||
|
valA = 'initValue';
|
||||||
|
|
||||||
|
ngDoCheck(): void { log.push('super ngDoCheck'); }
|
||||||
|
ngOnChanges(changes: SimpleChanges): void { log.push(changes.valA, changes.valB); }
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: SuperDirective,
|
||||||
|
selectors: [['', 'superDir', '']],
|
||||||
|
factory: () => new SuperDirective(),
|
||||||
|
features: [NgOnChangesFeature],
|
||||||
|
inputs: {valA: 'valA'},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class SubDirective extends SuperDirective implements DoCheck {
|
||||||
|
valB = 'initValue';
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: SubDirective,
|
||||||
|
selectors: [['', 'subDir', '']],
|
||||||
|
factory: () => new SubDirective(),
|
||||||
|
features: [InheritDefinitionFeature],
|
||||||
|
inputs: {valB: 'valB'},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const myDir = (SubDirective.ngDirectiveDef as DirectiveDefInternal<SubDirective>)
|
||||||
|
.factory() as SubDirective;
|
||||||
|
myDir.valA = 'first';
|
||||||
|
myDir.valB = 'second';
|
||||||
|
|
||||||
|
(SubDirective.ngDirectiveDef as DirectiveDefInternal<SubDirective>).doCheck !.call(myDir);
|
||||||
|
const changeA = new SimpleChange(undefined, 'first', true);
|
||||||
|
const changeB = new SimpleChange(undefined, 'second', true);
|
||||||
|
expect(log).toEqual([changeA, changeB, 'super ngDoCheck']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should apply the feature to inherited properties if on sub class', () => {
|
||||||
|
const log: any[] = [];
|
||||||
|
|
||||||
|
class SuperDirective {
|
||||||
|
valC = 'initValue';
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: SuperDirective,
|
||||||
|
selectors: [['', 'subDir', '']],
|
||||||
|
factory: () => new SuperDirective(),
|
||||||
|
features: [],
|
||||||
|
inputs: {valC: 'valC'},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class SubDirective extends SuperDirective implements OnChanges, DoCheck {
|
||||||
|
valA = 'initValue';
|
||||||
|
|
||||||
|
set valB(value: string) { log.push(value); }
|
||||||
|
|
||||||
|
get valB() { return 'works'; }
|
||||||
|
|
||||||
|
ngDoCheck(): void { log.push('ngDoCheck'); }
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
log.push('ngOnChanges');
|
||||||
|
log.push('valA', changes['valA']);
|
||||||
|
log.push('valB', changes['valB']);
|
||||||
|
log.push('valC', changes['valC']);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: SubDirective,
|
||||||
|
selectors: [['', 'superDir', '']],
|
||||||
|
factory: () => new SubDirective(),
|
||||||
|
// Inheritance must always be before OnChanges feature.
|
||||||
|
features: [
|
||||||
|
InheritDefinitionFeature,
|
||||||
|
NgOnChangesFeature,
|
||||||
|
],
|
||||||
|
inputs: {valA: 'valA', valB: 'valB'}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const myDir = (SubDirective.ngDirectiveDef as DirectiveDefInternal<SubDirective>)
|
||||||
|
.factory() as SubDirective;
|
||||||
|
myDir.valA = 'first';
|
||||||
|
expect(myDir.valA).toEqual('first');
|
||||||
|
|
||||||
|
myDir.valB = 'second';
|
||||||
|
expect(log).toEqual(['second']);
|
||||||
|
expect(myDir.valB).toEqual('works');
|
||||||
|
|
||||||
|
myDir.valC = 'third';
|
||||||
|
expect(myDir.valC).toEqual('third');
|
||||||
|
|
||||||
|
log.length = 0;
|
||||||
|
(SubDirective.ngDirectiveDef as DirectiveDefInternal<SubDirective>).doCheck !.call(myDir);
|
||||||
|
const changeA = new SimpleChange(undefined, 'first', true);
|
||||||
|
const changeB = new SimpleChange(undefined, 'second', true);
|
||||||
|
const changeC = new SimpleChange(undefined, 'third', true);
|
||||||
|
expect(log).toEqual(
|
||||||
|
['ngOnChanges', 'valA', changeA, 'valB', changeB, 'valC', changeC, 'ngDoCheck']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly computes firstChange', () => {
|
||||||
|
class MyDirective implements OnChanges {
|
||||||
|
public log: Array<string|SimpleChange> = [];
|
||||||
|
public valA: string = 'initValue';
|
||||||
|
// TODO(issue/24571): remove '!'.
|
||||||
|
public valB !: string;
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
this.log.push('valA', changes['valA']);
|
||||||
|
this.log.push('valB', changes['valB']);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: MyDirective,
|
||||||
|
selectors: [['', 'myDir', '']],
|
||||||
|
factory: () => new MyDirective(),
|
||||||
|
features: [NgOnChangesFeature],
|
||||||
|
inputs: {valA: 'valA', valB: 'valB'}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const myDir =
|
||||||
|
(MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>).factory() as MyDirective;
|
||||||
|
myDir.valA = 'first';
|
||||||
|
myDir.valB = 'second';
|
||||||
|
(MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>).doCheck !.call(myDir);
|
||||||
|
const changeA1 = new SimpleChange(undefined, 'first', true);
|
||||||
|
const changeB1 = new SimpleChange(undefined, 'second', true);
|
||||||
|
expect(myDir.log).toEqual(['valA', changeA1, 'valB', changeB1]);
|
||||||
|
|
||||||
|
myDir.log.length = 0;
|
||||||
|
myDir.valA = 'third';
|
||||||
|
(MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>).doCheck !.call(myDir);
|
||||||
|
const changeA2 = new SimpleChange('first', 'third', false);
|
||||||
|
expect(myDir.log).toEqual(['valA', changeA2, 'valB', undefined]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create a getter when only a setter is originally defined', () => {
|
||||||
|
class MyDirective implements OnChanges {
|
||||||
|
public log: Array<string|SimpleChange> = [];
|
||||||
|
|
||||||
|
public set onlySetter(value: string) { this.log.push(value); }
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
this.log.push('ngOnChanges');
|
||||||
|
this.log.push('onlySetter', changes['onlySetter']);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: MyDirective,
|
||||||
|
selectors: [['', 'myDir', '']],
|
||||||
|
factory: () => new MyDirective(),
|
||||||
|
features: [NgOnChangesFeature],
|
||||||
|
inputs: {onlySetter: 'onlySetter'}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const myDir =
|
||||||
|
(MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>).factory() as MyDirective;
|
||||||
|
myDir.onlySetter = 'someValue';
|
||||||
|
expect(myDir.onlySetter).toBeUndefined();
|
||||||
|
(MyDirective.ngDirectiveDef as DirectiveDefInternal<MyDirective>).doCheck !.call(myDir);
|
||||||
|
const changeSetter = new SimpleChange(undefined, 'someValue', true);
|
||||||
|
expect(myDir.log).toEqual(['someValue', 'ngOnChanges', 'onlySetter', changeSetter]);
|
||||||
|
});
|
||||||
|
});
|
@ -11,7 +11,7 @@ import {stringifyElement} from '@angular/platform-browser/testing/src/browser_ut
|
|||||||
import {Injector} from '../../src/di/injector';
|
import {Injector} from '../../src/di/injector';
|
||||||
import {CreateComponentOptions} from '../../src/render3/component';
|
import {CreateComponentOptions} from '../../src/render3/component';
|
||||||
import {extractDirectiveDef, extractPipeDef} from '../../src/render3/definition';
|
import {extractDirectiveDef, extractPipeDef} from '../../src/render3/definition';
|
||||||
import {ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDefInternal, DirectiveType, PublicFeature, RenderFlags, defineComponent, defineDirective, renderComponent as _renderComponent, tick} from '../../src/render3/index';
|
import {ComponentTemplate, ComponentType, DirectiveDefInternal, DirectiveType, PublicFeature, RenderFlags, defineComponent, defineDirective, renderComponent as _renderComponent, tick} from '../../src/render3/index';
|
||||||
import {NG_HOST_SYMBOL, renderTemplate} from '../../src/render3/instructions';
|
import {NG_HOST_SYMBOL, renderTemplate} from '../../src/render3/instructions';
|
||||||
import {DirectiveDefList, DirectiveDefListOrFactory, DirectiveTypesOrFactory, PipeDef, PipeDefList, PipeDefListOrFactory, PipeTypesOrFactory} from '../../src/render3/interfaces/definition';
|
import {DirectiveDefList, DirectiveDefListOrFactory, DirectiveTypesOrFactory, PipeDef, PipeDefList, PipeDefListOrFactory, PipeTypesOrFactory} from '../../src/render3/interfaces/definition';
|
||||||
import {LElementNode} from '../../src/render3/interfaces/node';
|
import {LElementNode} from '../../src/render3/interfaces/node';
|
||||||
|
@ -966,7 +966,7 @@ describe('ViewContainerRef', () => {
|
|||||||
textBinding(0, interpolation1('', cmp.name, ''));
|
textBinding(0, interpolation1('', cmp.name, ''));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
features: [NgOnChangesFeature()],
|
features: [NgOnChangesFeature],
|
||||||
inputs: {name: 'name'}
|
inputs: {name: 'name'}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user