19

In an Angular 2+ component, how do I pass in a callback function that takes parameters? My initial assumption was something like

<app-example [onComplete]="doThing('someParam')"></app-example>

And sometimes I won't need any parameters, like this:

<app-example [onComplete]="doSomeThingElse()"></app-example>

And then in the component I have

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
})
export class ExampleComponent {
  @Input() public onComplete: () => void;

  //run `this.onComplete()` somewhere as needed
}

But, what ends up happening is that the doThing('someParam') or doSomeThingElse() is immediately called without any interaction.

How am I supposed to pass callback functions in to a component to be called later?


EDIT:

The actual problem I am trying to solve here is to be able to run any passed in function at a later time. This is for a confirmation component that will ask the user "are you sure you want to continue?" and then if they press the "Yes I'm sure" button, the passed in function will run.

10
  • @yurzui isn't this reinventing the wheel with events and potentially dangerous with memory leaks? (not your solution but intention itself) Commented Dec 28, 2017 at 18:50
  • 1
    @deezg bind method will return new function every time angular calls change detection cycle. So i agree we shouldn't reinvent the wheel) Commented Dec 28, 2017 at 18:53
  • you should read about @Output. It's what you are looking for. Commented Dec 28, 2017 at 19:07
  • @toskv I know of @Output to pass values back to a parent controller, but I'm not sure how I would use that here in this context. Feel free to add an answer to this question with the details, and I'll mark it as correct. Telling me to "read about" something is not very helpful to me right now. Commented Dec 28, 2017 at 19:17
  • @ChrisBarr you might be better off actually describing the problem you want to solve. Problem not being 'passing a callback function as variable' but real problem you want your component(s) to solve. Usual solution for what this looks like is to send parameters to child as @Input params and then have event bound with @Output and event will be raised with whatever arguments you want. When event is raised, your parent component will call whatever function it wants (your callback in case above) with whatever parameters it got through event. Commented Dec 28, 2017 at 19:21

4 Answers 4

26

Here's an example of the @Output syntax @toskv was looking for, Angular pass callback function to child component as @Input

So for your example,

<app-example 
  (onComplete)="doThing()" 
  [completedParam]="'someParam'"></app-example>
@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
})
export class ExampleComponent {
  @Output() public onComplete: EventEmitter<any> = new EventEmitter();
  @Input() completedParam;

  runOnComplete(): void {
    this.onComplete.emit(this.completedParam);
  }
}

Does not feel as good as [onComplete]="doThing.bind(this, 'someParam')".

Sign up to request clarification or add additional context in comments.

3 Comments

I was able to remove the @Input() completedParam; and just call this.onComplete.emit() and it worked with anything I passed in or didn't pass in. thanks!
Interesting, so (onComplete)="doThing('someParam')"? I guess it's ok because param is a fixed string at compile time?
Yep, that worked, and I'm glad because it would be confusing to have a parent controller that had the function I needed to run, but then have to pass it parameterless and pass params in on some other binding. This is exactly what I was hoping would work! I actually did try this originally, but I ran into another issue and assumed it was related so I backtracked and posted this question. Oh well!
4

Template:

<app-example [someParams]="someParamsObject"
             (complete)="onComplete($event)" >
</app-example>

Component:

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
})
export class ExampleComponent {
  @Input()
  someParams: YourParamsType;

  @Output()
  complete:EventEmitter<any> = new EventEmitter<any>();

  //run `this.complete(this.someParams)` somewhere as needed and 
  //pack in some params if you need
}

In your calling, parent component you need a function named onComplete that receives parameter of type any in this case (that comes from @Output being defined as EventEmitter<any>). If you need, you can also have event parameters of any particular type you like EventEmitter<YourParticularType>.

Comments

4

The said solution will work only if you have a method that needs to invoke without taking action on the component itself. However, in my case, I need to execute an observable method inside of the app-example component and wait for the response to do some action inside of that component.

If anyone has the same issue. Here is the solution for it.

  1. create an interface.

    export interface Delegate<T> {
      (...args: any[]): T;
    }
    
  2. On your angular component, create a @Input variable

    @Component({
        selector: 'app-example',
        templateUrl: './example.component.html',
    })
    export class AppExampleComponent {
      @Input() executeWhen: Delegate<Observable<any>>;
    
      runOnComplete(): void {
        this.executeWhen().subscribe(() => // do some action);
      }
    }
    

1 Comment

creation of Interface is the solution to utilize @Input and the callback function can be called later, instead of setting it to @Output Parameter.
1

You can have a private method in your component:

private doThingFactory(param) {
  return () => this.doThing(param);
}

and then use it like that:

<app-example [onComplete]="doThingFactory('someParam')"></app-example>

4 Comments

I thought that might work, but I'd like to avoid this if possible. It seems like Angular should just have a way to pass a function and invoke it later
Actually, it is not a best practice. Angular encourages you to use Input and Output to solve your problems, and not use callback functions if possible. You should send someParam as input and receive the result as output.
I think the @Output is what he's looking for. Maybe add an example of that too?
i think it is better to use public method. as the method would be involved in html outside class. It is sometimes a checking for this which may cause compile error although there are not public / private method in javascript actaully.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.