Полное руководство по использованию автоотписки в компонентах и ​​сервисах Angular

При разработке приложений с помощью Angular обычно используются наблюдаемые объекты для управления потоком данных и обработки асинхронных операций. Хотя наблюдаемые объекты предоставляют мощный механизм для обработки событий и данных, они также могут привести к утечкам памяти, если ими не управлять должным образом.

Один из способов предотвратить утечку памяти в Angular — вручную отписаться от наблюдаемых объектов, когда они больше не нужны. Однако это может быть утомительно и подвержено ошибкам, особенно при работе с несколькими наблюдаемыми в нескольких компонентах и ​​службах.

Чтобы упростить процесс отказа от подписки в компонентах и ​​сервисах Angular, мы можем создать собственный декоратор с именем @AutoUnsubscribe. Этот декоратор автоматически отменяет все подписки при уничтожении компонента или службы. В этом руководстве мы покажем вам, как создать и использовать декоратор @AutoUnsubscribe, и приведем примеры того, как его можно реализовать.

Создание декоратора @AutoUnsubscribe

Декоратор @AutoUnsubscribe — это настраиваемый декоратор, который мы можем создать сами в нашем приложении Angular. Он работает, автоматически отписываясь от всех подписок в компоненте или службе, когда этот компонент или служба уничтожаются.

Чтобы создать декоратор @AutoUnsubscribe, нам сначала нужно импортировать интерфейс OnDestroy из пакета @angular/core. Этот интерфейс содержит единственный метод ngOnDestroy(), который вызывается при уничтожении компонента или службы.

import { OnDestroy } from '@angular/core';

export function AutoUnsubscribe(constructor: any) {
  // Get a reference to the original ngOnDestroy function of the component/service
  const original = constructor.prototype.ngOnDestroy;

  // Override the ngOnDestroy function of the component/service
  constructor.prototype.ngOnDestroy = function() {
    // Loop through all properties of the component/service
    for (const prop in this) {
      const property = this[prop];
      // Check if the property is a subscription
      if (property && typeof property.unsubscribe === 'function') {
         // Call the unsubscribe function to unsubscribe from the subscription
        property.unsubscribe();
      }
    }

    original?.apply(this, arguments);
  };
}

В этом фрагменте кода мы определяем функцию AutoUnsubscribe, которая принимает конструктор в качестве параметра. Конструктор представляет компонент или службу, которую мы хотим украсить.

Внутри функции мы сначала храним ссылку на исходный метод ngOnDestroy() прототипа конструктора. Это важно, потому что мы хотим сохранить исходное поведение ngOnDestroy() при добавлении собственных функций.

Далее мы переопределяем метод ngOnDestroy() прототипа конструктора. Наш новый метод ngOnDestroy() перебирает все свойства компонента или службы и проверяет, является ли каждое свойство подпиской. Если свойство является подпиской, мы вызываем его метод unsubscribe(), чтобы отписаться от него.

Наконец, мы вызываем исходный метод ngOnDestroy(), если он существует, чтобы гарантировать выполнение любой дополнительной логики очистки, определенной в компоненте или службе.

Имея этот код, мы теперь можем использовать декоратор @AutoUnsubscribe для автоматической отмены подписки в наших компонентах и ​​сервисах Angular.

В следующем разделе мы покажем вам, как использовать декоратор @AutoUnsubscribe, предоставив примеры реализации.

Как работает декоратор автоотписки

Декоратор @AutoUnsubscribe — это декоратор TypeScript, предоставляющий простой способ управления наблюдаемыми объектами в Angular. Применительно к компоненту или службе декоратор @AutoUnsubscribe автоматически отменяет подписку на любые наблюдаемые подписки, когда компонент или служба уничтожаются.

Чтобы использовать декоратор @AutoUnsubscribe, просто добавьте его в качестве декоратора к вашему компоненту или классу службы, например:

import { AutoUnsubscribe } from './auto-unsubscribe.decorator';

@Component({
  selector: 'app-counter',
  templateUrl: './app-counter.component.html'
})
@AutoUnsubscribe()
export class CounterComponent implements OnInit {
  // ...
}

В этом примере мы добавили декоратор @AutoUnsubscribe в класс CounterComponent. Теперь любые наблюдаемые подписки, созданные внутри компонента, будут автоматически отменяться при уничтожении компонента.

Под капотом декоратор @AutoUnsubscribe реализует интерфейс ловушки жизненного цикла OnDestroy. Когда компонент или служба уничтожаются, Angular вызывает метод ngOnDestroy, который, в свою очередь, отменяет подписку на любые наблюдаемые подписки.

Примеры реализации

Давайте рассмотрим несколько примеров реализации, чтобы увидеть, как декоратор @AutoUnsubscribe работает на практике.

Пример с компонентом

Предположим, у нас есть компонент, который подписывается на наблюдаемое и обновляет шаблон представления данными:

import { Component, OnInit } from '@angular/core';
import { Subscription, interval } from 'rxjs';

@Component({
  selector: 'app-counter',
  template: `<h1>Count: {{ count }}</h1>`
})
export class CounterComponent implements OnInit {
  private subscription: Subscription;
  public count: number = 0;

  ngOnInit() {
    this.subscription = interval(1000).subscribe(() => {
      this.count++;
    });
  }

  ngOnDestroy() {
    console.log('CounterComponent destroyed');
  }
}

В этом примере мы используем оператор interval из RxJS для генерации числа каждую секунду. Мы обновляем свойство count компонента этим числом и используем интерполяцию строк, чтобы отобразить значение count в представлении.

Теперь предположим, что мы уходим со страницы до завершения интервала. Без декоратора @AutoUnsubscribe подписка будет продолжать выдавать значения, а свойство count будет бесконечно увеличиваться. Это может привести к утечке памяти и проблемам с производительностью.

Однако с декоратором @AutoUnsubscribe подписка автоматически отменяется при уничтожении компонента. Мы можем проверить это, добавив оператор журнала в метод ngOnDestroy:

ngOnDestroy() {
  console.log('CounterComponent destroyed');
}

Теперь, когда мы уходим со страницы, в консоль выводится оператор журнала, указывающий, что компонент был уничтожен, а подписка отменена.

Чтобы использовать декоратор @AutoUnsubscribe с этим компонентом, мы просто добавляем его как декоратор в класс:

import { Component, OnInit } from '@angular/core';
import { Subscription, interval } from 'rxjs';
import { AutoUnsubscribe } from './auto-unsubscribe.decorator';

@Component({
  selector: 'app-counter',
  template: `
    <h1>Count: {{ count }}</h1>
  `
})
@AutoUnsubscribe()
export class CounterComponent implements OnInit {
  private subscription: Subscription;
  public count: number = 0;

  ngOnInit() {
    this.subscription = interval(1000).subscribe(() => {
      this.count++;
    });
  }

  // only for check
  ngOnDestroy() {
    console.log('CounterComponent destroyed');
  }
}

Теперь, когда мы уходим со страницы, CounterComponent уничтожается, а подписка автоматически отменяется.

Пример с сервисом

Предположим, у нас есть сервис, который извлекает данные из API и предоставляет их компоненту:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AppService{
  private apiUrl = 'https://jsonplaceholder.typicode.com/posts';

  constructor(private http: HttpClient) {}

  getPosts(): Observable<any[]> {
    return this.http.get<any[]>(this.apiUrl);
  }

  ngOnDestroy() {
    console.log('AppService destroyed');
  }
}

В этом примере мы используем HttpClient для получения данных из API и возвращаем наблюдаемый объект, который создает массив сообщений. Мы также добавили в службу метод ngOnDestroy, чтобы продемонстрировать, что он вызывается при уничтожении службы.

Теперь предположим, что мы используем этот сервис в компоненте для получения и отображения сообщений:

import { Component, OnInit } from '@angular/core';
import { AppService} from './app.service';

@Component({
  selector: 'app-counter',
  template: `
    <ul>
      <li *ngFor="let post of posts">
        {{ post.title }}
      </li>
    </ul>
  `
})
export class CounterComponent implements OnInit {
  public posts: any[];

  constructor(private appService: AppService) {}

  ngOnInit() {
    this.appService.getPosts().subscribe(posts => {
      this.posts = posts;
    });
  }
}

В этом примере мы вводим AppService в конструктор CounterComponent и используем метод getPosts для получения сообщений и сохранения их в свойстве posts компонента.

Опять же, без декоратора @AutoUnsubscribe подписка на метод getPosts будет продолжать выдавать значения даже после уничтожения компонента. Однако с помощью декоратора @AutoUnsubscribe подписка автоматически отменяется при уничтожении компонента.

Чтобы использовать декоратор @AutoUnsubscribe с этим сервисом, мы просто добавляем его как декоратор в класс:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { AutoUnsubscribe } from './auto-unsubscribe.decorator';

@Injectable({
  providedIn: 'root'
})
@AutoUnsubscribe()
export class AppService {
  private apiUrl = 'https://jsonplaceholder.typicode.com/posts';

  constructor(private http: HttpClient) {}

  getPosts(): Observable<any[]> {
    return this.http.get<any[]>(this.apiUrl);
  }

// only for check
ngOnDestroy() {
  console.log('AppService destroyed');
  }
}

Теперь, когда мы используем AppService в компоненте и уходим со страницы, служба уничтожается, а подписка автоматически отменяется.

Заключение

В этой статье мы узнали о @AutoUnsubscribedecorator, который автоматически отписывается от подписок, чтобы предотвратить утечку памяти в приложениях Angular. Мы увидели, как реализовать декоратор @AutoUnsubscribe как для компонентов, так и для сервисов, и увидели, как он работает на практике, на примерах.

Используя декоратор @AutoUnsubscribe, мы можем гарантировать, что наши приложения Angular свободны от утечек памяти, вызванных подписками. Это делает наши приложения более надежными и снижает вероятность сбоев из-за проблем с памятью.

Я надеюсь, что эта статья была полезна для понимания важности отказа от подписки в Angular и того, как использовать декоратор @AutoUnsubscribe для автоматизации этого процесса.

Спасибо за чтение и продолжайте посещать!