In Angular, we often encounter a situation where a component needs to react to changes in input parameters. In this article, we will look at ways to solve this problem using the example of a "user-profile" component.
Imagine we have a user profile component that accepts an input parameter @Input() id: string
, representing a user ID. We need to request user data using this identifier and display it.
The first thing that comes to mind is to load the data with the id we need into ngOnInit()
. This approach works if the id does not change during the life of the component. Also, it's important to remember to unsubscribe from the observable to avoid potential memory leaks.
@Component({
selector: 'user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.css'],
})
export class UserProfileComponent implements OnInit, OnDestroy {
@Input() id!: string;
user?: User;
private sub$ = Subscription.EMPTY;
constructor(private readonly userService: UserService) {}
ngOnInit() {
this.sub$ = this.userService.getUser(this.id).subscribe(user => {
this.user = user;
})
}
ngOnDestroy() {
this.sub$.unsubscribe();
}
}
<ng-container *ngIf="user">
...
</ng-container>
But what if the id can change over time? With the current approach, we won't catch this change and the data won't be updated.
Another option is to use a Subject
that we will update when the id value changes and then load new data. For this, we can use the ngOnChanges
hook or set id as a setter @Input() set id(id: number) {...}
.
@Component({
selector: 'user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.css'],
})
export class UserProfileComponent {
@Input() set id(id: string) {
this.id$.next(id);
}
private id$ = new ReplaySubject<string>(1);
readonly user$: Observable<User> = this.id$.pipe(
switchMap(id => this.userService.getUser(id))
);
constructor(private readonly userService: UserService) {}
}
<ng-container *ngIf="user$ | async as user">
...
</ng-container>
With this approach, we can track id changes and update user data accordingly. It is convenient to implement this option by using the async pipe
to subscribe to profile updates in the template. This will save us from having to manually unsubscribe.
There is a simplification option in the above approach. We can create a new pipe directly in the setter @Input() set id(id: string) {...}
and subscribe to it in the template. This approach reduces code size, improves readability, and simplifies subscription management.
@Component({
selector: 'user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.css'],
})
export class UserProfileComponent {
@Input() set id(id: string) {
this.user$ = this.userService.getUser(id);
}
user$?: Observable<User>;
constructor(private readonly userService: UserService) {}
}
<ng-container *ngIf="user$ | async as user">
...
</ng-container>
With this approach, a new observable will be created each time the id is changed, and the async pipe
will automatically unsubscribe from the old observable and subscribe to the new one.
This ensures the relevance of the user data even if the id changes dynamically during the component's life.
As a result, we get an efficient and manageable way to update data based on changes in the input parameters of the component.