fix(ivy): listeners inherited twice if sub class has own propMetadata (#29353)
Fixes host listeners being inherited twice, if the sub class has its own `propMetadata`. This is related to #29170 which fixed something similar, however all of the test cases there had a super class with some metadata and a sub class that didn't have any. The issue manifested itself in the `MatTreeToggle` which inherits a listener from the `CdkTreeToggle` and adds an extra `Input` of its own, causing the listener to be added twice. PR Close #29353
This commit is contained in:
parent
e8df000e97
commit
3d5b98631a
|
@ -27,8 +27,17 @@ export function setClassMetadata(
|
||||||
type: Type<any>, decorators: any[] | null, ctorParameters: (() => any[]) | null,
|
type: Type<any>, decorators: any[] | null, ctorParameters: (() => any[]) | null,
|
||||||
propDecorators: {[field: string]: any} | null): void {
|
propDecorators: {[field: string]: any} | null): void {
|
||||||
const clazz = type as TypeWithMetadata;
|
const clazz = type as TypeWithMetadata;
|
||||||
|
|
||||||
|
// We determine whether a class has its own metadata by taking the metadata from the parent
|
||||||
|
// constructor and checking whether it's the same as the subclass metadata below. We can't use
|
||||||
|
// `hasOwnProperty` here because it doesn't work correctly in IE10 for static fields that are
|
||||||
|
// defined by TS. See https://github.com/angular/angular/pull/28439#issuecomment-459349218.
|
||||||
|
const parentPrototype = clazz.prototype ? Object.getPrototypeOf(clazz.prototype) : null;
|
||||||
|
const parentConstructor: TypeWithMetadata|null = parentPrototype && parentPrototype.constructor;
|
||||||
|
|
||||||
if (decorators !== null) {
|
if (decorators !== null) {
|
||||||
if (clazz.hasOwnProperty('decorators') && clazz.decorators !== undefined) {
|
if (clazz.decorators !== undefined &&
|
||||||
|
(!parentConstructor || parentConstructor.decorators !== clazz.decorators)) {
|
||||||
clazz.decorators.push(...decorators);
|
clazz.decorators.push(...decorators);
|
||||||
} else {
|
} else {
|
||||||
clazz.decorators = decorators;
|
clazz.decorators = decorators;
|
||||||
|
@ -45,7 +54,8 @@ export function setClassMetadata(
|
||||||
// decorator types. Decorators on individual fields are not merged, as it's also incredibly
|
// decorator types. Decorators on individual fields are not merged, as it's also incredibly
|
||||||
// unlikely that a field will be decorated both with an Angular decorator and a non-Angular
|
// unlikely that a field will be decorated both with an Angular decorator and a non-Angular
|
||||||
// decorator that's also been downleveled.
|
// decorator that's also been downleveled.
|
||||||
if (clazz.propDecorators !== undefined) {
|
if (clazz.propDecorators !== undefined &&
|
||||||
|
(!parentConstructor || parentConstructor.propDecorators !== clazz.propDecorators)) {
|
||||||
clazz.propDecorators = {...clazz.propDecorators, ...propDecorators};
|
clazz.propDecorators = {...clazz.propDecorators, ...propDecorators};
|
||||||
} else {
|
} else {
|
||||||
clazz.propDecorators = propDecorators;
|
clazz.propDecorators = propDecorators;
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Directive, HostListener} from '@angular/core';
|
import {Directive, HostListener, Input} from '@angular/core';
|
||||||
import {setClassMetadata} from '@angular/core/src/render3/metadata';
|
import {setClassMetadata} from '@angular/core/src/render3/metadata';
|
||||||
|
|
||||||
import {convertToR3QueryMetadata, directiveMetadata, extendsDirectlyFromObject} from '../../../src/render3/jit/directive';
|
import {convertToR3QueryMetadata, directiveMetadata, extendsDirectlyFromObject} from '../../../src/render3/jit/directive';
|
||||||
|
@ -130,5 +130,29 @@ describe('jit directive helper functions', () => {
|
||||||
expect(directiveMetadata(SuperDirective, {}).propMetadata.handleClick).toBeFalsy();
|
expect(directiveMetadata(SuperDirective, {}).propMetadata.handleClick).toBeFalsy();
|
||||||
expect(directiveMetadata(SubDirective, {}).propMetadata.handleClick).toBeFalsy();
|
expect(directiveMetadata(SubDirective, {}).propMetadata.handleClick).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not inherit propMetadata from super class when sub class has its own propMetadata',
|
||||||
|
() => {
|
||||||
|
class SuperDirective {}
|
||||||
|
setClassMetadata(SuperDirective, [{type: Directive}], null, {
|
||||||
|
someInput: [{type: Input}],
|
||||||
|
handleClick: [{type: HostListener, args: ['click', ['$event']]}]
|
||||||
|
});
|
||||||
|
|
||||||
|
class SubDirective extends SuperDirective {}
|
||||||
|
setClassMetadata(
|
||||||
|
SubDirective, [{type: Directive}], null, {someOtherInput: [{type: Input}]});
|
||||||
|
|
||||||
|
const superPropMetadata = directiveMetadata(SuperDirective, {}).propMetadata;
|
||||||
|
const subPropMetadata = directiveMetadata(SubDirective, {}).propMetadata;
|
||||||
|
|
||||||
|
expect(superPropMetadata.handleClick).toBeTruthy();
|
||||||
|
expect(superPropMetadata.someInput).toBeTruthy();
|
||||||
|
|
||||||
|
expect(subPropMetadata.handleClick).toBeFalsy();
|
||||||
|
expect(subPropMetadata.someInput).toBeFalsy();
|
||||||
|
expect(subPropMetadata.someOtherInput).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue