New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Finalizing output()
and outputFromObservable()
APIs
#54650
Conversation
c0f485e
to
894a4e0
Compare
894a4e0
to
11aaa8c
Compare
A great addition to the
-import { outputFromObservable } from '@angular/core/rxjs-interop';
+import { rxOutput } from '@angular/core/rxjs-interop';
@Directive({..})
export class MyDir {
nameChange$ = new Subject<string>();
- nameChange = outputFromObservable(this.nameChange$);
+ nameChange = rxOutput(this.nameChange$);
} EDIT: Another suggestion - |
tmpFs.mount(tmpFs.resolve('/node_modules/@angular'), angularFolder.get()); | ||
} | ||
|
||
// TODO: Consider removing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Burn it all 🔥
packages/core/src/event_emitter.ts
Outdated
// Attempt to retrieve a `DestroyRef` optionally. | ||
// For backwards compatibility reasons, this cannot be required. | ||
try { | ||
this.__internal_DestroyRef = inject(DestroyRef); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not inject(DestroyRef, {optional: true})
?
Or are you worried about EventEmitter
s created outside of DI contexts? I would recommend avoiding the try
/catch
and checking getInjectImplementation() || getCurrentInjector()
(see impl of assertInInjectionContext
.
Also, have we checked in g3 whether making EventEmitter
depend on injection context is that breaking?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's because EventEmitter
may be constructed outside of an injection context, yes.
Started a TGP to check how breaking it would be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Results: 40k failures, unfeasible. Most issues arise from QueryList
lazily creating an EventEmitter
(?!) lazily for .changes
. This can happen outside of injection contexts and affects a lot of targets
packages/compiler-cli/src/ngtsc/annotations/directive/src/initializer_functions.ts
Show resolved
Hide resolved
|
||
// Stop yielding more values when the directive/component is already destroyed. | ||
const subscription = | ||
observable.pipe(takeUntil(onDestroy)).subscribe(value => callbackFn(value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to do something with the error/complete channels here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I think we should just document that users are responsible for handling errors beforehand. If they aren't, the error will be simply handled by RxJS and will be reported unhandled to window.onerror
(see RxJS reportUnhandledError.ts
)
I really like the suggestion from @markostanimirovic to use the same naming pattern as the signal interop with And if the extension of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. As discussed in person I'm not sure about the completion semantics of the output as Observable and - more importantly - EventEmitter
involvement in it. I would lean towards dropping the completion logic but at the same time I don't feel like I understand all the consequences here.
Reviewed-for: public-api
Reviewed-for: fw-core
I agree with Also from an import perspective, you might hit |
a75d9c5
to
32fd5e4
Compare
This PR was merged into the repository by commit d4154f9. |
This commit replaces `fake_core` with the real `@angular/core` output. See previous commit for reasons. Overall, this commit: * Replaces references of `fake_core` * Fixes tests that were testing Angular compiler detection that _would_ already be flagged by type-checking of TS directly. We keep these tests for now, and add `@ts-ignore` to verify the Angular checks, in case type checking is disabled in user applications- but it's worth considering to remove these tests. Follow-up question/non-priority. * Adds `@ts-ignore` to the tests for `defer` 1P because the property is marked as `@internal` and now is (correctly) causing failures in the compiler test environment. * Fixes a couple of tests with typos, wrong properties etc that previously weren't detected! A good sign. PR Close #54650
This commit creates the proposed `OutputRef` interface along with `OutputEmitterRef`: - `OutputRef` is the consistent interface for all Angular outputs. - `OutputEmitterRef` is an extension for emitting values. Like `EventEmitter`. - subscriptions are cleaned up automatically upon directive/component destroy. - emitting is an error when destroyed - subscribing programmatically is an error when already destroyed. This commit will also implement the shared output ref runtime construct, that can be used by `output()`, `outputFromObservable()` and `model()`. We will manage subscriptions in a simple way, manually, without RxJS. PR Close #54650
This commit exposes the new `output()` API with numerous benefits: - Symmetrical API to `input()`, `model()` etc. - Fixed types for `EventEmitter.emit`— current `emit` method of `EventEmitter` is broken and accepts `undefined` via `emit(value?: T)` - Removal of RxJS specific concepts from outputs. error channels, completion channels etc. We now have a simple consistent interface. - Automatic clean-up of subscribers upon directive/component destory- when subscribed programmatically. ```ts @directive({..}) export class MyDir { nameChange = output<string>(); // OutputEmitterRef<string> onClick = output(); // OutputEmitterRef<void> } ``` Note: RxJS custom observable cases will be handled in future commits via explicit helpers from the interop. PR Close #54650
Introduces a second API in addition to the new `output()` function. The new function `outputFromObservable()` can be used to declare outputs using the new `OutputRef` API and `output()` API, while using a custom RxJS observable as data source. This is something that is currently possible in Angular and we would like to keep possible- even though we never intended to support custom observables aside from RxJS-based `EventEmitter`. The interop bridges the gap and allows you to continue using `Subject`, `ReplaySubject`, `BehaivorSubjct,` - or cold custom observables for outputs. You can still trigger logic only when the output is subscribed- unlike with imperative `emit`s of `EventEmitter` or the new `OutputEmitterRef`. A notable difference is that you need two class members where you previously could access the `Subject` directly. This is an intentional trade-off we've made to ensure that all new outputs implement the `OutputRef` interface and we are exposing a minimal API surface to consumers of components that currently access the output programmatically. PR Close #54650
This commit introduces an addition to `output()` and `outputFromObservable`()` —called `outputToObservable()`. The helper lives in the RxJS interop package and allows agnostic programmatic subscriptions to `OutputRef`s by converting the output to an observable with `.pipe` etc. The function is ideally used in all places where you subscribe to an output programmatically. Those outputs in the future, with the new APIs, may not be actual RxJS constructs, but abstract `OutputRef`'s that simply expose a `.subscribe` method. The helper allows you to agnostically convert outputs to RxJS observables that you can safely interact with. The observables are also completed automatically, if possible, when the owning directive/component is destroyed— Something that is not guaranteed right now. PR Close #54650
A model signal is technically an output, at runtime and conceptually. This commit re-uses the shared output ref logic and ensures the interfaces match. PR Close #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
Adds runtime acceptance tests for `output()` and `outputFromObservable()`. PR Close #54650
…ion context (#54650) Technically `model()` and `output()` already need to be defined in an injection context- because `OutputRef` requires this. To improve the error messaging, this commit asserts this as part of the top-level entry functions for `model()` and `output()`. Without this change, the error would mention the `_createOutputRef` internal function. PR Close #54650
Adds additional language-service tests for `outputFromObservable()`. Existing tests already verify the behavior for `output()`. PR Close #54650
The `inject` global augmentation from upgrade tests, leak into all source files for IDEs, making it easy to run into issues when actually trying to deal with `inject` from Angular core for DI. PR Close #54650
…54650) Currently the `makeProgram` utility from `ngtsc/testing` does not use the test host by default- optimizing for source file caching. Additionally, the host can be updated to attempt caching of the `.d.ts` files from `@angular/core`— whether that's fake core, or the real core- is irrelevant. We are never caching if these changes between tests, so correctness is guaranteed. This commit reduces the type check test times form 80s to just 11 seconds, faster than what it was before with `fake_core`. The ngtsc tests also run significantly faster. From 40s to 30s PR Close #54650
For model signals we introduced some sniffing on the return type of a `.subscribe` invocation- allowing for subscribe to _just_ return a callback directly to unsubscribe. This works in practice, but the positive `tCleanup` indices have more meaning, especially in the context of `DebugElement`. A positive index indicates a DOM event- so we need to revert this change. This now surfaced as we made `EventEmitter` return a function + the subscription via a proxy that ended up `typeof function` --> and broke some tests where debug element incorrectly invoked non-dom outputs as dom listeners. We don't need this change with current unsubscribe function concept. PR Close #54650
.required when? I love some vscode yelling I forgot something. |
The new In the end: a Greetings, Flo |
Why output() does not use signals like the new input()? |
It represents an event, i.e. an action at some point in time, rather than state, i.e. stored data. Signals correspond with state storage, not events. |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
See individual commits.
———
output()
This commit exposes the new
output()
API with numerous benefits:input()
,model()
etc.EventEmitter.emit
— currentemit
method ofEventEmitter
is broken and acceptsundefined
viaemit(value?: T)
completion channels etc. We now have a simple consistent
interface.
when subscribed programmatically.
Note: RxJS custom observable cases will be handled in future commits via
explicit helpers from the interop.
outputFromObservable()
outputToObservable