fix(ivy): host bindings and listeners not being inherited from undecorated classes (#30158)

Fixes `HostBinding` and `HostListener` declarations not being inherited from base classes that don't have an Angular decorator.

This PR resolves FW-1275.

PR Close #30158
This commit is contained in:
Kristiyan Kostadinov
2019-04-27 09:33:10 +02:00
committed by Andrew Kushnir
parent 164d160b22
commit 68ff2cc323
15 changed files with 365 additions and 117 deletions

View File

@ -149,6 +149,8 @@ export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade {
}
export interface R3BaseMetadataFacade {
name: string;
propMetadata: {[key: string]: any[]};
inputs?: {[key: string]: string | [string, string]};
outputs?: {[key: string]: string};
queries?: R3QueryMetadataFacade[];

View File

@ -571,6 +571,11 @@ export function ɵɵdefineBase<T>(baseDefinition: {
* set of instructions to be inserted into the template function.
*/
viewQuery?: ViewQueriesFunction<T>| null;
/**
* Function executed by the parent template to allow children to apply host bindings.
*/
hostBindings?: HostBindingsFunction<T>;
}): ɵɵBaseDef<T> {
const declaredInputs: {[P in keyof T]: string} = {} as any;
return {
@ -579,6 +584,7 @@ export function ɵɵdefineBase<T>(baseDefinition: {
outputs: invertObject<T>(baseDefinition.outputs as any),
viewQuery: baseDefinition.viewQuery || null,
contentQueries: baseDefinition.contentQueries || null,
hostBindings: baseDefinition.hostBindings || null
};
}

View File

@ -9,7 +9,7 @@
import {Type} from '../../interface/type';
import {fillProperties} from '../../util/property';
import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
import {ComponentDef, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
import {ComponentDef, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, HostBindingsFunction, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
import {adjustActiveDirectiveSuperClassDepthPosition} from '../state';
import {isComponentDef} from '../util/view_utils';
@ -56,6 +56,8 @@ export function ɵɵInheritDefinitionFeature(definition: DirectiveDef<any>| Comp
if (baseDef) {
const baseViewQuery = baseDef.viewQuery;
const baseContentQueries = baseDef.contentQueries;
const baseHostBindings = baseDef.hostBindings;
baseHostBindings && inheritHostBindings(definition, baseHostBindings);
baseViewQuery && inheritViewQuery(definition, baseViewQuery);
baseContentQueries && inheritContentQueries(definition, baseContentQueries);
fillProperties(definition.inputs, baseDef.inputs);
@ -65,34 +67,8 @@ export function ɵɵInheritDefinitionFeature(definition: DirectiveDef<any>| Comp
if (superDef) {
// Merge hostBindings
const prevHostBindings = definition.hostBindings;
const superHostBindings = superDef.hostBindings;
if (superHostBindings) {
if (prevHostBindings) {
// because inheritance is unknown during compile time, the runtime code
// needs to be informed of the super-class depth so that instruction code
// can distinguish one host bindings function from another. The reason why
// relying on the directive uniqueId exclusively is not enough is because the
// uniqueId value and the directive instance stay the same between hostBindings
// calls throughout the directive inheritance chain. This means that without
// a super-class depth value, there is no way to know whether a parent or
// sub-class host bindings function is currently being executed.
definition.hostBindings = (rf: RenderFlags, ctx: any, elementIndex: number) => {
// The reason why we increment first and then decrement is so that parent
// hostBindings calls have a higher id value compared to sub-class hostBindings
// calls (this way the leaf directive is always at a super-class depth of 0).
adjustActiveDirectiveSuperClassDepthPosition(1);
try {
superHostBindings(rf, ctx, elementIndex);
} finally {
adjustActiveDirectiveSuperClassDepthPosition(-1);
}
prevHostBindings(rf, ctx, elementIndex);
};
} else {
definition.hostBindings = superHostBindings;
}
}
superHostBindings && inheritHostBindings(definition, superHostBindings);
// Merge queries
const superViewQuery = superDef.viewQuery;
@ -190,3 +166,33 @@ function inheritContentQueries(
definition.contentQueries = superContentQueries;
}
}
function inheritHostBindings(
definition: DirectiveDef<any>| ComponentDef<any>,
superHostBindings: HostBindingsFunction<any>) {
const prevHostBindings = definition.hostBindings;
if (prevHostBindings) {
// because inheritance is unknown during compile time, the runtime code
// needs to be informed of the super-class depth so that instruction code
// can distinguish one host bindings function from another. The reason why
// relying on the directive uniqueId exclusively is not enough is because the
// uniqueId value and the directive instance stay the same between hostBindings
// calls throughout the directive inheritance chain. This means that without
// a super-class depth value, there is no way to know whether a parent or
// sub-class host bindings function is currently being executed.
definition.hostBindings = (rf: RenderFlags, ctx: any, elementIndex: number) => {
// The reason why we increment first and then decrement is so that parent
// hostBindings calls have a higher id value compared to sub-class hostBindings
// calls (this way the leaf directive is always at a super-class depth of 0).
adjustActiveDirectiveSuperClassDepthPosition(1);
try {
superHostBindings(rf, ctx, elementIndex);
} finally {
adjustActiveDirectiveSuperClassDepthPosition(-1);
}
prevHostBindings(rf, ctx, elementIndex);
};
} else {
definition.hostBindings = superHostBindings;
}
}

View File

@ -135,6 +135,11 @@ export interface ɵɵBaseDef<T> {
* components that extend the directive.
*/
viewQuery: ViewQueriesFunction<T>|null;
/**
* Refreshes host bindings on the associated directive.
*/
hostBindings: HostBindingsFunction<T>|null;
}
/**
@ -173,11 +178,6 @@ export interface DirectiveDef<T> extends ɵɵBaseDef<T> {
*/
factory: FactoryFn<T>;
/**
* Refreshes host bindings on the associated directive.
*/
hostBindings: HostBindingsFunction<T>|null;
/* The following are lifecycle hooks for this component */
onChanges: (() => void)|null;
onInit: (() => void)|null;

View File

@ -220,22 +220,28 @@ function extractBaseDefMetadata(type: Type<any>): R3BaseMetadataFacade|null {
const queries = extractQueriesMetadata(type, propMetadata, isContentQuery);
let inputs: {[key: string]: string | [string, string]}|undefined;
let outputs: {[key: string]: string}|undefined;
// We only need to know whether there are any HostListener or HostBinding
// decorators present, the parsing logic is in the compiler already.
let hasHostDecorators = false;
for (const field in propMetadata) {
propMetadata[field].forEach(ann => {
if (ann.ngMetadataName === 'Input') {
const metadataName = ann.ngMetadataName;
if (metadataName === 'Input') {
inputs = inputs || {};
inputs[field] = ann.bindingPropertyName ? [ann.bindingPropertyName, field] : field;
} else if (ann.ngMetadataName === 'Output') {
} else if (metadataName === 'Output') {
outputs = outputs || {};
outputs[field] = ann.bindingPropertyName || field;
} else if (metadataName === 'HostBinding' || metadataName === 'HostListener') {
hasHostDecorators = true;
}
});
}
// Only generate the base def if there's any info inside it.
if (inputs || outputs || viewQueries.length || queries.length) {
return {inputs, outputs, viewQueries, queries};
if (inputs || outputs || viewQueries.length || queries.length || hasHostDecorators) {
return {name: type.name, inputs, outputs, viewQueries, queries, propMetadata};
}
return null;