327

AngularJS has the & parameters where you could pass a callback to a directive (e.g AngularJS way of callbacks. Is it possible to pass a callback as an @Input for an Angular Component (something like below)? If not what would be the closest thing to what AngularJS does?

@Component({
    selector: 'suggestion-menu',
    providers: [SuggestService],
    template: `
    <div (mousedown)="suggestionWasClicked(suggestion)">
    </div>`,
    changeDetection: ChangeDetectionStrategy.Default
})
export class SuggestionMenuComponent {
    @Input() callback: Function;

    suggestionWasClicked(clickedEntry: SomeModel): void {
        this.callback(clickedEntry, this.query);
    }
}


<suggestion-menu callback="insertSuggestion">
</suggestion-menu>
Ian Steffy
  • 1,234
  • 3
  • 18
  • 45
Michail Michailidis
  • 11,792
  • 6
  • 63
  • 106
  • 9
    for future readers ```@Input``` way suggested made my code spagetti and not easy to maintain.. ```@Output```s are a much more natural way of doing what I want. As a result I changed the accepted answer – Michail Michailidis Nov 20 '16 at 08:04
  • @IanS question is about how something is done in Angular similar to AngularJS? why is title misleading? – Michail Michailidis Jul 15 '19 at 12:33
  • Angular is very different from AngularJS. Angular 2+ is just Angular. – Ian Steffy Jul 16 '19 at 14:31
  • @IanS You will need to do things differently but the things you need to do are about the same regardless of how they are done in frameworks.. I still don't see your point. passing a callback to a child component can be done in both AngularJS and Angular. Since it is not obvious how it is done in Angular compared to the explicit way of AngularJS, people coming from AngularJS found the question helpful as it seems, disproving your point about misleading title. You can suggest a better title if there is one though – Michail Michailidis Jul 16 '19 at 19:51
  • Java and JavaScript both have For Loops but a question about Java should be about Java and a question about JS should be about JS. When people look up Java and they find a question that is actually about JavaScript, despite both being about For Loops, it is misleading. You did not disprove my point, but thank you for the feedback – Ian Steffy Jul 17 '19 at 11:50
  • question is about Angular and answer is about Angular for people coming from AngularJS to implement the same mechanism. Please suggest a better title as this seems like trolling otherwise – Michail Michailidis Jul 17 '19 at 14:40
  • 1
    Fixed your title ;) – Ian Steffy Jul 19 '19 at 09:50
  • 1
    @IanS Thanks! now the question is about angularJs too though - with the tag you added though. – Michail Michailidis Jul 19 '19 at 10:32
  • No problem. It is just specifying that you look for the Angular answer most similar to the way it's done in AngularJS over maybe the newer Angular (newest v) solution – Ian Steffy Jul 22 '19 at 07:12

13 Answers13

405

I think that is a bad solution. If you want to pass a Function into component with @Input(), @Output() decorator is what you are looking for.

export class SuggestionMenuComponent {
    @Output() onSuggest: EventEmitter<any> = new EventEmitter();

    suggestionWasClicked(clickedEntry: SomeModel): void {
        this.onSuggest.emit([clickedEntry, this.query]);
    }
}

<suggestion-menu (onSuggest)="insertSuggestion($event[0],$event[1])">
</suggestion-menu>
Michail Michailidis
  • 11,792
  • 6
  • 63
  • 106
