<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 "> Bitovi Blog - UX and UI design, JavaScript and Front-end development
Loading

Angular |

Angular Callback Functions for Communication Between Components

How to use callback functions to communicate between Angular components using only the @Input parameter instead of the traditional @Output event emitter.

Igor Ciric

Igor Ciric

Twitter Reddit

Can two Angular components communicate without the @Output event emitter using only the @Input parameter? Yes, they can, and we will go through the perfect use case of this uncommon approach.

How does it work?

The parent component will pass an object containing all callback functions to the child component, as in the example below:

@Component(...)
export class App {
  appData = 'Bitovi';
  config = {
    field1: 'MyStringValue',
    field2: false,
    field3: 999,

    onChange: function (data) { // callback function 
      console.log('🟡 Do something:', data, this.appData);
      // ❌ you can't use "this" context (it will be undefined)
    },
    onSave: () => { // callback arrow function
      console.log('💾 Save data', this.appData);
      // ✅ you can use this context
    },
  };
}

onChange and onSave are our callback functions, in this case, they will replace @Output events.

The arrow function will not lose this context, like in the example onSave function.

In the child component, we will pass the config object as an @Input parameter, and then we can use our callback functions onChange and onSave.

@Component(...)
export class AppPresenter implements OnInit {
  @Input() config: App['config']; 
  myField = new FormControl<string>('');

  ngOnInit() {
    this.myField.valueChanges.subscribe({
      next: (value) => {
        this.config.onChange(value); // 🔵 onChange callback function 
      },
    });
  }

  save() {
    this.config.onSave(); // 🔵 onSave callback arrow function
  }
}

If we type the word “test” and call the save() function, this will be the output in the console:

🟡 Do something: t undefined
🟡 Do something: te undefined
🟡 Do something: tes undefined
🟡 Do something: test undefined

💾 Save data Bitovi

As we can see in the example, the child component from the <input /> form field has passed the values to the parent component using the callback function onChange and triggered a save action using the onSave callback arrow function.

Check this example code to see how it works in practice.

When to use callback functions with @Input?

Using callback functions as a way of communication between components is an uncommon pattern, but there is nothing wrong with using it, especially if we are dealing with dynamically rendered components; in that case, it can be helpful! Callback functions in @Input match dynamically rendered components perfectly, along with using the Formly library.

With Formly, we can use FormlyFieldConfig data input to pass the callback functions as part of the props object.

Check the example of using Formly and this pattern.

It’s good to know

@Input type

If we don’t want to lose the type of your object as @Input and avoid creating an unnecessary interface, we can pass the reference of the type of your @Input like in the example below, where the App is a reference to our parent component:

@Input() config: App['config'] // 🔵 type reference

and then we will also have the autocomplete available in our code editor.

Angular Callback Functions for Communication Between Components

constructor() and using a callback function

If we need to use the callback function in our child component before the component is initialized, then we need to pass the data using dependency injection.

In our module or, in this case, in the parent standalone component, we would need first to pass the values inside providers:

@Component({
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule, AppPresenter, ReactiveFormsModule],
  providers: [ // 🔵 Dependency injection
    {
      provide: Data,
      useValue: data,
    },
  ],
  template: `
    <h1 style="font-size:18px">Angular ...</h1>
    <app-presenter [config]="config"></app-presenter>
  `,
})

Now, inside the child component, we can access the data from the constructor.

constructor(private data: Data) {
  this.data.config.onChange('constructor() test');
}

Check the example of using callback functions for communication between components with dependency injection.

Conclusion

Using callback functions for communication between components can be very helpful in some scenarios. The examples in this post show you the basic working flow concept, how to use it with other libraries, and the advanced usage with dependency injections. Besides that, we remind ourselves about the advantage of using arrow functions and how not to lose the type for our passed data to the @Input.

What do you think?

We would love to hear your thoughts about this approach! Join our Community Discord and share your feedback with us on the #Angular channel. Don't hesitate to schedule a free consultation call if you need any assistance. Our team is always here to support you.

Join our Discord