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.
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');
}
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.