Serginho
  • 7,291
  • 2
  • 27
  • 52
  • Shouldn't there be `new EventEmitter()` and brackets around `onSuggest` attribute? Anyway that should be a correct answer. – luke-at-work Oct 17 '16 at 13:35
  • 58
    To be precise you're not passing the function but rather hooking up an listener event listener to the output. Helpful for understanding why it works. – Jens Nov 06 '16 at 20:24
  • @Sergihno This is a great answer and I didn't expect it to work! Could you please elaborate on what how $event is converted to SomeModel? Also I think with emit you can send back only one thing which for me is enough.. so there is a typo?Everything else I had was known from the caller and wasn't needed to be returned from callee as well :) – Michail Michailidis Nov 20 '16 at 08:00
  • I was going to ignore this warning because in my case I need the function to return an Observable and the component to subscribe to it, so it still seemed like the correct use case for @Input, since you cannot capture the return value of the function using an emitter. However, I found that what I was doing in the callback wrecked havoc on change detection, so I went back and heeded the warning; instead passing a callback to the handler aka: `(save)="saveAsync($event, doneCallback)"` – parliament Dec 09 '16 at 16:02
  • 19
    This is a great method, but I was left with a lot of questions after reading this answer. I was hoping it would be more in-depth or have a link provided describing `@Output` and `EventEmitter`. So, here is the [**Angular documentation for @Output**](https://angular.io/docs/ts/latest/cookbook/component-communication.html#parent-listens-for-child-event) for those interested. – WebWanderer Jan 19 '17 at 18:33
  • 12
    This is fine for one-way binding. You can hookup to the child's event. But you can't pass a callback function to the child and let it analyze the callback's return value. The answer below allows that. – rook Feb 20 '17 at 07:44
  • @Serginho any comments about this answer http://stackoverflow.com/a/42131227/986160? – Michail Michailidis Mar 09 '17 at 22:43
  • 9
    I would expect to have more explanation on why to prefer one way vs another instead of having "I think that is a bad solution.". – Fidan Hakaj Jun 16 '17 at 13:42
  • I can see how this is cleaner than the `[myCallback]="theCallback.bind(this)"` - because what would you do if theCallback needed some arguments? You would have to pass them as binded properties to the child component and your code would become cumbersome while you just needed to execute some action onClick in the first place. – nyxz Oct 30 '17 at 16:00
  • 1
    @nyxz Get a glimple [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Partially_applied_functions) if you want to pass params to a binded function – Serginho Oct 31 '17 at 08:23
  • 2
    @MichailMichailidis About your link.(And ignoring the `this`pointer in angular template) I think that's usefull if you need a dynamic function in the ChildComponent. For example, if you have a button that opens another confirmation button before do any stuff and you need a hook between both buttons like beforeConfirm. You want to encapsulate the button and the confirm button in the same component, in this case, passing a callback with `@Input` is the correct solution. But the right way to do 99% of component is using `@Output`. – Serginho Jan 15 '18 at 22:33
  • This solution is very good! I just want to say about output naming. Below naming starting without 'on' is better; `@Output() suggest: EventEmitter = new EventEmitter();` Rather than; `@Output() onSuggest: EventEmitter = new EventEmitter();` Otherwise, you will get this error; `'TSLint: In the class "YourComponent", the output property "onSuggest" should not be prefixed with on (no-output-on-prefix)'` – yusuf Feb 21 '18 at 18:28
  • 9
    Probably good for 80% of cases, but not when a child component wants visualization conditional on whether a callback exists. – John Freeman Apr 11 '18 at 01:43
  • 2
    And what happens if the child want to pass it on to grandchild component and so on? more @Outputs? Do we have to define a function on each layer to pass as output to the next one? I think for those cases passing the actual callback is better. Or an other solution exists? By the way, I personally find parameter-passing via bind not cumbersome code at all. I thought it is standard js practice. Am I the only one? – Alkis Mavridis Oct 22 '18 at 09:39
  • *NOTE* you should avoid using the on prefix for proper linting `TSLint: In the class "SuggestionMenuComponent", the output property "onSuggest" should not be prefixed with on(no-output-on-prefix)` – Ricardo Saracino Jun 24 '20 at 13:33
153

UPDATE

This answer was submitted when Angular 2 was still in alpha and many of the features were unavailable / undocumented. While the below will still work, this method is now entirely outdated. I strongly recommend the accepted answer over the below.

Original Answer

Yes in fact it is, however you will want to make sure that it is scoped correctly. For this I've used a property to ensure that this means what I want it to.

@Component({
  ...
  template: '<child [myCallback]="theBoundCallback"></child>',
  directives: [ChildComponent]
})
export class ParentComponent{
  public theBoundCallback: Function;

  public ngOnInit(){
    this.theBoundCallback = this.theCallback.bind(this);
  }

  public theCallback(){
    ...
  }
}

@Component({...})
export class ChildComponent{
  //This will be bound to the ParentComponent.theCallback
  @Input()
  public myCallback: Function; 
  ...
}
SnareChops
  • 13,175
  • 9
  • 69
  • 91
  • 1
    This worked! Thanks! I wish the documentation had that somewhere :) – Michail Michailidis Feb 11 '16 at 01:26
  • I assume there is no way this would work with static function from parent as callback right? That is under the assumption that template "sees" things on this..as it seems. – Michail Michailidis Feb 11 '16 at 01:30
  • 1
    You could use a static method if you would like, but then you would not have access to any of the instance members of the component. So probably not your use case. But yes, you would need to pass that as well from `Parent -> Child` – SnareChops Feb 11 '16 at 02:49
  • comments on the simplified answer? Thanks – Michail Michailidis Mar 23 '16 at 18:46
  • 4
    Great answer! I typically don't rename the function when binding though. in `ngOnInit` I would just use: `this.theCallback = this.theCallback.bind(this)` and then you can pass along `theCallback` instead of `theBoundCallback`. – Zack May 11 '16 at 21:26
  • How to bind function that is returning observable ?? Any suggestions ?? – microchip78 Jul 29 '16 at 03:38
  • @microchip78 You can bind any function with any return type in the same fashion. The `.bind` only ensures that `this` means what it should when it gets called. The arguments and return types are unaffected. – SnareChops Jul 31 '16 at 14:37
  • This work but you always need to bind your callback to the parent instance in ngOnInit. I'm looking for a simpler solution that can do context switching automatically. I think that we can use typescript decorator to do that. Like put `@Callback` before `theCallback` function. – truongnguyen1912 Sep 30 '16 at 07:06
  • The new answer below yields cleaner code with simpler signature for my directives and smaller callback function signatures.. it seems more natural so I change my accepted answer – Michail Michailidis Nov 20 '16 at 07:58
  • 1
    @MichailMichailidis Yes, I agree with your solution and have updated my answer with a note to lead people to the better way. Thanks for keeping an eye on this one. – SnareChops Nov 21 '16 at 05:09
  • I didn't like you write in the answer: "I strongly recommend the accepted answer over the below." People decide man! – Serginho Jan 19 '17 at 19:19
  • @Serginho Yes, which is why I left this answer here instead of deleting it. People decide, and frameworks change. The answer is still valid, but is far from ideal. I feel that I also have a moral obligation to alert people that there is a cleaner solution that aligns better to the angular conventions that will make understanding the code easier. At the end of the day, it's still just a recommendation. You as the reader can choose to take the advice, or not. I want to also prevent the scenario where a reader only reads the highest upvoted answer and takes that as "the way" to implement. – SnareChops Jan 23 '17 at 00:28
  • 9
    @Output and EventEmitter are fine for one way binding. You can hookup to the child's event but you can't pass a callback function to the child and let it analyze the callback's return value. This answer allows that. – rook Feb 20 '17 at 07:46
  • @SnareChops any comments on this answer http://stackoverflow.com/a/42131227/986160? – Michail Michailidis Mar 09 '17 at 22:44
  • 1
    @MichailMichailidis It *may* work, but does `this` mean the instance of the component in the angular template syntax? I'm not sure, haven't seen it in the documentation anywhere, though I haven't gone looking for it either. Unless it's explicitly stated in the angular documentation, I *personally* wouldn't trust that it will remain unbroken. Just a caution. – SnareChops Mar 10 '17 at 13:26
  • @SnareChops good point! In the template this is implicit so that might be broken in the future.. – Michail Michailidis Mar 11 '17 at 14:37
  • 2
    instead of `public theCallback(){..}` you could declare an arrow function `theCallback = () => {...};` and remove this instruction `this.theBoundCallback = this.theCallback.bind(this);` – Olivier Boissé May 18 '18 at 20:55
  • Any example with also passing parameters to callback ? – Jiro Matchonson Aug 29 '18 at 23:13
  • This solution works for me quite well, gracias. I have one question though, anyone used Ionic's modals with NavParams? I opted in for @SnareChops approach because I couldn't get around NavParam errors. – Dev Yego Oct 31 '19 at 13:26
