Angular: Effective ways of using @Input() or input()
Decorator
When developing Angular applications, understanding how to use the @Input()
decorator effectively is essential for clean, maintainable code. Angular applications are often structured around two types of components: smart components and dumb components.
The Role of Smart and Dumb Components
- Dumb components are presentational components. They focus on rendering data and managing user interactions without handling business logic or data fetching. They receive data exclusively through the
@Input()
decorator. - Smart components manage the application’s logic, including data fetching and state management. They pass data to dumb components using the
@Input()
decorator and handle services.
Defining Parent and Child Components in Angular
A component is considered a child component when it is declared inside another component’s HTML template. Conversely, the parent component declares the child component in its template.
- Dumb components are always child components, while smart components can be both. Generally, smart components act as parent components and rarely use input or output decorators.
Best Practices for Using the @Input()
Decorator
1. Define Data Contracts with Interfaces
When passing data between components, define interfaces to ensure the data structure is clear and consistent.
For example, to pass items to a navbar component, define an interface:
export interface NavbarItem {
label: string;
route: string;
}
2. Use @Input()
Decorator to Receive Data
Decorate a property in your child component to receive data from the parent:
@Input() navbarItems: NavbarItem[] = [];
This tells Angular that the navbarItems
property can receive input from a parent component.
- Providing a default value (e.g., an empty array) prevents errors during compilation.
- If you prefer not to assign a default, use the
!
operator to assert that the property will be initialized:
@Input() navbarItems!: NavbarItem[];
3. Pass Data from Parent to Child Components
To pass data from a parent component to a child component, define the data in the parent component:
navItems: NavbarItem[] = [
{ label: 'home', route: '/' },
{ label: 'expenses approval', route: '/expenses-approval' }
];
Use Angular’s property binding to pass this data in the parent component’s template:
<app-navbar [navbarItems]="navItems"></app-navbar>
This syntax uses the square brackets ([ ]
) to bind the parent component's navItems
array to the child component's navbarItems
input property.
4. Monitor Input Changes with the ngOnChanges
Hook
When an input property changes, Angular triggers the ngOnChanges()
lifecycle hook. This hook provides an opportunity to respond to changes:
ngOnChanges(changes: SimpleChanges) {
console.log(changes);
}
Use ngOnChanges()
for extra logic when inputs change, but be mindful that it can become cumbersome if many input properties are involved.
5. Simplify Input Handling with Getters and Setters
Transform the @Input()
property into a getter and setter to handle more complex logic:
- Create a Private Property:
private navItems: NavbarItem[] = [];
2. Define the Getter and Setter:
@Input() set navbarItems(value: NavbarItem[]) {
this.navItems = [{ label: 'home', route: '/' }, ...value];
}
get navbarItems(): NavbarItem[] { return this.navItems; }
By doing this, every time new values are set, you can manipulate the data, such as adding a default home route.
6. Use the Transform Property for Cleaner Code
From Angular 16.1, you can use the transform
property of the @Input()
decorator to achieve the same transformation without using getters and setters:
@Input({ transform: addHome }) navbarItems: NavbarItem[] = [];
Then, define the transformation function:
function addHome(items: NavbarItem[]): NavbarItem[] {
return [{ label: 'home', route: '/' }, ...items];
}
This approach reduces boilerplate and keeps your code cleaner.
7. Make Inputs Required
Starting from Angular 16, you can make input properties required:
@Input({ transform: addHome, required: true }) navbarItems: NavbarItem[] = [];
This ensures the parent component provides a value for the input property, or the compiler throws an error, improving code safety.
Conclusion
Using the @Input()
decorator effectively allows you to build modular, maintainable Angular applications. By defining clear data contracts, monitoring changes, and leveraging new Angular features like the transform
property, you can streamline your component communication and manage inputs efficiently.