1. Observable: что значит "подписаться"?
Если вы только начали работать с RxJS, слово подписка (subscribe) может звучать как что-то из мира журналов или YouTube. В программировании идея похожа: Observable — это как газета, на которую вы можете подписаться, чтобы получать новые выпуски. Пока не подписались — газета вам не приходит, а Observable не "запускается".
subscribe() — это метод, который "включает" Observable. До вызова subscribe никакие данные не поступают, никакой работы не происходит.
Пример
import { Observable } from 'rxjs';
const obs = new Observable(subscriber => {
subscriber.next('Привет!');
subscriber.next('Мир!');
subscriber.complete();
});
obs.subscribe(value => {
console.log(value);
});
Что произойдет:
Как только мы вызвали subscribe, Observable начал "работать" и выдал два значения: "Привет!" и "Мир!", которые вывелись в консоль.
2. Синтаксис subscribe()
Метод subscribe может принимать до трёх функций-обработчиков:
observable.subscribe(
nextHandler, // что делать с очередным значением
errorHandler, // что делать при ошибке
completeHandler // что делать при завершении
);
Можно передавать их по отдельности или объектом:
observable.subscribe({
next: value => { ... },
error: err => { ... },
complete: () => { ... }
});
Примеры
Только обработка данныхobs.subscribe(value => {
console.log('Получено:', value);
});
Обработка данных и ошибок
obs.subscribe(
value => console.log('Получено:', value),
error => console.error('Ошибка:', error)
);
Обработка всех событий
obs.subscribe(
value => console.log('Получено:', value),
error => console.error('Ошибка:', error),
() => console.log('Завершено!')
);
Через объект
obs.subscribe({
next: value => console.log('Получено:', value),
error: error => console.error('Ошибка:', error),
complete: () => console.log('Завершено!')
});
3. Обработка данных из Observable: на практике
Пример: HTTP-запрос в Angular
Продолжаем развивать наше учебное Angular-приложение. Пусть у нас есть сервис, который получает список пользователей с сервера:
// user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class UserService {
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>('https://jsonplaceholder.typicode.com/users');
}
}
Теперь в компоненте мы можем подписаться на этот Observable:
// user-list.component.ts
import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';
import { User } from './user.model';
@Component({
selector: 'app-user-list',
template: `
<div *ngFor="let user of users">
{{ user.name }} ({{ user.email }})
</div>
<div *ngIf="error" class="error">
Ошибка: {{ error }}
</div>
`
})
export class UserListComponent implements OnInit {
users: User[] = [];
error: string = '';
constructor(private userService: UserService) {}
ngOnInit() {
this.userService.getUsers().subscribe(
users => this.users = users, // next
err => this.error = 'Не удалось загрузить пользователей' // error
);
}
}
Что происходит:
- Когда компонент инициализируется, мы подписываемся на Observable, который возвращает массив пользователей.
- Как только данные приходят — сохраняем их в свойство users.
- Если произошла ошибка — выводим сообщение.
4. Как работает подписка: жизненный цикл
Когда вы подписываетесь на Observable:
- Observable начинает "исполняться" (например, отправляется HTTP-запрос).
- Как только приходят данные, вызывается обработчик next.
- Если что-то пошло не так — вызывается обработчик error.
- Когда Observable завершился (например, поток данных закончен, или сервер ответил) — вызывается обработчик complete.
Важно!
Observable может выдать не одно, а несколько значений (например, события клика мыши). HTTP-запрос, как правило, выдает одно значение и завершает Observable.
5. Что возвращает subscribe()
Метод subscribe() возвращает объект подписки (Subscription). Это что-то вроде "абонемента" — с его помощью можно отменить подписку, если она больше не нужна.
Пример: отписка вручную
import { Subscription } from 'rxjs';
export class ExampleComponent implements OnInit, OnDestroy {
sub!: Subscription;
ngOnInit() {
this.sub = this.userService.getUsers().subscribe(users => {
// ...
});
}
ngOnDestroy() {
// **Важно!** Отписываемся, чтобы не было утечки памяти
this.sub.unsubscribe();
}
}
Зачем отписываться?
- Если Observable бесконечный (например, слушает события клика или WebSocket), и компонент уничтожается, подписка останется "висеть" в памяти.
- Для HTTP-запросов обычно не критично (они завершаются сами), но для событий и таймеров — обязательно!
6. Полезные нюансы
Обработка ошибок
Ошибки — неотъемлемая часть жизни программиста. В Observable ошибки обрабатываются вторым аргументом subscribe или через свойство error объекта-обработчика.
Пример: обработка ошибкиthis.userService.getUsers().subscribe(
users => this.users = users,
error => {
console.error('Ошибка при загрузке пользователей:', error);
this.error = 'Что-то пошло не так!';
}
);
Что произойдет, если не обработать ошибку?
- Observable "упадет" и завершится, но вы не узнаете, что именно случилось.
- В Angular приложение не "сломается", но пользователь останется без информации.
Обработка завершения (complete)
Третий обработчик — это функция, вызываемая, когда Observable завершился.
Примерobs.subscribe(
value => console.log('Значение:', value),
error => console.error('Ошибка:', error),
() => console.log('Observable завершился!')
);
Когда это полезно?
- Для отображения "загрузка завершена" или скрытия индикатора загрузки.
- Для очистки ресурсов.
Подписка на события: пример с кнопкой
Observable — это не только HTTP-запросы. Например, можно подписаться на события DOM:
import { fromEvent } from 'rxjs';
const button = document.getElementById('myButton');
const clicks$ = fromEvent(button, 'click');
const sub = clicks$.subscribe(event => {
console.log('Клик!', event);
});
В Angular обычно работают с событиями через шаблон, но RxJS позволяет делать сложные цепочки событий.
Отписка: зачем и когда
Классика жанра:
- Подписались на Observable, компонент уничтожился, а подписка осталась.
- В результате: утечка памяти, неожиданные ошибки, "зомби"-код.
Лучшие практики
- Для "короткоживущих" Observable (HTTP-запросы) — можно не отписываться вручную.
- Для "долгоиграющих" (клики, таймеры, WebSocket) — обязательно отписывайтесь!
- В Angular часто используют async pipe — он сам отписывается (рассмотрим позже).
7. Пример: подписка и отписка в реальном компоненте
Давайте добавим "живой" пример в наше приложение. Например, будем показывать количество секунд, прошедших с открытия компонента:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { interval, Subscription } from 'rxjs';
@Component({
selector: 'app-timer',
template: `<div>Прошло секунд: {{ seconds }}</div>`
})
export class TimerComponent implements OnInit, OnDestroy {
seconds = 0;
private sub!: Subscription;
ngOnInit() {
this.sub = interval(1000).subscribe(count => {
this.seconds = count;
});
}
ngOnDestroy() {
// **Важно отписаться!**
this.sub.unsubscribe();
}
}
Что происходит:
- Observable interval(1000) каждую секунду выдает новое число.
- Мы подписываемся и обновляем счетчик.
- Когда компонент уничтожается — отписываемся, чтобы не было утечек.
8. Типичные ошибки при подписке на Observable
Ошибка №1: Забыли отписаться от "долгоиграющего" Observable.
Это приводит к утечкам памяти. Если компонент уничтожился, а подписка осталась, данные продолжают приходить в никуда.
Ошибка №2: Не обработали ошибку.
Если не указать обработчик ошибок, и произойдет сбой (например, сервер не отвечает), вы не узнаете об этом, и приложение не сможет корректно реагировать.
Ошибка №3: Подписка в шаблоне через переменную, а не через async pipe.
В Angular рекомендуется использовать async pipe для отображения Observable в шаблоне — он сам управляет подпиской и отпиской.
Ошибка №4: Множественные подписки на один и тот же Observable.
Если Observable "холодный" (например, HTTP-запрос), каждый subscribe запускает новую операцию (новый запрос!). Иногда это неожиданно.
Ошибка №5: Попытка подписаться на Observable вне зоны Angular (например, в чистом JS).
В этом случае Angular не узнает о новых данных, и не обновит шаблон. Решение: использовать сервисы и подписываться внутри компонентов.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