Уважаемый NgReader, добро пожаловать в серию моих статей Angular Abyss, в которых описаны самые горячие темы и распространенные проблемы Angular. Надеюсь, ты повеселишься!

Все статьи:

  1. Жизненный цикл компонентов.
  2. Объем услуги.
  3. Обнаружение изменений OnPush.
  4. Оптимизация NgFor с помощью магии trackBy.

Проблема:

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

1. Настройка

Прежде всего, давайте создадим проект с помощью Angular CLI.

ng new my-app --routing

Я использую VS Code, который имеет удобный ярлык командной строки для открытия редактора в текущем каталоге.

cd my-app && code .

Теперь используйте angular cli для запуска следующих команд.

ng g c components/parent
ng g c components/block-a
ng g c components/block-b
ng g c components/child-a
ng g c components/child-b

Вы должны увидеть такую ​​структуру:

Построим простое дерево компонентов. У нас есть один компонент Parent, у которого есть два дочерних элемента: BlockA и BlockB. У блоков есть свои дети.

2. Построить иерархию компонентов

Прежде чем мы начнем, давайте добавим CSS, чтобы упростить визуализацию наших вложенных блоков. Откройте app.component.css и вставьте этот код:

Теперь откройте parent.component.ts и создайте объект с именем "data", который мы будем передавать через всю иерархию компонентов всем дочерним элементам и реализуем некоторые методы для изменения этого >данные объекта.

Мы создали объект data с prop1, представляющим строку, и prop2, представляющим собой числовой массив.

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

Помните, что changeObject() не просто изменяет свойство, но создает ссылку на совершенно новый объект. Это важно для понимания обнаружения изменений.

Перейдите на страницу parent.component.html и добавьте код шаблона:

Второй шаг — создать ветки для BlockA — › ChildA и примерно то же самое для BlockB — › ChildB. Я покажу вам код только для ветки А, просто чтобы сэкономить ваше время 🕒. (см. ссылку на полный код проекта в конце)

Код для block-a.component.html:

И block-a.component.ts:

Что здесь делается?

BlockAComponent получает объект data через свойство Input(). Визуализирует объект в шаблоне с {{ data | json }}привязка. Он также регистрирует событие при изменении привязок (в нашем случае при изменении свойства данных) и когда для этого компонента выполняется изменение обнаружения изменений.

Сделайте почти то же самое для BlockBComponent, просто измените строки в журнале шаблона и консоли.

Пришло время внести изменения в ChildAComponent. Откройте child-a.component.html:

И очень похожий код здесь:

Дочерний компонент выполняет почти ту же работу, что и блочный компонент. Разница лишь в том, что блок содержит дочерний элемент.

Выполните те же операции для ChildBComponent.

3. Как односторонняя привязка работает с объектами

Теперь запустите приложение и нажмите кнопки. 🌟 Смотрите логи консоли.

ng serve -o

Что мы видим? Даже если бы мы использовали синтаксис односторонней привязки,

<app-child-a [data]="data"></app-child-a>

ссылка на объект является общей.

Это означает, что независимо от того, какой компонент изменяет свойства объекта data, он меняется везде.

Когда вы нажимаете кнопку «ChangeString» и изменяете data.prop1, вы видите в журнале консоли только сообщения «check», а не «change». Почему?

Это происходит потому, что свойство Input() связывает ссылку на объект, которая остается неизменной. Изменяются только значения свойств объекта.

Если вы нажмете кнопку «ChangeObject», появятся журналы «change». Теперь вызывается метод ngOnChanges().

4. Хорошо это или плохо?

Иногда мы можем использовать это с пользой, но в большинстве случаев баги с изменением свойств объекта где-то в дереве компонентов трудно отловить. 🐛

Более того, проверки обнаружения изменений выполняются сверху вниз по дереву. Это может привести к множественным проверкам с глубоко вложенными иерархиями.

Решение?

5. Стратегия обнаружения изменений OnPush

Мы можем изменить одну строку, чтобы одна ветвь вел себя по-другому. Все, что нам нужно сделать, это установить для ChangeDecectionStrategy значение OnPush.

Это делается в аннотации данных компонента перед классом.

Что изменилось?

Теперь при нажатии кнопки «ChangeString» в ветке A ничего не меняется. Объекты данных BlockA и ChildA не изменились.

Но при нажатии кнопки «ChangeObject» данные меняются везде.

Вывод. Обычно, когда мы связываем объекты и передаем их через свойства Input(), компоненты используют одну и ту же ссылку на объект. Любые изменения свойств объекта будут отражены в каждом компоненте.

Когда мы меняем стратегию обнаружения изменений компонента на OnPush, мы сообщаем Angular выполнять обнаружение изменений только тогда, когда Input() получает новую ссылку на объект. То же самое для простых типов, таких как строка, число, логическое значение.

См. код здесь.

🚀 Если вы прочитали что-то интересное из этой статьи, ставьте лайк и подписывайтесь на меня, чтобы не пропустить новые публикации. Спасибо NgReader! 😏