fix(elements): support output()-shaped outputs (#57535)

Previously Elements was assuming that every output was an RxJS `Subject` and
supports `.pipe()`. This is not true for `output()`-based outputs which
have `.subscribe()` but not `.pipe()`. This commit fixes such outputs by
using a `new Observable` instead of `map` to forward outputs.

PR Close #57535
This commit is contained in:
Alex Rickabaugh
2024-08-26 12:54:52 -07:00
committed by Andrew Kushnir
parent 3896f86865
commit fe5c4e086a
2 changed files with 21 additions and 3 deletions
@@ -18,9 +18,10 @@ import {
ɵChangeDetectionScheduler as ChangeDetectionScheduler,
ɵNotificationSource as NotificationSource,
ɵViewRef as ViewRef,
OutputRef,
} from '@angular/core';
import {merge, Observable, ReplaySubject} from 'rxjs';
import {map, switchMap} from 'rxjs/operators';
import {switchMap} from 'rxjs/operators';
import {
NgElementStrategy,
@@ -219,8 +220,11 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
protected initializeOutputs(componentRef: ComponentRef<any>): void {
const eventEmitters: Observable<NgElementStrategyEvent>[] = this.componentFactory.outputs.map(
({propName, templateName}) => {
const emitter: EventEmitter<any> = componentRef.instance[propName];
return emitter.pipe(map((value) => ({name: templateName, value})));
const emitter: EventEmitter<any> | OutputRef<any> = componentRef.instance[propName];
return new Observable((observer) => {
const sub = emitter.subscribe((value) => observer.next({name: templateName, value}));
return () => sub.unsubscribe();
});
},
);
@@ -16,6 +16,7 @@ import {
Input,
NgZone,
Output,
OutputEmitterRef,
SimpleChange,
SimpleChanges,
createComponent,
@@ -113,6 +114,18 @@ describe('ComponentFactoryNgElementStrategy', () => {
]);
});
it('should listen to output() emitters', () => {
const events: NgElementStrategyEvent[] = [];
strategy.events.subscribe((e) => events.push(e));
componentRef.instance.output3.emit('output-a');
componentRef.instance.output3.emit('output-b');
expect(events).toEqual([
{name: 'templateOutput3', value: 'output-a'},
{name: 'templateOutput3', value: 'output-b'},
]);
});
it('should initialize the component with initial values', () => {
expect(strategy.getInputValue('fooFoo')).toBe('fooFoo-1');
expect(componentRef.instance.fooFoo).toBe('fooFoo-1');
@@ -369,6 +382,7 @@ export class CdTrackerDir {
export class TestComponent {
@Output('templateOutput1') output1 = new Subject();
@Output('templateOutput2') output2 = new Subject();
@Output('templateOutput3') output3 = new OutputEmitterRef();
@Input() fooFoo: unknown;
@Input({alias: 'my-bar-bar'}) barBar: unknown;