УГЛОВОЙ

Пример угловых сигналов — сигнал, вычисление, эффект и методы

Angular 16 вводит реактивность через сигналы. Вот пример сигналов Angular, включающий все реактивные примитивы: сигнал, вычисление и эффект.

Angular 16 вводит реактивность через сигналы.

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

Пример угловых сигналов

В следующем примере сигналы используются для построения интерактивного прямоугольника, площадь которого со временем увеличивается.

Сигнал

Сигнал — это значение, которое сообщает Angular, когда оно изменяется.

Объявите сигналы высоты и ширины в классе компонента.

Стандартный синтаксис: propertyName = signal(value), где signal — это ключевое слово для идентификации значения сигнала, а value — любое значение, которое вы хотите присвоить свойству сигнала.

// AppComponent

import { Component, signal} from '@angular/core';

export class AppComponent implements OnInit {
  height = signal<number>(20);
  width = signal<number>(100);
  
  ...
}

Приведенный выше код определяет два сигнала: height и width и дает им начальные значения 20 и 100.

Мы можем вычислить площадь прямоугольника по ширине и высоте. Поэтому мы объявляем другое свойство и называем его area.

Это подводит нас ко второму реактивному примитиву: вычисляемому.

Вычислено

Вычислено выводит новое значение, когда любой из Сигналов, от которого он зависит, изменяется.

Другими словами, вычисленный — это сигнал, который получает реактивное значение из выражения.

Стандартный синтаксис — propertyName = computed(expression), где computed — это ключевое слово для идентификации значения сигнала, полученного из выражения. expression — это выражение, которое мы передаем в вычисление для получения значения сигнала.

// AppComponent

import { Component, signal, computed} from '@angular/core';

export class AppComponent implements OnInit {
  height = signal<number>(20);
  width = signal<number>(100);
  area = computed<number>(() => this.width() * this.height());
  
  ...
}

Приведенный выше код определяет вычисляемый сигнал area, который является результатом height умножения на width.

Поскольку сигналы уведомляют Angular об изменении height или width, все значения, вычисленные из height или width, будут автоматически обновляться.

Это ключевое различие между сигналами и обычными свойствами!

Начальное значение area равно 2000, как для сигналов, так и для обычных свойств.

Если бы мы изменили значение height или width, значение area изменилось бы автоматически только тогда, когда area является вычисляемым сигналом.

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

Это именно то, что делают сигналы. Они делают вещи более реактивными.

Эффект

Эффект — это побочная операция, которая считывает значение Сигналов. Некоторые из причин, по которым вы можете захотеть использовать эффекты, упомянуты в видео и приведены ниже:

  • запуск сетевых запросов
  • выполнение действий рендеринга
  • чтение или изменение DOM вручную

Сначала рассмотрим простой пример. Мы будем использовать effect для чтения сигнала при его изменении. Не забудьте импортировать effect из @angular/core, а затем:

// AppComponent - TS

areaChanged = effect(() => console.log(`Area changed ${this.area()}`));

Приведенный выше код определяет эффект areaChanged, которому мы передаем функцию, которая выполняется каждый раз при изменении area. Это декларативный подход, но мы могли бы использовать эффект отдельно в конструкторе следующим образом:

// AppComponent - TS

constructor() {
    effect(() => console.log(`Areaaa changed ${this.area()}`));
  }

Благодаря эффектам вы можете вызывать другие функции и выполнять код при изменении сигнала.

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

Управление сигналами

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

Чтение сигналов

Как правило, не имеет значения, хотите ли вы прочитать сигнал в шаблоне или в классе, синтаксис один и тот же:

signalName()

С технической точки зрения вы читаете функцию получения, которую Angular создал при создании свойства сигнала.

Итак, читаем значение сигналов в шаблоне следующим образом:

// AppComponent - HTML

<h2>Build a rectangle</h2>
<h3>Height: {{ height() }}, width: {{ width() }}</h3>
<h3>Area: {{ area() }}</h3>

Внутри класса мы уже считываем значения сигналов, выполнив

// AppComponent - TS

area = computed<number>(() => this.width() * this.height());

Управление сигналами — установить

Метод set() непосредственно устанавливает новое значение сигнала и уведомляет всех зависимых элементов.

Просто set() изменяет значение сигнала на новое значение.

