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:

committed by
Andrew Kushnir

parent
164d160b22
commit
68ff2cc323
@ -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[];
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
Reference in New Issue
Block a user