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

Support binding component outputs as callbacks #12630

Closed
jonrimmer opened this issue Oct 31, 2016 · 10 comments
Closed

Support binding component outputs as callbacks #12630

jonrimmer opened this issue Oct 31, 2016 · 10 comments
Labels
area: core Issues related to the framework runtime core: inputs / outputs 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 freq1: low
Milestone

Comments

@jonrimmer
Copy link
Contributor

I'm submitting a ... (check one with "x")

[ ] bug report
[x] feature request
[ ] support request

Current behavior

In Angular 1, I had a directive called async-action:

<button 
  async-action="$ctrl.doSomething()"
>Submit</button>

The directive would register a click listener on the host. When the host was clicked, the directive would call the event handler.

The event handler could optionally return a promise, and the directive would apply animations to the host element based on the promise lifecycle. The flow was roughly this:

toggleAnimation('active', true);

$q.when(this.onAsyncAction()).then(() => {
  playAnimation('success');
})
.catch(() => {
  playAnimation('error');
})
.finally(() => {
  toggleAnimation('active', false);
});

This pattern was an simple and expressive way for directives/components to declaratively delegate parts of their lifecycle to their consumers. Unfortunately, it does not appear to be supported in Angular 2. Event binding is far more limited and inflexible, being tied to the class EventEmitter, which requires an RxJS approach and precludes returning results from handlers.

Expected behavior

Angular 2 should support callback bindings to callbacks. E.g. if my component class looks like this...

@Output()
public action: () => Promise<void>;

...then Angular should assign the statement call from the template directly to the field.

What is the motivation / use case for changing the behavior?

Expressiveness, productivity and Angular 1 parity.

  • Angular version:
    2.1.1

  • Browser:
    All

  • Language:
    All

@vsavkin vsavkin added the area: core Issues related to the framework runtime label Oct 31, 2016
@jonrimmer
Copy link
Contributor Author

So, thinking about this more, the general problem is that there is no simple way to declaratively provide a callback/delegate to a child component in such a way that the callback can also be supplied with template variables.

Consider my async-button component being used inside a form:

<form #f="ngForm">
  ...
  <async-button (action)="submitForm(f)">
    Submit Form
  </async-button> 
</form>

As stated earlier, there is no easy way for async-button to get the result of the submitForm() event calls.

In something like React's JSX, this isn't a problem, as an arrow function could be passed as an input:

<AsyncButton action={ () => submit(form) } />

But this won't work in Angular 2, as arrow functions are not supported in template expressions.

Still, there are a few of ways to achieve this using the existing Angular 2 template syntax:

1 — Make action an @Input property and pass the callback and its parameters as an array:

<async-button [action]="[submitForm, f]">
@Input() public action: any[];

public onAction() {
  let result = this.action[0].apply(this.action.slice(1));

  ... // do something with result.
}

The downside of this approach it is non-obvious from looking at the template that this represents a call.

2 — Make the EventHandler synchronous, and make the $event parameter a function that captures the result of the actual handler call and makes its result available to the child component:

<async-button (action)="$event(submitForm(f))">
@Output() public action: EventHandler<any> = new EventHandler(false);

public onAction() {
  let result;
  this.action.emit(r => result = r);

  ... // do something with result.
}

The downside is that it feels a little weird and non-obvious to be using $event this way, and to require consumers of your component to do so. Especially if you're writing a general-purpose control for distribution to 3rd parties.

3 — Have a function in the parent controller that accepts the template variables as parameters and creates a callback function that can be bound to an @Input on the child component:

<async-button [action]="createSubmitCallback(f)">
public createSubmitCallback(form: NgForm) {
  return () => this.submitForm(form);
}

The drawback is that change-detection is going to keep triggering a re-evaluation of createSubmitCallback(f) and keep creating new functions each time.

Possible Improvements

1 — Allow arrow functions in @Input expressions:

<async-button [action]="() => submitForm(f)">
@Input() public action: () => ResultType;

This has a nice parity with React and modern JS.

2 — Add a new decorator type, called @Delegate or something similar, where the statement is bound directly to the property:

<async-button (action)="submitForm(f)">
@Delegate() public action: () => ResultType;

The drawback of this is that it isn't obvious from looking at the template whether something is bound as delegate or an event.

@tbosch
Copy link
Contributor

tbosch commented Nov 4, 2016

So you are missing:

Event binding is far more limited and inflexible, being tied to the class EventEmitter, which requires an RxJS approach and precludes returning results from handlers.

Let's put aside liking / not liking the RxJS approach, the missing feature is not being able to get the result of actions, right?

@tbosch tbosch added feature Issue that requests a new feature freq1: low labels Nov 4, 2016
@jonrimmer
Copy link
Contributor Author

@tbosch Yes, exactly.

@vikrambhatla
Copy link

I have a component which is something like this:
@output() onNext:EventEmitter = new EventEmitter();

I want to make a http call onNext and then once I get the response I would like to excute rest of the function.

onNextInternal(event) {
this.onNext.emit(event).then((res) => {
//go to next step
}, (error) {
//stay at current step
})
}

Can we achieve something similar or I need to wait for this bug to get fixed?

@mlc-mlapis
Copy link
Contributor

@vikrambhatla ... re-order of your code ... and finally call emit in subscribe or then part of your async operation and not on the beginning.

@vikrambhatla
Copy link

@mlc-mlapis
you meant
this.onNext.subscribe((res) => {
this.onNext.emit();
//go to next step
}, (error) {
//stay at current step
})

it won't work, as emit needs to be called before suscribe.
Do you think this will work?
this.onNext.subscribe((res) => {
//go to next step
}, (error) {
//stay at current step
})

this.onNext.emit();

@ngbot ngbot bot added this to the Backlog milestone Jan 23, 2018
@timruhle
Copy link

I solved this by creating an observable that is passed into the emit. For example

    const observable: Observable<any> = Observable.create((observer) => {
        this.onNext.emit(observer);
      });
      observable.subscribe((response) => {
        //go to next step
      }, (error) => {
        //stay at current step
      });

Hopefully this helps.

@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 24, 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 24, 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 Mar 4, 2022
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 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 freq1: low
Projects
None yet
Development

No branches or pull requests

8 participants