Skip to content

Commit 866271a

Browse files
devversionpkozlowski-opensource
authored andcommitted
refactor(core): EventEmitter implements OutputRef. (#54650)
An `EventEmitter` is a construct owned by Angular that should be used for outputs as of right now. As we are introducing the new `OutputRef` interface for the new output function APIs, we also think `EventEmitter` should implement `OutputRef`— ensuring all "known" outputs follow the same contract. This commit ensures `EventEmitter` implements an `OutputRef` Note: An output ref captures the destroy ref from the current injection context for clean-up purposes. This is also done for `EventEmitter` in a backwards compatible way: - not requiring an injection context. EventEmitter may be used elsewhere. - not cleaning up subscriptions/completing the emitter when the directive/component is destroyed. This would be a change in behavior. Note 2: The dependency on `DestroyRef` causes it to be retained in all bundling examples because ironically `NgZone` uses `EventEmitter`- not for outputs. The code is pretty minimal though, so that should be acceptable. `EventEmitter` will now always retain `NgZone. This increases the payload size slightly around 800b for AIO. Note that the other increases were coming from previous changes. This commit just pushed it over the threshold. PR Close #54650
1 parent 30355f6 commit 866271a

File tree

18 files changed

+311
-120
lines changed

18 files changed

+311
-120
lines changed

goldens/circular-deps/packages.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,6 @@
6363
"packages/core/src/render3/interfaces/container.ts",
6464
"packages/core/src/render3/interfaces/view.ts"
6565
],
66-
[
67-
"packages/core/src/render3/interfaces/query.ts",
68-
"packages/core/src/render3/interfaces/view.ts"
69-
],
7066
[
7167
"packages/forms/src/directives.ts",
7268
"packages/forms/src/directives/ng_control_status.ts",

goldens/public-api/core/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ export class ErrorHandler {
647647
}
648648

649649
// @public
650-
export interface EventEmitter<T> extends Subject<T> {
650+
export interface EventEmitter<T> extends Subject<T>, OutputRef<T> {
651651
new (isAsync?: boolean): EventEmitter<T>;
652652
emit(value?: T): void;
653653
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): Subscription;

goldens/size-tracking/aio-payloads.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"aio-local": {
1313
"uncompressed": {
1414
"runtime": 4252,
15-
"main": 526463,
15+
"main": 531485,
1616
"polyfills": 33862,
1717
"styles": 60209,
1818
"light-theme": 31691,

packages/core/rxjs-interop/test/output_to_observable_spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,5 +89,33 @@ describe('outputToObservable()', () => {
8989
expect(subscription.closed).toBe(true);
9090
expect(subject.observed).toBe(false);
9191
});
92+
93+
it('may not complete the observable with an improperly ' +
94+
'configured `OutputRef` without a destroy ref as source',
95+
() => {
96+
const outputRef = new EventEmitter<number>();
97+
const observable = outputToObservable(outputRef);
98+
99+
let completed = false;
100+
const subscription = observable.subscribe({
101+
complete: () => completed = true,
102+
});
103+
104+
outputRef.next(1);
105+
outputRef.next(2);
106+
107+
expect(completed).toBe(false);
108+
expect(subscription.closed).toBe(false);
109+
expect(outputRef.observed).toBe(true);
110+
111+
// destroy `EnvironmentInjector`.
112+
TestBed.resetTestingModule();
113+
114+
expect(completed)
115+
.withContext('Should not be completed as there is no known time when to destroy')
116+
.toBe(false);
117+
expect(subscription.closed).toBe(false);
118+
expect(outputRef.observed).toBe(true);
119+
});
92120
});
93121
});

packages/core/src/event_emitter.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
import {setActiveConsumer} from '@angular/core/primitives/signals';
1010
import {PartialObserver, Subject, Subscription} from 'rxjs';
1111

12+
import {OutputRef} from './authoring/output/output_ref';
13+
import {inject} from './di/injector_compatibility';
14+
import {DestroyRef} from './linker/destroy_ref';
15+
1216
/**
1317
* Use in components with the `@Output` directive to emit custom events
1418
* synchronously or asynchronously, and register handlers for those events
@@ -60,7 +64,7 @@ import {PartialObserver, Subject, Subscription} from 'rxjs';
6064
* @see [Observables in Angular](guide/observables-in-angular)
6165
* @publicApi
6266
*/
63-
export interface EventEmitter<T> extends Subject<T> {
67+
export interface EventEmitter<T> extends Subject<T>, OutputRef<T> {
6468
/**
6569
* @internal
6670
*/
@@ -101,12 +105,21 @@ export interface EventEmitter<T> extends Subject<T> {
101105
subscribe(observerOrNext?: any, error?: any, complete?: any): Subscription;
102106
}
103107

104-
class EventEmitter_ extends Subject<any> {
108+
class EventEmitter_ extends Subject<any> implements OutputRef<any> {
105109
__isAsync: boolean; // tslint:disable-line
110+
destroyRef: DestroyRef|undefined;
106111

107112
constructor(isAsync: boolean = false) {
108113
super();
109114
this.__isAsync = isAsync;
115+
116+
// Attempt to retrieve a `DestroyRef` optionally.
117+
// For backwards compatibility reasons, this cannot be required.
118+
try {
119+
this.destroyRef = inject(DestroyRef);
120+
} catch {
121+
this.destroyRef = undefined;
122+
}
110123
}
111124

112125
emit(value?: any) {

packages/core/src/render3/interfaces/view.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,25 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import type {ChangeDetectionScheduler} from '../../change_detection/scheduling/zoneless_scheduling';
10+
import {TDeferBlockDetails} from '../../defer/interfaces';
911
import type {Injector} from '../../di/injector';
1012
import {ProviderToken} from '../../di/provider_token';
1113
import {DehydratedView} from '../../hydration/interfaces';
1214
import {SchemaMetadata} from '../../metadata/schema';
1315
import {Sanitizer} from '../../sanitization/sanitizer';
16+
import type {AfterRenderEventManager} from '../after_render_hooks';
1417
import type {ReactiveLViewConsumer} from '../reactive_lview_consumer';
1518
import type {EffectScheduler} from '../reactivity/effect';
16-
import type {AfterRenderEventManager} from '../after_render_hooks';
1719

1820
import {LContainer} from './container';
1921
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList, ViewQueriesFunction} from './definition';
2022
import {I18nUpdateOpCodes, TI18n, TIcu} from './i18n';
2123
import {TConstants, TNode} from './node';
22-
import {LQueries, TQueries} from './query';
24+
import type {LQueries, TQueries} from './query';
2325
import {Renderer, RendererFactory} from './renderer';
2426
import {RElement} from './renderer_dom';
2527
import {TStylingKey, TStylingRange} from './styling';
26-
import {TDeferBlockDetails} from '../../defer/interfaces';
27-
import type {ChangeDetectionScheduler} from '../../change_detection/scheduling/zoneless_scheduling';
2828

2929

3030

packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@
167167
{
168168
"name": "DefaultDomRenderer2"
169169
},
170+
{
171+
"name": "DestroyRef"
172+
},
170173
{
171174
"name": "DomAdapter"
172175
},
@@ -371,6 +374,9 @@
371374
{
372375
"name": "NodeInjector"
373376
},
377+
{
378+
"name": "NodeInjectorDestroyRef"
379+
},
374380
{
375381
"name": "NodeInjectorFactory"
376382
},
@@ -1025,6 +1031,9 @@
10251031
{
10261032
"name": "injectArgs"
10271033
},
1034+
{
1035+
"name": "injectDestroyRef"
1036+
},
10281037
{
10291038
"name": "injectElementRef"
10301039
},
@@ -1367,6 +1376,9 @@
13671376
{
13681377
"name": "shouldSearchParent"
13691378
},
1379+
{
1380+
"name": "storeLViewOnDestroy"
1381+
},
13701382
{
13711383
"name": "stringify"
13721384
},

packages/core/test/bundling/animations/bundle.golden_symbols.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@
191191
{
192192
"name": "DefaultDomRenderer2"
193193
},
194+
{
195+
"name": "DestroyRef"
196+
},
194197
{
195198
"name": "DomAdapter"
196199
},
@@ -407,6 +410,9 @@
407410
{
408411
"name": "NodeInjector"
409412
},
413+
{
414+
"name": "NodeInjectorDestroyRef"
415+
},
410416
{
411417
"name": "NodeInjectorFactory"
412418
},
@@ -1094,6 +1100,9 @@
10941100
{
10951101
"name": "injectArgs"
10961102
},
1103+
{
1104+
"name": "injectDestroyRef"
1105+
},
10971106
{
10981107
"name": "injectElementRef"
10991108
},
@@ -1439,6 +1448,9 @@
14391448
{
14401449
"name": "shouldSearchParent"
14411450
},
1451+
{
1452+
"name": "storeLViewOnDestroy"
1453+
},
14421454
{
14431455
"name": "stringify"
14441456
},

0 commit comments

Comments
 (0)