Осторожно! Angular крадет ваше время

Angular

Angular может все  —  ну или почти все. Но иногда это «почти» заставляет вас тратить время на написание обходных решений или на попытки понять, почему что-то происходит или не работает, как нужно. Я хочу сэкономить вам время и рассказать об этих ловушках Angular, основываясь на собственном опыте. Начнем.

#1 Пользовательская директива не работает

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

Осторожно! Angular крадет ваше время — IT-МИР. ПОМОЩЬ В IT-МИРЕ 2020Не сработало, Angular молчит.

Запускаем приложение и… оно не работает. Как опытный разработчик вы проверяете консоль Chrome. И ничего не видите 🙂

Тогда какая-то светлая голова в вашей команде догадывается заключить директиву в квадратные скобки:

Теперь, потратив время, мы находим причину:

Осторожно! Angular крадет ваше время — IT-МИР. ПОМОЩЬ В IT-МИРЕ 2020Ха! Мы просто забыли импортировать модуль вместе с директивой.

Проблема очевидна: мы просто забыли импортировать модуль директивы в модуль приложения Angular.

Итак, важное правило: никогда не используйте директивы без квадратных скобок.

Проверить, как это работает, можно в песочнице Stackblitz.

#2 ViewChild возвращает undefined

Вы помещаете ссылку на элемент inputв шаблон Angular.

Осторожно! Angular крадет ваше время — IT-МИР. ПОМОЩЬ В IT-МИРЕ 2020Ссылка #inputTag

И хотите создать поток с обновлениями ввода текста с помощью RxJS функции fromEvent. Вам нужно ввести нативный элемент ref, который можно получить с помощью декоратора Angular ViewChild:

Осторожно! Angular крадет ваше время — IT-МИР. ПОМОЩЬ В IT-МИРЕ 2020Создание потока из изменений данных элемента ввода с помощью RxJS функции fromEvent

Давайте запустим код:

Осторожно! Angular крадет ваше время — IT-МИР. ПОМОЩЬ В IT-МИРЕ 2020Хорошая картинка 🙂

ЧТО?

Здесь главное правило: если ViewChild возвращает undefined, ищите *ngIf в шаблоне.

Осторожно! Angular крадет ваше время — IT-МИР. ПОМОЩЬ В IT-МИРЕ 2020Вот он, наш виновник *ngIf

Кроме того, проверьте, есть ли другие структурные директивы или ng-шаблоны над этим элементом.

Возможные решения:

  1. Просто спрячьте элемент в шаблоне, когда он вам не нужен. В этом случае элемент всегда существует, и ViewChild может вернуть ссылку на него в хук-функции ngAfterViewInit.
Осторожно! Angular крадет ваше время — IT-МИР. ПОМОЩЬ В IT-МИРЕ 2020

2. Другая возможность  —  использовать сеттер:

Angular единожды присваивает inputTagзаданное значение, тогда мы создаем поток из введенных данных элемента.

#3 Запускаем код после обновления *ngFor

Скажем, у вас есть хорошая пользовательская директива прокрутки. Вы хотите применить ее в списке, сгенерированном *ngFor.

Если список обновился, мы обычно запускаем метод scrollDirective.update, чтобы пересчитать математику прокрутки, так как общая высота элементов тоже изменилась. Можно сделать это внутри хук-функции ngOnChanges:

Но… метод вызывается до того, как список новых элементов отображается в браузере. Значит, пересчет директивы прокрутки будет неверным 😒.

