fix(ivy): output should not be subscribe twice when 2 listeners (#30144)

PR Close #30144
This commit is contained in:
Marc Laval 2019-04-26 12:05:27 +02:00 committed by Andrew Kushnir
parent f3ce8eeb83
commit 6c86ae710a
2 changed files with 40 additions and 2 deletions

View File

@ -110,6 +110,8 @@ function listenerInternal(
ngDevMode && assertNodeOfPossibleTypes( ngDevMode && assertNodeOfPossibleTypes(
tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer); tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer);
let processOutputs = true;
// add native event listener - applicable to elements only // add native event listener - applicable to elements only
if (tNode.type === TNodeType.Element) { if (tNode.type === TNodeType.Element) {
const native = getNativeByTNode(tNode, lView) as RElement; const native = getNativeByTNode(tNode, lView) as RElement;
@ -149,6 +151,7 @@ function listenerInternal(
// Attach a new listener at the head of the coalesced listeners list. // Attach a new listener at the head of the coalesced listeners list.
(<any>listenerFn).__ngNextListenerFn__ = (<any>existingListener).__ngNextListenerFn__; (<any>listenerFn).__ngNextListenerFn__ = (<any>existingListener).__ngNextListenerFn__;
(<any>existingListener).__ngNextListenerFn__ = listenerFn; (<any>existingListener).__ngNextListenerFn__ = listenerFn;
processOutputs = false;
} else { } else {
// The first argument of `listen` function in Procedural Renderer is: // The first argument of `listen` function in Procedural Renderer is:
// - either a target name (as a string) in case of global target (window, document, body) // - either a target name (as a string) in case of global target (window, document, body)
@ -180,7 +183,7 @@ function listenerInternal(
const outputs = tNode.outputs; const outputs = tNode.outputs;
let props: PropertyAliasValue|undefined; let props: PropertyAliasValue|undefined;
if (outputs && (props = outputs[eventName])) { if (processOutputs && outputs && (props = outputs[eventName])) {
const propsLength = props.length; const propsLength = props.length;
if (propsLength) { if (propsLength) {
const lCleanup = getCleanup(lView); const lCleanup = getCleanup(lView);

View File

@ -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 {Component, Directive, ErrorHandler, HostListener, QueryList, ViewChildren} from '@angular/core'; import {Component, Directive, ErrorHandler, EventEmitter, HostListener, Input, Output, QueryList, ViewChild, ViewChildren} from '@angular/core';
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser'; import {By} from '@angular/platform-browser';
import {onlyInIvy} from '@angular/private/testing'; import {onlyInIvy} from '@angular/private/testing';
@ -203,5 +203,40 @@ describe('event listeners', () => {
expect(returnsFalseDir.event.preventDefault).toHaveBeenCalled(); expect(returnsFalseDir.event.preventDefault).toHaveBeenCalled();
}); });
it('should not subscribe twice to the output when there are 2 coalesced listeners', () => {
@Directive({selector: '[foo]'})
class FooDirective {
@Input('foo') model: any;
@Output('fooChange') update = new EventEmitter();
updateValue(value: any) { this.update.emit(value); }
}
@Component({
selector: 'test-component',
template: `<div [(foo)]="someValue" (fooChange)="fooChange($event)"></div>`
})
class TestComponent {
count = 0;
someValue = -1;
@ViewChild(FooDirective) fooDirective: FooDirective|null = null;
fooChange() { this.count++; }
triggerUpdate(value: any) { this.fooDirective !.updateValue(value); }
}
TestBed.configureTestingModule({declarations: [TestComponent, FooDirective]});
const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
const componentInstance = fixture.componentInstance;
componentInstance.triggerUpdate(42);
fixture.detectChanges();
expect(componentInstance.count).toEqual(1);
expect(componentInstance.someValue).toEqual(42);
});
}); });
}); });