70

In some cases, you might need business logic to be performed by a parent component. In the example below we have a child component that renders table row depending on the logic provided by the parent component:

@Component({
  ...
  template: '<table-component [getRowColor]="getColor"></table-component>',
  directives: [TableComponent]
})
export class ParentComponent {

 // Pay attention on the way this function is declared. Using fat arrow (=>) declaration 
 // we can 'fixate' the context of `getColor` function
 // so that it is bound to ParentComponent as if .bind(this) was used.
 getColor = (row: Row) => {
    return this.fancyColorService.getUserFavoriteColor(row);
 }

}

@Component({...})
export class TableComponent{
  // This will be bound to the ParentComponent.getColor. 
  // I found this way of declaration a bit safer and convenient than just raw Function declaration
  @Input('getRowColor') getRowColor: (row: Row) => Color;

  renderRow(){
    ....
    // Notice that `getRowColor` function holds parent's context because of a fat arrow function used in the parent
    const color = this.getRowColor(row);
    renderRow(row, color);
  }
}

So, I wanted to demonstrate 2 things here:

  1. Fat arrow (=>) functions instead of .bind(this) to hold the right context;
  2. Typesafe declaration of a callback function in the child component.
Danylo Zatorsky
  • 5,856
  • 2
  • 25
  • 49
