fix(ivy): host listeners being inherited twice (#28902)

Fixes inherited host event listeners being registered twice.

This PR resolves FW-1071.

PR Close #28902
This commit is contained in:
Kristiyan Kostadinov 2019-02-21 22:45:37 +01:00 committed by Ben Lesh
parent 9dac04ff50
commit 43181ea568
4 changed files with 70 additions and 5 deletions

View File

@ -728,7 +728,7 @@ const updateBaseDefFromIOProp = (getProp: (baseDef: {inputs?: any, outputs?: any
const baseDef = constructor.ngBaseDef;
const defProp = getProp(baseDef);
defProp[name] = args[0];
defProp[name] = args[0] || name;
};
/**

View File

@ -210,6 +210,13 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
return propMetadata;
}
ownPropMetadata(typeOrFunc: any): {[key: string]: any[]} {
if (!isType(typeOrFunc)) {
return {};
}
return this._ownPropMetadata(typeOrFunc, Object) || {};
}
hasLifecycleHook(type: any, lcProperty: string): boolean {
return type instanceof Type && lcProperty in type.prototype;
}

View File

@ -63,7 +63,8 @@ export function compileComponent(type: Type<any>, metadata: Component): void {
preserveWhitespaces: metadata.preserveWhitespaces || false,
styles: metadata.styles || EMPTY_ARRAY,
animations: metadata.animations,
viewQueries: extractQueriesMetadata(type, getReflect().propMetadata(type), isViewQuery),
viewQueries:
extractQueriesMetadata(type, getReflect().ownPropMetadata(type), isViewQuery),
directives: [],
changeDetection: metadata.changeDetection,
pipes: new Map(),
@ -138,7 +139,7 @@ export function extendsDirectlyFromObject(type: Type<any>): boolean {
*/
function directiveMetadata(type: Type<any>, metadata: Directive): R3DirectiveMetadataFacade {
// Reflect inputs and outputs.
const propMetadata = getReflect().propMetadata(type);
const propMetadata = getReflect().ownPropMetadata(type);
return {
name: type.name,

View File

@ -5,8 +5,9 @@
* 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 {Component, HostBinding} from '@angular/core';
import {Component, Directive, HostBinding, HostListener, QueryList, ViewChildren} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {onlyInIvy} from '@angular/private/testing';
@ -40,4 +41,60 @@ describe('acceptance integration tests', () => {
expect(element.classList.contains('foo')).toBeFalsy();
expect(element.classList.contains('bar')).toBeTruthy();
});
});
it('should only call inherited host listeners once', () => {
let clicks = 0;
@Component({template: ''})
class ButtonSuperClass {
@HostListener('click')
clicked() { clicks++; }
}
@Component({selector: 'button[custom-button]', template: ''})
class ButtonSubClass extends ButtonSuperClass {
}
@Component({template: '<button custom-button></button>'})
class MyApp {
}
TestBed.configureTestingModule({declarations: [MyApp, ButtonSuperClass, ButtonSubClass]});
const fixture = TestBed.createComponent(MyApp);
const button = fixture.debugElement.query(By.directive(ButtonSubClass));
fixture.detectChanges();
button.nativeElement.click();
fixture.detectChanges();
expect(clicks).toBe(1);
});
it('should support inherited view queries', () => {
@Directive({selector: '[someDir]'})
class SomeDir {
}
@Component({template: '<div someDir></div>'})
class SuperComp {
@ViewChildren(SomeDir) dirs !: QueryList<SomeDir>;
}
@Component({selector: 'button[custom-button]', template: '<div someDir></div>'})
class SubComp extends SuperComp {
}
@Component({template: '<button custom-button></button>'})
class MyApp {
}
TestBed.configureTestingModule({declarations: [MyApp, SuperComp, SubComp, SomeDir]});
const fixture = TestBed.createComponent(MyApp);
const subInstance = fixture.debugElement.query(By.directive(SubComp)).componentInstance;
fixture.detectChanges();
expect(subInstance.dirs.length).toBe(1);
expect(subInstance.dirs.first).toBeAnInstanceOf(SomeDir);
});
});