Angular components are the building blocks of applications, and component communication via one-way data flow is the key to writing clean Angular applications.
In this tutorial you’ll learn how to pass data into an Angular component using the @Input
decorator and custom property binding. Angular is built upon property binding, so learning this is essential.
No components are created equal, there are parent and child components…
A parent component is likely responsible for retrieving data and passing it to the correct components for rendering and user interaction.
✨ This introduces the concept of a smart, or “container” component. A container component is smart because it has awareness outside of the Angular component tree - for example it retrieves data from a Service.
Once that data is available it’s time to delegate the rendering, and behavior, down to a dumb component (or presentational component).
Smart components are aware of things outside of the Angular component tree, whereas dumb components are not.
Let’s begin with a simple smart container component that receives a list of menu items and renders them:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'menu',
template: `
<div *ngIf="items$ | async as items">
<div *ngFor="let item of items">
{{ item.name }}
{{ item.price }}
</div>
</div>
`
})
export class MenuComponent implements OnInit {
items$: Observable<Item[]>;
constructor(private menuService: MenuService) {}
ngOnInit() {
this.items$ = this.menuService.getAll();
}
}
Assume that menuService.getAll()
returns a list of menu items to render as an Observable
, hence the async
pipe.
But, our template
is not using any components, which is fine to begin with but we need to break down our logic to keep our application clean and separated. One component should perform a specific role in your application - so let’s turn our component into this:
@Component({
selector: 'menu',
template: `
<div *ngIf="items$ | async as items">
<menu-item
*ngFor="let item of items"
[item]="item">
</menu-item>
</div>
`
})
export class MenuComponent {...}
Which means we need a <menu-item>
component and pass each item
down. For this, you can see [item]
which is a property binding. A property binding creates a communication path on our custom element for Angular to understand. Without it we’d see an error like “cannot bind to property item”.
So, let’s create it and set it up to receive each item via an @Input
- our first presentational component.
For this, we import the Input
decorator from @angular/core
and declare it inside the component against a property name we wish to bind to:
import { Component, Input } from '@angular/core';
@Component({
selector: 'menu-item',
template: `
<div>
{{ item.name }}
{{ item.price }}
</div>
`
})
export class MenuItemComponent {
@Input() item: Item;
constructor() {}
}
The @Input
decorator then tells Angular to create a property binding to the item
property that we bound to using square brackets []
, which lets us reference item
in the component and any properties such as name
and price
.
As our parent container component is in charge of rendering our child presentational component, the data is already available to us - so we don’t need any Observable references or constructor
logic - we can simply use it to render out based on the @Input
data passed into the component.
Here’s a StackBlitz example of what we’ve done so far:
Before you leave there’s one more thing to show you - custom property names. This helps us keep our child component the same, but bind to a different named property.
This is not that common to use, but well worth understanding. If you want to keep your internal class property name, but bind to a different property on the component you can use this syntax by passing a string
with a value into the @Input
decorator, for example @Input('product')
:
import { Component, Input } from '@angular/core';
@Component({
selector: 'menu-item',
template: `
<div>
{{ item.name }}
{{ item.price }}
</div>
`
})
export class MenuItemComponent {
@Input('product') item: Item;
constructor() {}
}
This then allows us to keep the item: Item
naming inside our component but bind to [product]
instead:
@Component({
selector: 'menu',
template: `
<div *ngIf="items$ | async as items">
<menu-item
*ngFor="let item of items"
[product]="item">
</menu-item>
</div>
`
})
export class MenuComponent {...}
Notice how we use [product]
above instead of [item]
. There may be various reasons why you wish to adopt this approach, but at least you know ‘how’.
This gives you some flexibility with creating component property names that may conflict with “built-in” HTML or JavaScript property names, or use it for composing your very own component library and namespacing the property name when it’s used publicly.
Next steps
Next you’ll want to dive into the Output Decorator and EventEmitter to complete the circle of one-way data flow and component communication.
I’d also highly recommend checking out my new Angular courses that will guide you through every step of the Angular ecosystem (and more).