35

An alternative to the answer SnareChops gave.

You can use .bind(this) in your template to have the same effect. It may not be as clean but it saves a couple of lines. I'm currently on angular 2.4.0

@Component({
  ...
  template: '<child [myCallback]="theCallback.bind(this)"></child>',
  directives: [ChildComponent]
})
export class ParentComponent {

  public theCallback(){
    ...
  }
}

@Component({...})
export class ChildComponent{
  //This will be bound to the ParentComponent.theCallback
  @Input()
  public myCallback: Function; 
  ...
}
Max Fahl
  • 818
  • 9
  • 19
  • 2
    as others have commented bind(this) in the template is nowhere documented so it might become deprecated/unsupported in the future. Plus again `@Input` is causing the code to become spaghetti and using `@Output` results in a more natural/untangled process – Michail Michailidis Apr 01 '17 at 16:25
  • 3
    When you place bind() in the template, Angular re-evaluates this expression at every change detection. The other solution - doing the bind outside of the template - is less concise, but it doesn't have this problem. – Chris Oct 31 '18 at 01:53
  • question: when doing .bind(this), you are binding method theCallBack with the child or parent? I think it's with the child. But the thing is, when the bind is being called, it's always the child calling it, so this bind doesn't seem necessary if I am correct. – ChrisZ Jan 30 '19 at 19:33
  • It binds with the parent component. The reason this is done is that when theCallBack() is being called, it will probably want to do something inside itself, and if "this" is not the parent component it will be out of context and therefore cannot reach his own methods and variables anymore. – Max Fahl Feb 01 '19 at 08:09
16

An alternative to the answer Max Fahl gave.

You can define callback function as an arrow function in the parent component so that you won't need to bind that.

@Component({
  ...
  // unlike this, template: '<child [myCallback]="theCallback.bind(this)"></child>',
  template: '<child [myCallback]="theCallback"></child>',
  directives: [ChildComponent]
})
export class ParentComponent {

   // unlike this, public theCallback(){
   public theCallback = () => {
    ...
  }
}

@Component({...})
export class ChildComponent{
  //This will be bound to the ParentComponent.theCallback
  @Input()
  public myCallback: Function; 
  ...
}
Hunter Tran
  • 13,257
  • 2
  • 14
  • 23
