Skip to content

🚀 Handle Outputs more reactively #40872

@igabesz

Description

@igabesz

🚀 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 an EventEmitter (or some other Observable, because we love to live dangerously).
  • Parent Template and TS: handle the output in onOutput (imperative)
  • Parent TS: manually set up output$ and fire it in onOutput
  • Parent TS: clean up output$ in onNgDestroy

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 a Subject / EventEmitter. (When??)
  • The output$ is auto-completed during ngOnDestroy.
  • The implementation of the stream can remain hidden. (Very useful while Subjects 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.
  • 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 ☹️

Workarounds for LVL 2:

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions