От автора: если вы новичок в Angular или не знаете, что представляет собой в Angular жизненный цикл, я предлагаю вам ознакомиться с официальной документацией. Сегодняшний материал является продолжением твита Angular Hot Tip, который я разослал ранее на этой неделе. Он стал популярным и вызвал широкую дискуссию. Концепции, рассмотренные в этой статье, отражают это обсуждение.
В качестве продолжения вышеупомянутого твита мы обсудим ограничения, связанные с тем, как и когда вызывается ngOnDestroy. Мы также обсудим способы преодоления этих ограничений.
Версии пакета NPM
Для контекста, эта статья предполагает, что вы используете следующие версии npm package.json: @angular/*: 7.2.9
Краткое руководство по NgOnDestroy
Прежде чем углубляться в данную тему, давайте потратим несколько минут и рассмотрим ngOnDestroy. NgOnDestroy — это метод жизненного цикла, который можно добавить, реализовав класс OnDestroy и добавив новый метод класса с именем ngOnDestroy. Согласно Angular Docs, его основная цель — «Выполнить очистку непосредственно перед тем, как Angular удалит директиву / компонент. Отменить подписку Observables и отсоединить обработчики событий, чтобы избежать утечек памяти. Вызывается непосредственно перед тем, как Angular удалит директиву / компонент».
Утечки из MyValueComponent
Давайте представим, что у нас есть компонент с именем MyValueComponent, который подписывается на значение из MyService в методе ngOnInit:
import { Component, OnInit } from '@angular/core'; import { MyService } from './my.service'; @Component({ selector: 'app-my-value', templateUrl: './my-value.component.html', styleUrls: [ './my-value.component.css' ] }) export class MyValueComponent implements OnInit { myValue: string; constructor(private myService: MyService) {} ngOnInit() { this.myService.getValue().subscribe(value => this.myValue = value); } }
Если этот компонент создается и удаляется несколько раз в жизненном цикле приложения Angular, каждый раз, когда он создается, должен вызываться ngOnInit для создания новой подписки. Это может быстро выйти из-под контроля, при этом значение будет обновляться в геометрической прогрессии. Это создает то, что называется «утечка памяти». Утечки памяти могут нанести ущерб производительности приложения и, кроме того, добавить непредсказуемые или непреднамеренные действия. Дальше мы узнаем, как устранить эту утечку.
Исправление утечки для MyValueComponent
Чтобы устранить утечку памяти, нам нужно дополнить класс компонента реализацией подписки OnDestroy и unsubscribe от подписки. Давайте обновим наш компонент, добавив следующее:
Добавим OnDestroy в import typescript
Добавим OnDestroy в список implements
Создадим поле класса с именем myValueSub: Subscription, чтобы отслеживать подписку
Установим значение this.myValueSub равным значению this.myService.getValue().subscription
Создадим новый метод класса с именем ngOnDestroy
Вызовем this.myValueSub.unsubscribe() в ngOnDestroy, в случае если подписка установлена.
Обновленный компонент будет выглядеть примерно так:
import { Component, OnInit, OnDestroy } from '@angular/core'; import { MyService } from './my.service'; @Component({ selector: 'app-my-value', templateUrl: './my-value.component.html', styleUrls: [ './my-value.component.css' ] }) export class MyValueComponent implements OnInit, OnDestroy { myValue: string; myValueSub: Subscription; constructor(private myService: MyService) {} ngOnInit() { this.myValueSub = this.myService.getValue().subscribe(value => this.myValue = value); } ngOnDestroy() { if (this.myValueSub) { this.myValueSub.unsubscribe(); } } }
Что еще кроме утечек памяти?
Отлично! Теперь у вас есть некоторые сведения о ngOnDestroy и о том, что устранение утечек памяти является основным вариантом использования этого метода жизненного цикла. Но что, если вы хотите сделать еще один шаг и добавить дополнительную логику очистки? Как насчет выполнения вызовов очистки на стороне сервера? Может быть, предотвращение пользовательской навигации?
Дальше, мы обсудим три метода обновления ngOnDestroy для оптимального использования.
Обновление № 1 — Создание Async NgOnDestroy
Как и в случае других методов жизненного цикла в Angular, вы можете изменить ngOnDestroy с помощью async. Это позволит выполнять вызовы методов, возвращающих Promise. Это может быть мощным способом управления действиями по очистке в приложении. Рассмотрим пример.
Добавление логики для вызова AuthService.logout из ngOnDestroy
Давайте представим, что вам нужно выполнить выход из системы на стороне сервера, когда MyValueComponent удален. Для этого нам нужно обновить метод следующим образом:
Добавить AuthService к imports
Добавить AuthService к constructor
Добавить async перед именем метода ngOnDestroy
Вызвать AuthService, чтобы выйти из системы, используя ключевое слово await.
Обновленный MyValueComponent будет выглядеть примерно так:
import { Component, OnInit, OnDestroy } from '@angular/core'; import { MyService } from './my.service'; import { AuthService } from './auth.service'; @Component({ selector: 'app-my-value', templateUrl: './my-value.component.html', styleUrls: [ './my-value.component.css' ] }) export class MyValueComponent implements OnInit, OnDestroy { myValue: string; myValueSub: Subscription; constructor(private myService: MyService, private authService: AuthService) {} ngOnInit() { this.myValueSub = this.myService.getValue().subscribe(value => this.myValue = value); } async ngOnDestroy() { if (this.myValueSub) { this.myValueSub.unsubscribe(); } await this.authService.logout(); } }
Тадам! Теперь, когда компонент удален, будет выполнен вызов async для выхода пользователя из системы и удаления его сеанса на сервере.
Обновление № 2 — обеспечение выполнения во время событий браузера
Многие разработчики удивляются, узнав, что ngOnDestroyон запускается только тогда, когда класс, в котором он был реализован, удаляется в контексте запущенной сессии браузера. Другими словами, ngOnDestroy вызывается не надежно в следующих сценариях:
Обновление страницы
Закрытие вкладки
Закрытие окна браузера
Переход со страницы
Это может нарушить выполнения в контексте предыдущего примера выхода пользователя из системы при удалении. Почему? Ну, большинство пользователей просто закроют сеанс браузера или перейдут на другой сайт. Итак, как мы можем обеспечить захват или подключение к этим действиям, если ngOnDestroyона не работает в этих сценариях?
Декорирование ngOnDestroy с помощью HostListener
Декораторы TypeScript используются во всех приложениях Angular. Более подробную информацию можно найти в официальной документции TypeScript.
Чтобы обеспечить выполнение ngOnDestroy при вышеупомянутых событиях браузера, мы можем добавить одну простую строку кода в начало ngOnDestroy. Давайте продолжим предыдущий пример MyValueComponent и декорируем ngOnDestroy:
Добавим HostListener в импорты
Поместим @HostListener(‘window:beforeunload’) перед ngOnDestroy
Наш обновленный MyValueComponent будет выглядеть примерно так:
import { Component, OnInit, OnDestroy, HostListener } from '@angular/core'; import { MyService } from './my.service'; import { AuthService } from './auth.service'; @Component({ selector: 'app-my-value', templateUrl: './my-value.component.html', styleUrls: [ './my-value.component.css' ] }) export class MyValueComponent implements OnInit, OnDestroy { myValue: string; myValueSub: Subscription; constructor(private myService: MyService, private authService: AuthService) {} ngOnInit() { this.myValueSub = this.myService.getValue().subscribe(value => this.myValue = value); } @HostListener('window:beforeunload') async ngOnDestroy() { if (this.myValueSub) { this.myValueSub.unsubscribe(); } await this.authService.logout(); } }
Теперь метод ngOnDestroy вызывается как при удалении компонента Angular, так и при возникновении события браузера window:beforeunload. Это мощная комбинация!
Подробнее о HostListener
@HostListener() является декоратором Angular, который может быть помещен перед любым методом класса. Этот декоратор принимает два аргумента: eventName и необязательный args. В приведенном выше примере мы передаем window:beforeunload, как событие DOM. Это означает, что Angular будет автоматически вызывать наш метод при возникновении события DOM window:beforeunload. Для получения дополнительной информации о @HostListener ознакомьтесь с официальной документацией. Если вы хотите использовать это для предотвращения перехода со страницы или компонента, то:
Добавьте $event к аргументам @HostListener
Вызовите event.preventDefault()
Установите строковое значение сообщения event.returnValue, которое будет отображать браузер
Наш пример будет выглядеть так:
@HostListener('window:beforeunload', ['$event']) async ngOnDestroy($event) { if (this.myValueSub) { this.myValueSub.unsubscribe(); } await this.authService.logout(); $event.preventDefault(); $event.returnValue = 'A message.'; }
ОБРАТИТЕ ВНИМАНИЕ: Это официально не поддерживается Angular! OnDestroy и ngOnDestroy предполагают, что ngOnDestroy не передаются входные аргументы. Хотя это и не поддерживается, на самом деле пока все будет функционировать, как обычно.
Подробнее о window:beforeunload
Событие window:beforeunload, которое инициируется непосредственно перед выгрузкой window. Более подробную информацию можно найти в документации.
Пара моментов, о которых нужно знать:
Это событие в настоящее время поддерживается во всех основных браузерах, кроме iOS Safari.
Если вам нужен этот функционал в iOS Safari, посмотрите эту тему на Stack Overflow.
Если вы используете это событие, чтобы заблокировать переход, вам нужно установить строку сообщения event.returnValue, которую вы хотите отобразить. Подробнее в этом примере.
Заключение
Я понимаю, что некоторые из советов, приведенных в этой статье, не являются общепринятыми и могут вызвать некоторую обеспокоенность. Пожалуйста, помните, что, как всегда, сначала стоит попробовать их и посмотреть, подходят ли они для того, что вы делаете в своем приложении. Если они работают — отлично! Если нет, тогда можно двигаться дальше.
Автор: Wes Grimes
Источник: https://dev.to
Редакция: Команда webformyself.