Новые хуки afterNextRender и afterRender в Angular
В рамках работ над улучшением фреймворка Angular, команда Angular представила два новых хука. Эти хуки будут особенно полезны, когда нам нужно обновлять DOM с учетом нашего состояния, но только при выполнении в браузере.
Иногда требуется использовать API браузера для ручного чтения или записи DOM. Однако это может быть сложно сделать с использованием событий жизненного цикла компонентов, так как они также выполняются во время серверного рендеринга и предварительного рендеринга. Для этих целей Angular предоставляет два новых хука: afterRender
и afterNextRender
. Эти функции могут быть использованы без ограничений, однако они будут работать только в браузере. Обе функции принимают колбек, который будет выполнен после завершения следующего цикла обнаружения изменений (включая любые вложенные циклы).
afterNextRender
Это событие срабатывает один раз после следующего цикла обнаружения изменений, запущенного Angular. Это делает это событие идеальным для любого инициализационного кода, который вам может понадобиться в вашем приложении, например, для интеграции сторонних библиотек или API, доступных только в браузере (например, инициализация API ResizeObserver
).
afterRender
Каждый раз, когда запускается цикл обнаружения изменений, это событие будет выполнено. Это идеально подходит для обновления DOM каждый раз, когда Angular обнаруживает изменение. Вы можете использовать его для выполнения дополнительных записей в DOM (или чтения из него) на основе обновления приложения.
Небольшое демо
Вы можете попробовать игру здесь: ссылка на игру и просмотреть код на GitHub ссылка на GitHub.
Цель этой игры - переместить красную точку по доске, пока вы не коснетесь синей точки, которая (удивление!) перемещается в другое место.
Мы используем HostListener
для отслеживания события keydown
на странице, а затем перемещаем красную точку. Если обновленные координаты красной точки совпадают с координатами синей точки, то мы также обновляем ее позицию.
@HostListener('document:keydown', ['$event']) onKeydown( event: KeyboardEvent, ) { switch (event.key) { case 'ArrowLeft': // left this.moveHorizontal(-PIXELS_MOVEMENT); break; case 'ArrowUp': // up this.moveVertical(-PIXELS_MOVEMENT); break; case 'ArrowRight': // right this.moveHorizontal(PIXELS_MOVEMENT); break; case 'ArrowDown': // down this.moveVertical(PIXELS_MOVEMENT); break; } }
Мы используем WritableSignal<number>
для хранения счета и обновляем DOM каждый раз, когда его значение обновляется. Позиция красной и синей точек также хранится в WritableSignal<[number, number]>
, которые представляют собой координаты x
и y
доски.
position = signal<[number, number]>([0, 0]); positionPointToTouch = signal<[number, number]>([ CONTAINER_SIZE - POINT_SIZE, CONTAINER_SIZE - POINT_SIZE, ]); score = signal<number>(0);
У нас есть два метода для обновления очков и один для проверки, касается ли красная точка синей точки:
private movePoint(axis: number, amount: number, maxAxisPosition: number) {...} private movePointToTouch() {...} private hasTouchedPoint() {...}
movePoint
обрабатывает обновление позиции красной точки с новыми координатами.
movePointToTouch
обрабатывает обновление позиции синей точки с новыми координатами.
hasTouchedPoint
- метод для проверки совпадения координат позиций красной и синей точек, а затем вызывает метод для перемещения синей точки.
Теперь обновим DOM используя afterRender
Поскольку сигналы обновляются, они запускают цикл обнаружения изменений в Angular, который затем запускает событие afterRender
. Мы можем воспользоваться этим, вызывая методы, которые рисуют красную и синюю точки на доске каждый раз, когда изменение было обработано:
constructor() { afterRender(() => { this.drawPoint(); this.drawPointToTouch(); }); ... }
private drawPoint() { const [x, y] = this.position(); this.point.nativeElement.setAttribute( 'style', `transform: translateY(${y}px) translateX(${x}px)`, ); } private drawPointToTouch() { const [x, y] = this.positionPointToTouch(); this.pointToTouch.nativeElement.setAttribute( 'style', `transform: translateY(${y}px) translateX(${x}px)`, ); }
Мы используем afterNextRender
для запуска метода initBoard
, который просто незначительно обновляет DOM. Это идеальное место, если мы хотим изменить наш DOM один раз.
constructor() { ... afterNextRender(() => { this.drawInitBoard(); }); }
private drawInitBoard() { this.playingArea.nativeElement.setAttribute( 'style', `border:3px solid #dedede;`, ); const instructions = document.createElement('div'); instructions.innerHTML = `<p style="font-weight: bolder; font-size:1.2rem;">Using your arrow keys, move the red dot to catch the blue one</p>`; this.playingArea.nativeElement.parentNode.append(instructions); }
Как видите, счет обновляется с помощью механизма реактивности Angular Change Detection
через сигналы, а позиция точек обрабатывается событием afterRender
.
Ошибка ExpressionChangedAfterItHasBeenCheckedError
может возникнуть, если вы пытаетесь обновить любую переменную в событии afterRender
. Поэтому это событие следует использовать только для дополнительных чтений и записей в DOM, а не для обновления состояния или чего-либо еще.
Обновление DOM становится более простым благодаря использованию afterRender.
Эти новые хуки afterRender
и afterNextRender
предоставляют много возможностей для наших приложений. Важно помнить, что afterRender
и afterNextRender
находятся в стадии предварительной разработки, и их API может измениться.
Это вольный перевод оригинального материала