fix(ivy): output should not be subscribe twice when 2 listeners (#30144)
PR Close #30144
This commit is contained in:
parent
f3ce8eeb83
commit
6c86ae710a
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue