Skip to content
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

🚀 Handle Outputs more reactively #40872

Closed
igabesz opened this issue Feb 16, 2021 · 8 comments
Closed

🚀 Handle Outputs more reactively #40872

igabesz opened this issue Feb 16, 2021 · 8 comments
Labels
area: core Issues related to the framework runtime core: inputs / outputs cross-cutting: observables feature: insufficient votes Label to add when the not a sufficient number of votes or comments from unique authors feature: votes required Feature request which is currently still in the voting phase feature Issue that requests a new feature
Milestone

Comments

@igabesz
Copy link

igabesz commented Feb 16, 2021

🚀 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:

@josephperrott josephperrott added the area: core Issues related to the framework runtime label Feb 16, 2021
@ngbot ngbot bot added this to the needsTriage milestone Feb 16, 2021
@AndrewKushnir AndrewKushnir added the feature Issue that requests a new feature label Feb 16, 2021
@ngbot ngbot bot modified the milestones: needsTriage, Backlog Feb 16, 2021
@kbrilla
Copy link

kbrilla commented Feb 17, 2021

Hey,
this solution:
@ComponentStream() -
it is practicly @ViewChild('your-copomonent') component
and then You can simply go
component.output1.subscribe()... component.output999.subscribe()... .
But still, You need to explicitly know that the component caught by @ViewChild/@componentStream is the one You want. If there would be 2 or more then You need to add some id/s or # to reference them from ts code.
So it is actually more error-prone - Someone adds a second instance of the component You reference before and kaboom, now Your code is working on another instance.

Maybe extending @ViewChild/@ViewChildren/@ContentChild/@ContentChildren to be able to read the field of the component/directive instead of the whole component would be a solution.

So we would have
@ViewChild( 'your-copomonent', { read: YourComponent, readKey: (c)=>c.output }) output$: Observable<Boolean>;
but again this solution seems like overkill if there would be 2 or more outputs that You would like to use in code.

@igabesz
Copy link
Author

igabesz commented Feb 17, 2021

@criskrzysiu Your suggestion definitely works, however, it couples the component's logic (TS) to its presentation (HTML) which is usually to be avoided. It's also error-prone, just as you wrote some examples. Extending those decorators won't make it less coupled.

@igabesz
Copy link
Author

igabesz commented Feb 17, 2021

Another question: what happens in this case below? If the source is an EventEmitter (or an Observable) within an Angular component then we already have a stream. But what about regular JS events?

<button (click.next)="output$">Click me</button>

@igabesz
Copy link
Author

igabesz commented Feb 24, 2021

Another thing. Angular builds heavily on streams. Currently, both Angular-oriented things (HTTP, Forms, etc.) and downward data flow (parent -> children) can be done properly with streams. However, the upward direction is not that stream-friendly. If one wants proper stream-based event flow upwards, then they shall use NgRx or something similar.

NgRx is kinda complicated and it does not fit to every use-case. So in "vanilla Angular" we lack the convenient tools to create fully stream-oriented applications, despite the fact that Angular APIs are heavily stream-oriented. I find this somewhat discrepant.

@angular-robot angular-robot bot added the feature: votes required Feature request which is currently still in the voting phase label Jun 4, 2021
@angular-robot
Copy link
Contributor

angular-robot bot commented Jun 4, 2021

Just a heads up that we kicked off a community voting process for your feature request. There are 20 days until the voting process ends.

Find more details about Angular's feature request process in our documentation.

@angular-robot
Copy link
Contributor

angular-robot bot commented Jun 25, 2021

Thank you for submitting your feature request! Looks like during the polling process it didn't collect a sufficient number of votes to move to the next stage.

We want to keep Angular rich and ergonomic and at the same time be mindful about its scope and learning journey. If you think your request could live outside Angular's scope, we'd encourage you to collaborate with the community on publishing it as an open source package.

You can find more details about the feature request process in our documentation.

@angular-robot angular-robot bot added the feature: insufficient votes Label to add when the not a sufficient number of votes or comments from unique authors label Jun 25, 2021
@alxhub
Copy link
Member

alxhub commented Jul 16, 2021

I'm going to close this as a conceptual twin of #5689.

As @mgechev summarizes there, we are interested in exploring ways to increase the ergonomics of using Angular in a reactive context, without introducing additional required RxJS to @angular/core. This request would fall under such experimentation.

@alxhub alxhub closed this as completed Jul 16, 2021
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Aug 16, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: core Issues related to the framework runtime core: inputs / outputs cross-cutting: observables feature: insufficient votes Label to add when the not a sufficient number of votes or comments from unique authors feature: votes required Feature request which is currently still in the voting phase feature Issue that requests a new feature
Projects
None yet
Development

No branches or pull requests

6 participants