jeadonara
  • 1,176
  • 10
  • 10
  • Yea, I thought this would work in my code, but I had to do the binding in the actual html, meaning that even having a function expression to a bind didn't work, neither an arrow, nor an explicit bind. Just as a note to anyone, if this isn't working. – matttm Aug 12 '21 at 10:42
8

As an example, I am using a login modal window, where the modal window is the parent, the login form is the child and the login button calls back to the modal parent's close function.

The parent modal contains the function to close the modal. This parent passes the close function to the login child component.

import { Component} from '@angular/core';
import { LoginFormComponent } from './login-form.component'

@Component({
  selector: 'my-modal',
  template: `<modal #modal>
      <login-form (onClose)="onClose($event)" ></login-form>
    </modal>`
})
export class ParentModalComponent {
  modal: {...};

  onClose() {
    this.modal.close();
  }
}

After the child login component submits the login form, it closes the parent modal using the parent's callback function

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

@Component({
  selector: 'login-form',
  template: `<form (ngSubmit)="onSubmit()" #loginForm="ngForm">
      <button type="submit">Submit</button>
    </form>`
})
export class ChildLoginComponent {
  @Output() onClose = new EventEmitter();
  submitted = false;

  onSubmit() {
    this.onClose.emit();
    this.submitted = true;
  }
}
5

Passing method with argument, using .bind inside template

@Component({
  ...
  template: '<child [action]="foo.bind(this, 'someArgument')"></child>',
  ...
})
export class ParentComponent {
  public foo(someParameter: string){
    ...
  }
}

@Component({...})
export class ChildComponent{

  @Input()
  public action: Function; 

  ...
}
Shogg
  • 796
  • 2
  • 7
  • 14
  • Isn't your answer essentially the same as this: https://stackoverflow.com/a/42131227/986160 ? – Michail Michailidis Jun 15 '19 at 16:52
  • answering this comment https://stackoverflow.com/questions/35328652/angular-pass-callback-function-to-child-component-as-input/56609039?noredirect=1#comment91124797_35329009 – Shogg Jun 15 '19 at 21:14
5

Following works for me in Angular 13 (as of March 2022).

P.S.- This is more or less similar to what others answered. Adding this answer just to let people know it works in Angular 13.

Define the Function as Flat Arrow and not regular function in parent component.

callBackFn= (args: string): void => {
  // callback code here
  // This will work (Flat Arrow)
}
// callbackFn(args: string): void {
//   //This type of definition will not work.
// }

Pass callback function as attribute to child component

<app-child [callBack]=”callBackFn”></app-child>

Receive the callback function as Input in the child component. The definition should match as that defined in the parent.

@Input() callBack: (args: string) => void;

Then call this function in the child component. You can also call this is child component template.

this.callBack('Test');

OR

<button (click)="callBack('Test')"></button>

But not sure whether this approach is good or not. I see a similar approach in ReactJS and it works great but still not sure how it works in angular and what will be its impact.

Any comments on this approach would be appreciated.

Gopal Mishra
  • 1,575
  • 1
  • 14
  • 17
  • back then `@input` seemed natural but then I saw that `@Output` was better and made code way less coupled.. so I even switched accepted answer.. I don't think the semantics changed from back then and I still support `@Output` instead of Input .. – Michail Michailidis Dec 05 '22 at 13:48
  • For those who still want to use inputs, like in my case (not sure if I could make it work with output), `` needs to be replaced with ``, otherwise the method will be called with `this` inside it pointing to the child, not the parent. In my case, I have a service in the parent which was not available in the child and I was getting errors without the `bind`. – AsGoodAsItGets Apr 27 '23 at 16:48
4

Another alternative.

The OP asked a way to use a callback. In this case he was referring specifically to a function that process an event (in his example: a click event), which shall be treated as the accepted answer from @serginho suggests: with @Output and EventEmitter.

However, there is a difference between a callback and an event: With a callback your child component can retrieve some feedback or information from the parent, but an event only can inform that something happened without expect any feedback.

