From 199ff4fe7f2cd4b561703e8520c2d6ccc1e2afb7 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 19 May 2023 09:07:19 +0200 Subject: [PATCH] fix(core): host directives incorrectly validating aliased bindings (#50364) Fixes that the host directives feature was incorrectly throwing the conflicting alias error when an aliased binding was being exposed under the same alias. Fixes #48951. PR Close #50364 --- .../test/ngtsc/host_directives_spec.ts | 53 ++++++++++++++++ .../features/host_directives_feature.ts | 2 +- .../test/acceptance/host_directives_spec.ts | 60 +++++++++++++++++++ 3 files changed, 114 insertions(+), 1 deletion(-) diff --git a/packages/compiler-cli/test/ngtsc/host_directives_spec.ts b/packages/compiler-cli/test/ngtsc/host_directives_spec.ts index 7c063356d09..b8dbe627245 100644 --- a/packages/compiler-cli/test/ngtsc/host_directives_spec.ts +++ b/packages/compiler-cli/test/ngtsc/host_directives_spec.ts @@ -944,6 +944,59 @@ runInEachFileSystem(() => { expect(messages).toEqual([]); }); + + it('should not produce a diagnostic when exposing an aliased binding', () => { + env.write('test.ts', ` + import {Directive, EventEmitter} from '@angular/core'; + + @Directive({ + outputs: ['opened: triggerOpened'], + selector: '[trigger]', + standalone: true, + }) + export class Trigger { + opened = new EventEmitter(); + } + + @Directive({ + standalone: true, + selector: '[host]', + hostDirectives: [{directive: Trigger, outputs: ['triggerOpened']}] + }) + export class Host {} + `); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(0); + }); + + it('should not produce a diagnostic when exposing an inherited aliased binding', () => { + env.write('test.ts', ` + import {Directive, EventEmitter} from '@angular/core'; + + @Directive({standalone: true}) + export abstract class Base { + opened = new EventEmitter(); + } + + @Directive({ + outputs: ['opened: triggerOpened'], + selector: '[trigger]', + standalone: true, + }) + export class Trigger extends Base {} + + @Directive({ + standalone: true, + selector: '[host]', + hostDirectives: [{directive: Trigger, outputs: ['triggerOpened: hostOpened']}] + }) + export class Host {} + `); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(0); + }); }); }); }); diff --git a/packages/core/src/render3/features/host_directives_feature.ts b/packages/core/src/render3/features/host_directives_feature.ts index 6e3e2afde86..82121488786 100644 --- a/packages/core/src/render3/features/host_directives_feature.ts +++ b/packages/core/src/render3/features/host_directives_feature.ts @@ -204,7 +204,7 @@ function validateMappings( const remappedPublicName = hostDirectiveBindings[publicName]; - if (bindings.hasOwnProperty(remappedPublicName) && + if (bindings.hasOwnProperty(remappedPublicName) && remappedPublicName !== publicName && bindings[remappedPublicName] !== publicName) { throw new RuntimeError( RuntimeErrorCode.HOST_DIRECTIVE_CONFLICTING_ALIAS, diff --git a/packages/core/test/acceptance/host_directives_spec.ts b/packages/core/test/acceptance/host_directives_spec.ts index 85b6a951814..03ae81467e4 100644 --- a/packages/core/test/acceptance/host_directives_spec.ts +++ b/packages/core/test/acceptance/host_directives_spec.ts @@ -3219,5 +3219,65 @@ describe('host directives', () => { fixture.detectChanges(); }).not.toThrow(); }); + + it('should not throw when exposing an aliased binding', () => { + @Directive({ + outputs: ['opened: triggerOpened'], + selector: '[trigger]', + standalone: true, + }) + class Trigger { + opened = new EventEmitter(); + } + + @Directive({ + standalone: true, + selector: '[host]', + hostDirectives: [{directive: Trigger, outputs: ['triggerOpened']}] + }) + class Host { + } + + @Component({template: '
', standalone: true, imports: [Host]}) + class App { + } + + expect(() => { + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + }).not.toThrow(); + }); + + it('should not throw when exposing an inherited aliased binding', () => { + @Directive({standalone: true}) + abstract class Base { + opened = new EventEmitter(); + } + + @Directive({ + outputs: ['opened: triggerOpened'], + selector: '[trigger]', + standalone: true, + }) + class Trigger extends Base { + } + + @Directive({ + standalone: true, + selector: '[host]', + hostDirectives: [{directive: Trigger, outputs: ['triggerOpened: hostOpened']}] + }) + class Host { + } + + @Component({template: '
', standalone: true, imports: [Host]}) + class App { + } + + expect(() => { + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + }).not.toThrow(); + }); }); });