Задача
Отрефакторить компонент: убрать ручные подписки, утечку памяти и лишний код. Объяснить преимущества async pipe.
@Component({
selector: 'user-list',
template: `
<div *ngFor="let user of users">{{ user.name }}</div>
<p>Total: {{ total }}</p>
`,
})
export class UserListComponent implements OnInit {
users: User[] = [];
total = 0;
constructor(private userService: UserService) {}
ngOnInit(): void {
this.userService.getUsers().subscribe((users) => {
this.users = users;
this.total = users.length;
});
// нет отписки — утечка
}
}Проблемы
Что не так
- Подписка без отписки — при уничтожении компонента поток остаётся живым (утечка памяти).
- Ручное присваивание в поля — лишний императивный код, состояние легко рассинхронизировать.
totalдублирует данные — это производное значение отusers, отдельное поле не нужно.- Плохо работает с OnPush — присваивание вне зоны/асинхронно может не вызвать перерисовку без ручного
markForCheck.
Решение
Оптимальное решение (async pipe)
@Component({ selector: 'user-list', template: ` <ng-container *ngIf="users$ | async as users"> <div *ngFor="let user of users">{{ user.name }}</div> <p>Total: {{ users.length }}</p> </ng-container> `, changeDetection: ChangeDetectionStrategy.OnPush, }) export class UserListComponent { // поток объявляется декларативно, без подписки в TS users$: Observable<User[]> = inject(UserService).getUsers(); }
Почему async pipe лучше
- Сам подписывается и отписывается при уничтожении компонента — нет утечек, не нужен
OnDestroy/takeUntil.- Дружит с OnPush: помечает компонент на проверку при новом значении, перерисовка происходит автоматически.
- Декларативно: поток описан в одном месте, нет ручного управления состоянием.
- Производные значения (
users.length) берём прямо в шаблоне из развёрнутого значения — без дублирующих полей.
Если подписка в TS всё же нужна — takeUntilDestroyed
export class UserListComponent { private destroyRef = inject(DestroyRef); constructor(private userService: UserService) { this.userService.getUsers() .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(/* ... */); } }