Но как вызвать метод сразу после того, как *ngForзакончит свою работу? Это можно сделать в 3 простых шага:

  •  Поместите ссылки на элементы, к которым применяется *ngFor (#listItems).
Осторожно! Angular крадет ваше время — IT-МИР. ПОМОЩЬ В IT-МИРЕ 2020
  • С помощью декоратора ViewChildrenполучите список этих элементов. Он вернет объект типа QueryList.
  • Класс QueryList предоставляет свойство только для чтения changes. Оно оповещает каждый раз, когда изменяется список.

Проблема решена. С примерами можно поиграть в песочнице.

#4 Проблема с ActivatedRoute.queryParam

Чтобы понять следующую проблему, подробно рассмотрим этот код:

  1. Мы определяем маршруты и добавляем RouterModule к основному модулю приложения. Маршруты сконфигурированы так: если в URL не указан маршрут, мы перенаправляем использование на страницу /home.
  2. AppComponent  —  корневой.
  3. AppComponent использует <router-outlet> для отображения соответствующих компонентов маршрута.
  4. Основная часть: мы хотим получить queryParams маршрута из URL.

Например:

https://localhost:4400/home?accessToken=someTokenSequence

Тогда у запроса один параметр: {accessToken: ‘someTokenSequence’}.

Осторожно! Angular крадет ваше время — IT-МИР. ПОМОЩЬ В IT-МИРЕ 2020Тестовое приложение с маршрутом

Вы можете спросить  —  в чем проблема? Параметры получены  —  бинго!

Хорошо, если вы внимательно посмотрите на скриншот, вы можете заметить, что queryParams отображается дважды. Первый эмит пустого объекта происходит как часть процесса инициализации Angular Router. И только после этого мы получаем объект с актуальными queryParams ({accessToken: ‘someTokenSequence’} в примере).

Проблема вот в чем: если в URL не указаны параметры запроса, то маршрутизатор ничего не эмитит: нет второго пустого объекта, который сообщит, что параметров нет.

Осторожно! Angular крадет ваше время — IT-МИР. ПОМОЩЬ В IT-МИРЕ 2020Нет повторного оповещения из ActivatedRoute.queryParams

Значит, код ждет повторного оповещения, чтобы получить актуальные данные (пустые или нет). Он никогда не запустится, если в URL нет параметров. Как решить эту проблему? Здесь нам поможет RxJS. Создадим два Observable из ActivatedRoute.queryParams:

  • Первый Observable paramsInUrl$сработает, если значение queryParamsне пустое:
  •  Второй Observable noParamsInUrl$эмитит пустой объект, если в URL нет параметров:

Теперь скомбинируем их с RxJS функцией merge:

Составная Observable params$ испускает значение только один раз, независимо от того, присутствует ли queryParams (выдан пустой объект) или нет (выдан объект со значениями queryParams).

Проверить, как работает код, можно здесь.

#5 Низкий FPS страницы

Есть компонент, отображающий некоторые отформатированные значения, например:

Этот компонент делает две вещи:

  1. Отображает массив элементов (предполагается, что один раз). Также, вызывая метод formatItem, он форматирует отображаемый вывод.
  2. Отображает координаты мыши (значение определенно будет обновляться часто).

Вы не ожидаете большого влияния на производительность и запускаете тест  —  просто чтобы соблюсти формальности. Тест показывает что-то странное:

Осторожно! Angular крадет ваше время — IT-МИР. ПОМОЩЬ В IT-МИРЕ 2020Множество вызовов formatItem и высокая загрузка ЦП.

Но почему?

Потому что каждый раз, перерисовывая шаблон, Angular вызывает все его функции (в нашем случае это formatItem). Следовательно, выполнение функцией трудоемких вычислений в шаблоне влияет на загрузку ЦП и впечатления пользователя.

Как это исправить? Просто сделать предварительные вычисления formatItem и отобразить рассчитанное значение.

Теперь результаты теста выглядят приемлемо.

Осторожно! Angular крадет ваше время — IT-МИР. ПОМОЩЬ В IT-МИРЕ 2020Только 6 вызовов formatItem и низкая загрузка ЦП.

Приложение работает лучше, но у этого решения есть свои минусы:

  • В шаблоне отображаются координаты мыши. А значит, событие mousemove все еще провоцирует запуск обнаружения изменений. Но это неизбежно, раз нам нужны эти координаты.
  • Если обработчик mousemoveдолжен выполнять только вычисления (без отображения результата), вот что ускорит работу приложения:

1. Можно использовать NgZone.runOutsideOfAngular внутри функции обработчика, чтобы предотвратить обнаружение изменений в mousemove(это влияет только на конкретный обработчик).

2. Можно предотвратить исправление zone.js для некоторых событий, добавив эту строку в polyfill.ts. Это повлияет на все приложение.

* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // отключает изменение указанного eventNames

Подробнее на эту тему на русском языке:

  1. Оптимизация размера Angular bundle за 4 шага.
  2. Улучшите производительность с помощью веб-воркеров.

Специально для сайта ITWORLD.UZ. Новость взята с сайта NOP::Nuances of programming