-
Notifications
You must be signed in to change notification settings - Fork 26.5k
Description
🚀 Handle Outputs more reactively
Relevant Package
This feature request is for @angular/core.
Description
Currently, dealing with an @Output
of a component is quite imperative from the parent component's side. I'd like to have a more reactive, stream-oriented approach.
The issue is originally described here, I summarize it below.
We have a parent component (template and TS below). A child component emits an output
event, which we want to process as a stream. Now we can do this the following way:
<child-component (output)="onOutput($event)"></child-component>
export class ParentComponent {
output$ = new Subject<any>();
ngOnInit() {
this.output$
.pipe(preprocessFilterAndMapPipe)
.subscribe(output => console.log("got output", output));
}
ngOnDestroy() { this.output$.complete(); }
onOutput(output) { this.output$.next(output); }
}
What happens here:
- ChildComponent: there's the
output
as anEventEmitter
(or some other Observable, because we love to live dangerously). - Parent Template and TS: handle the
output
inonOutput
(imperative) - Parent TS: manually set up
output$
and fire it inonOutput
- Parent TS: clean up
output$
inonNgDestroy
Practically we walk a few extra lines just to make a stream out of another stream. This could be done much better.
Describe the solution you'd like
Eliminate the overhead as much as possible: stream-callback-stream to be simplified.
I'm not quite sure of the exact implementation though. But I have a wild guess. Please be gentle on me. 😄
LVL 1
<child-component (output.next)="output$"></child-component>
export class ParentComponent {
output$ = new Subject<T>();
ngOnInit() {
this.output$
.pipe(preprocessFilterAndMapPipe)
.subscribe(output => console.log("got output", output));
}
ngOnDestroy() { this.output$.complete(); }
}
Handle events from components via (eventname.next)="subject"
, where the subject
must be an Rx Subject
/ BehaviorSubject
. This will pipe the next stream to the target subject. We may handle error
and complete
streams similarly, perhaps all 3 with the (eventname.subscribe)
syntax.
LVL 2
I think there could be a way to eliminate the manual creation and completion of the output$
stream. Something like this:
export class ParentComponent {
@ComponentStream() // My best bad idea. Please think of something better.
output$: Observable<T>;
ngOnInit() {
this.output$
.pipe(preprocessFilterAndMapPipe)
.subscribe(output => console.log("got output", output));
}
Here we have 3 extra things with @ComponentStream
(provided by Angular):
- The
output$
is auto-instantiated as aSubject
/EventEmitter
. (When??) - The
output$
is auto-completed duringngOnDestroy
. - The implementation of the stream can remain hidden. (Very useful while
Subject
s are not accepted officially.)
And ✨ a metric ton of boilerplate disappeared. Or at least a few lines and quite a few bug-spawners. Furthermore, it would be an awesome new reactive tool in our toolbox.
Describe alternatives you've considered
There are a few workarounds at the original StackOverflow issue for LVL 1:
- Handle the Output the way I described here
☹️ - Fire the Subject right in the template code
<child-component (output)="output$.next($event)">
☹️ - This eliminates the callback function in the template but still... in my opinion calling the
next
shouldn't be the responsibility of the template.
- This eliminates the callback function in the template but still... in my opinion calling the
- Pass the Parent's subject to the component as an Input 🤮
- Use a separate service (overkill)
- EDIT: Use
@ViewChild
or its friends to access the child component directly (from @criskrzysiu)- Tight coupling between the TS and the template code
☹️
- Tight coupling between the TS and the template code
Workarounds for LVL 2:
- Manually create and complete the stream, as in the example
- https://github.com/ngneat/until-destroy which is not the same but related.