diff --git a/packages/core/src/metadata/directives.ts b/packages/core/src/metadata/directives.ts index 8e556613c6..e03c91ee46 100644 --- a/packages/core/src/metadata/directives.ts +++ b/packages/core/src/metadata/directives.ts @@ -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; }; /** diff --git a/packages/core/src/reflection/reflection_capabilities.ts b/packages/core/src/reflection/reflection_capabilities.ts index c95112c140..fa47d0e074 100644 --- a/packages/core/src/reflection/reflection_capabilities.ts +++ b/packages/core/src/reflection/reflection_capabilities.ts @@ -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; } diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts index d04debf859..8a9b96f1e0 100644 --- a/packages/core/src/render3/jit/directive.ts +++ b/packages/core/src/render3/jit/directive.ts @@ -63,7 +63,8 @@ export function compileComponent(type: Type, 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): boolean { */ function directiveMetadata(type: Type, metadata: Directive): R3DirectiveMetadataFacade { // Reflect inputs and outputs. - const propMetadata = getReflect().propMetadata(type); + const propMetadata = getReflect().ownPropMetadata(type); return { name: type.name, diff --git a/packages/core/test/acceptance/integration_spec.ts b/packages/core/test/acceptance/integration_spec.ts index 1ee6334991..e5d05b75b3 100644 --- a/packages/core/test/acceptance/integration_spec.ts +++ b/packages/core/test/acceptance/integration_spec.ts @@ -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(); }); -}); \ No newline at end of file + + 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: ''}) + 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: '
'}) + class SuperComp { + @ViewChildren(SomeDir) dirs !: QueryList; + } + + @Component({selector: 'button[custom-button]', template: '
'}) + class SubComp extends SuperComp { + } + + @Component({template: ''}) + 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); + }); + +});