Если мы хотим изменить значение height или width, нам нужно использовать set следующим образом:

// AppComponent - TS

this.height.set(this.height() + 1);

Когда мы устанавливаем новое значение для сигнала, сигнал уведомляет все сигналы, которые зависят от него. Следуя нашему примеру, area и areaChanged будут отражать изменения.

Для справки:

// AppComponent - TS

area = computed<number>(() => this.width() * this.height());
areaChanged = effect(() => console.log(`Area changed ${this.area()}`));

Обратите внимание, что даже область в шаблоне будет отражать новое значение!

// AppComponent - HTML

<h2>Build a rectangle</h2>
<h3>Height: {{ height() }}, width: {{ width() }}</h3>
<h3>Area: {{ area() }}</h3>

Управление сигналами — обновление

Метод update() обновляет значение сигнала на основе его текущего значения и уведомляет все зависимые элементы.

Просто update() берет текущее значение сигнала и обновляет его в соответствии с определенной функцией, которую мы передаем.

// AppComponent - TS

this.width.update((w) => w + 5);

Приведенный выше код обновляет значение сигнала width до самого себя плюс 5.

Манипулирование сигналами — мутировать

Метод mutate()обновляет текущее значение, изменяя его на месте, и уведомляет все зависимые элементы.

Метод mutate() используется для изменения значений внутри массива сигналов и объектов. Таким образом, mutate() изменяет не массив или сам объект, а только его элементы.

Это почти все, и далее следует простой пример, объединяющий три новых реактивных примитива: сигнал, вычисляемый и эффект.

Пример сигнала

Этот пример основан на приведенном выше коде и показывает прямоугольник, размер которого со временем увеличивается.

Мы используем height, width и area для отображения значений во времени в любой момент.

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

Наконец, прямоугольник получает значения ширины и высоты из класса с помощью ngStyle.

// AppComponent - HTML

<h2>Build a rectangle</h2>
<h3>Height: {{ height() }}, width: {{ width() }}</h3>
<h3>Area: {{ area() }}</h3>

<button (click)="handleClick()">{{ isActive ? "Stop" : " Start" }}</button>
<hr />
<div
  style="border: 1px solid brown; background-color: rgba(165, 42, 42, 0.427)"
  [ngStyle]="{ 'height.px': height(), width: divWidth() }"
></div>

Примечание об использовании ngStyle. Как видно из приведенного выше кода, стили можно объявлять по-разному.

  • ‘height.px’: height() устанавливает высоту прямоугольника в значение пикселя, возвращаемое сигналом height.
  • width: divWidth() устанавливает ширину прямоугольника на значение, возвращаемое сигналом divWidth. Здесь мне пришлось создать вычисляемый сигнал, чтобы значение divWidth было строкой в ​​виде 5px. Мы увидим это через мгновение.

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

Теперь перейдем к классу.

Во-первых, не забудьте импортировать все примитивы

// AppComponent - TS

import { Component, computed, signal, effect } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent { ... }

В классе мы объявили почти все следующие сигналы и свойства во время поста.

Единственным новым является intervalId, который для простоты имеет тип any, но лучшим типом может быть ReturnType<typeof setInterval>. Если вам нужна дополнительная информация, прочитайте первые несколько ответов на StackOverflow.

// AppComponent - TS

...
export class AppComponent { 
  height = signal<number>(20);
  width = signal<number>(100);
  area = computed<number>(() => this.width() * this.height());
  areaChanged = effect(() => console.log(`Area changed ${this.area()}`));
  isActive: boolean = false;
  divWidth = computed<string>(() => `${this.width()}px`);
  intervalId: any;

  handleClick() { ... }
}

Наконец, функция handleClick() обрабатывает увеличение прямоугольника, запуская и останавливая функцию setInterval.

// AppComponent - TS

...
export class AppComponent { 
  ...

  handleClick() {
    if (this.isActive) {
      clearInterval(this.intervalId);
    } else {
      this.intervalId = setInterval(() => {
        this.height.set(this.height() + 1);
        this.width.update((w) => w + 5);
      }, 1000);
    }

    this.isActive = !this.isActive;
  }
}

См. пример на StackBlitz.

Еще один пример может включать в себя манипулирование значениями сигнала через входные элементы. Увидимся там!