JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Подписка на Observable: s...

Подписка на Observable: subscribe(), обработка данных

Модуль 4: Node.js, Next.js и Angular
17 уровень , 3 лекция
Открыта

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 не узнает о новых данных, и не обновит шаблон. Решение: использовать сервисы и подписываться внутри компонентов.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