There are use cases where a feedback is necessary, ex. get a color, or a list of elements that the component needs to handle. You can use bound functions as some answers have suggested, or you can use interfaces (that's always my preference).

Example

Let's suppose you have a generic component that operates over a list of elements {id, name} that you want to use with all your database tables that have these fields. This component should:

  • retrieve a range of elements (page) and show them in a list
  • allow remove an element
  • inform that an element was clicked, so the parent can take some action(s).
  • allow retrieve the next page of elements.

Child Component

Using normal binding we would need 1 @Input() and 3 @Output() parameters (but without any feedback from the parent). Ex. <list-ctrl [items]="list" (itemClicked)="click($event)" (itemRemoved)="removeItem($event)" (loadNextPage)="load($event)" ...>, but creating an interface we will need only one @Input():

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

export interface IdName{
  id: number;
  name: string;
}

export interface IListComponentCallback<T extends IdName> {
    getList(page: number, limit: number): Promise< T[] >;
    removeItem(item: T): Promise<boolean>;
    click(item: T): void;
}

@Component({
    selector: 'list-ctrl',
    template: `
      <button class="item" (click)="loadMore()">Load page {{page+1}}</button>
      <div class="item" *ngFor="let item of list">
          <button (click)="onDel(item)">DEL</button>
          <div (click)="onClick(item)">
            Id: {{item.id}}, Name: "{{item.name}}"
          </div>
      </div>
    `,
    styles: [`
      .item{ margin: -1px .25rem 0; border: 1px solid #888; padding: .5rem; width: 100%; cursor:pointer; }
      .item > button{ float: right; }
      button.item{margin:.25rem;}
    `]
})
export class ListComponent implements OnInit {
    @Input() callback: IListComponentCallback<IdName>; // <-- CALLBACK
    list: IdName[];
    page = -1; 
    limit = 10;

    async ngOnInit() {
      this.loadMore();
    }
    onClick(item: IdName) {
      this.callback.click(item);   
    }
    async onDel(item: IdName){ 
        if(await this.callback.removeItem(item)) {
          const i = this.list.findIndex(i=>i.id == item.id);
          this.list.splice(i, 1);
        }
    }
    async loadMore(){
      this.page++;
      this.list = await this.callback.getList(this.page, this.limit); 
    }
}

Parent Component

Now we can use the list component in the parent.

import { Component } from "@angular/core";
import { SuggestionService } from "./suggestion.service";
import { IdName, IListComponentCallback } from "./list.component";

type Suggestion = IdName;

@Component({
  selector: "my-app",
  template: `
    <list-ctrl class="left" [callback]="this"></list-ctrl>
    <div class="right" *ngIf="msg">{{ msg }}<br/><pre>{{item|json}}</pre></div>
  `,
  styles:[`
    .left{ width: 50%; }
    .left,.right{ color: blue; display: inline-block; vertical-align: top}
    .right{max-width:50%;overflow-x:scroll;padding-left:1rem}
  `]
})
export class ParentComponent implements IListComponentCallback<Suggestion> {
  msg: string;
  item: Suggestion;

  constructor(private suggApi: SuggestionService) {}

  getList(page: number, limit: number): Promise<Suggestion[]> {
    return this.suggApi.getSuggestions(page, limit);
  }
  removeItem(item: Suggestion): Promise<boolean> {
    return this.suggApi.removeSuggestion(item.id)
      .then(() => {
        this.showMessage('removed', item);
        return true;
      })
      .catch(() => false);
  }
  click(item: Suggestion): void {
    this.showMessage('clicked', item);
  }
  private showMessage(msg: string, item: Suggestion) {
    this.item = item;
    this.msg = 'last ' + msg;
  }
}

Note that the <list-ctrl> receives this (parent component) as the callback object. One additional advantage is that it's not required to send the parent instance, it can be a service or any object that implements the interface if your use case allows it.

The complete example is on this stackblitz.

Wilfredo Pomier
  • 1,091
  • 9
  • 12
  • Passing the parent's entire context is fine in your case, I think, only because it contains no more than what the child needs. If it did, and you only needed one function or something, I'd suggest just passing a bound func. – matttm Aug 12 '21 at 10:38
0

Use Observable pattern. You can put Observable value (not Subject) into Input parameter and manage it from parent component. You do not need callback function.

See example: https://stackoverflow.com/a/49662611/4604351

0

As for me, besides .bind(this), I had to put a pair of parentheses behind the the method's name for the method to be executed.

In the Parent component:

In the .ts file:

    this.pillTabs = [
        { tabName: 'Subscribers', tabMethod: this.showSubscribers.bind(this) },
        { tabName: 'Exemplars', tabMethod: this.showExemplars.bind(this) }
    ];

In the .html file:

    <pill-tabs [pillTabs]="pillTabs"></pill-tabs>

In the Child component:

In the .ts file:

    @Input() pillTabs: PillTab[];

In the .html file:

    <div *ngFor="let pillTab of pillTabs; let i = index">
        <input type="radio" id="{{'radio-' + i}}" name="tabs" [checked]="pillTab.checked"
            (click)="pillTab.tabMethod()" />
        <label class="tab" for="{{'radio-' + i}}">{{pillTab.tabName}}</label>
    </div>

The code was NOT working when I did NOT have the pair of parentheses behind the method:

      (click)="pillTab.tabMethod"

And then when I put the pair of parentheses there, the code started to work.

      (click)="pillTab.tabMethod()"

I hope that someone finds it helpful.

William Hou
  • 1,251
  • 14
  • 17
0

There is a way to pass a function to a child component using @Input and it's quite simple.

On your parent component:

myCallback = (args: any): void => {
    //your code here
}

On your child component:

@Input() callback: (args: any) => void = () => {};

Pass the function to your child component binding:

<app-child-component [callback]=”myCallback”></app-child-component>

Although it works like this, perhaps as mentioned above, the best solution could be to use @Output instead.

  • Output seemed a better design choice in big projects back then compared to Input.. I guess this is still holding true.. here is a related answer for this debate https://stackoverflow.com/a/40069142/986160 – Michail Michailidis Jul 12 '23 at 10:34
  • @MichailMichailidis You are right, it seems a better choice to use Output. In my opinion your answer is definitely the recommended one. Perhaps for those who are beginning, is not an easy way to understand the concept of Output. And if they are working on a smaller or personal project, it will be fine to use Input. I just wanted to leave here this other way of approaching the problem as the question was specific asking how to pass a function to a child component using Input. There might be some use cases where can be useful. – dev.rcastrucci Jul 17 '23 at 14:12
-3

The current answer can be simplified to...

@Component({
  ...
  template: '<child [myCallback]="theCallback"></child>',
  directives: [ChildComponent]
})
export class ParentComponent{
  public theCallback(){
    ...
  }
}

@Component({...})
export class ChildComponent{
  //This will be bound to the ParentComponent.theCallback
  @Input()
  public myCallback: Function; 
  ...
}
SnareChops
  • 13,175
  • 9
  • 69
  • 91
Blue
  • 728
  • 8
  • 9
  • so there is no need to bind explicitly? – Michail Michailidis Mar 23 '16 at 18:46
  • 3
    Without the `.bind(this)` then the `this` inside of the callback will be `window` which may not matter depending on your use case. However if you have `this` in the callback at all, then `.bind(this)` is necessary. If you don't then this simplified version is the way to go. – SnareChops Mar 23 '16 at 23:40
  • 3
    I recommend always bind the callback with the component, because eventually you will use `this` inside the callback function. It's just error prone. – Alexandre Junges Aug 05 '16 at 12:50
  • That's an example of an Angular 2 antipattern. – Serginho Oct 14 '16 at 17:45
  • It does not have to be an anti-pattern. There are cases where you want exactly this. It's not that uncommon to want to tell the component HOW to do something that's not about the view. It makes sense and I don't see why this answer is getting so much hate. – Lazar Ljubenović Jan 17 '18 at 10:00