February 26

Angular 17: model()

Новое дополнение к Angular - это функция model, которая улучшает двустороннее связывание данных с использованием сигналов. Эта функция упрощает управление данными, предоставляя записываемые сигналы, доступные через пары ввода/вывода в директивах и компонентах.

Двустороннее связывание с сигналом

Использование функции model() в Angular открывает дверь к безупречным возможностям двустороннего связывания, особенно когда в работе используются сигналы. Давайте проиллюстрируем это на практическом примере с компонентом пагинации и потребителем данных:

@Component({
  selector: 'app-pagination',
  standalone: true,
  template: `{{ page() }}`,
})
export class PaginationComponent {
  page = model(1);
}


В данном сценарии компонент PaginationComponent предоставляет модель страницы с помощью функции model. По умолчанию устанавливается значение 1.

@Component({
  selector: 'app-todos',
  standalone: true,
  template: `
   <app-pagination [(page)]="currentPage" />
  `,
})
export class TodosComponent {
  currentPage = signal(1);
}

Компонент TodosComponent включает в себя компонент PaginationComponent и привязывает его сигнал currentPage к модели страницы компонента PaginationComponentс использованием синтаксиса banana-in-a-box ([(page)]="currentPage").

Замечательно то, что здесь происходит двусторонняя синхронизация между сигналом страницы компонента PaginationComponent и сигналом currentPage компонента TodosComponent. Когда один из сигналов обновляется, Angular гарантирует, что соответствующее значение в другом компоненте также обновляется.

Двунаправленная связка данных

Функция model()легко интегрируется с несигнальными значениями, сохраняя привычный опыт двусторонней привязки:

@Component({
  selector: 'app-todos',
  standalone: true,
  template: `
   <app-pagination [(page)]="currentPage" />
  `,
})
export class TodosComponent {
  currentPage = 1;
}

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

Еще о двунаправленной связке

Одной из интересных особенностей является его совместимость с пользовательскими реализациями двусторонней привязки. Рассмотрим следующий пример:

@Component({
  selector: 'app-pagination',
  standalone: true,
  template: `{{ page }}`,
})
export class PaginationComponent {
  @Input() page = 1;
  @Output() pageChange = new EventEmitter<number>();
}
@Component({
  selector: 'app-todos',
  standalone: true,
  template: `
   <app-pagination [(page)]="currentPage" />
  `,
})
export class TodosComponent {
  currentPage = signal(1);
}


В этой настройке PaginationComponent использует настраиваемую двустороннюю привязку с page в качестве входного и pageChange в качестве выходного сигнала. Несмотря на этот настраиваемый подход, Angular без проблем интегрируется с ним. Сигнал currentPage в TodosComponent без проблем связывается с свойством page в PaginationComponent, обеспечивая двунаправленный поток данных.

Однонаправленное связывание данных с model()

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

@Component({
  selector: 'app-todos',
  standalone: true,
  template: `
   <app-pagination [page]="currentPage" />
  `,
})
export class TodosComponent {
  currentPage = 1;
}


Реагирование на изменения модели

Для реагирования на изменения модели подпишитесь на соответствующее событие, которое генерируется компонентом. Название события следует конвенции, где оно производится из имени свойства модели с добавлением постфикса "Change". Например, если ваше свойство модели называется page, соответствующее событие будет называться pageChange:

@Component({
  selector: 'app-todos',
  standalone: true,
  template: `
   <app-pagination [page]="currentPage" 
                   (pageChange)="onChange($event)" />
  `,
})
export class TodosComponent {
  currentPage = 1;

  onChange(page: number) {}
}

Использование псевдонимов и обязательных параметров

Подобно Signal Inputs, функция model()поддерживает расширенные параметры, такие как псевдоним и обязательный параметр:

@Component({
  selector: 'app-pagination',
  standalone: true,
  template: `{{ page() }}`,
})
export class PaginationComponent {
  page = model(1, { alias: 'currentPage' });
  id = model.required<string>();
}
@Component({
  selector: 'app-todos',
  standalone: true,
  template: `
   <app-pagination [(currentPage)]="currentPage" />

   <app-pagination [currentPage]="currentPage()" 
   (currentPageChange)="onChange()" />
  `,
})
export class TodosComponent {
  currentPage = signal(1);
}

Обновление моделей и директив

@Component({
  selector: 'app-select',
  standalone: true,
  templateUrl: './select.component.html',
})
export class SelectComponent {
  placeholder = model('Select an option');
  options = model<string[]>([]);
}
@Directive({
  selector: '[appUsersOptions]',
  standalone: true,
})
export class UsersOptionsDirective {
  select = inject(SelectComponent);

  ngOnInit() {
    this.select.placeholder.set('Select a user');

    setTimeout(() => {
      this.select.options.set(['John', 'Jane', 'Doe']);
    }, 1000);
  }
}

В компоненте SelectComponent используется функция model для определения заполнителя и массива опций. Тем временем, директива UsersOptionsDirective устанавливает плейсхолдер в "Выберите пользователя" и обновляет массив опций после небольшой задержки.

Сравнение функций model() и input()

В Angular функции input() и model() используются для определения входных сигналов на основе сигналов, но они имеют различные характеристики:

Определение вывода
Функция model() устанавливает как вход, так и выход.

Тип сигнала
ModelSignal, используемый функцией model(), является WritableSignal, позволяющим изменять значения с помощью методов set и update из любого места. В отличие от этого, InputSignal, используемый функцией input(), доступен только для чтения и может быть изменен только через шаблон.

Преобразование входных данных
Модельные входы не поддерживают преобразование входных данных, что возможно с сигнальными входами.

Это перевод оригинального материала