Skip to main content

Прив'язка даних в Angular

Прив'язка даних (data binding) в Angular автоматично синхронізує TypeScript-властивості компонента з HTML-шаблоном. Руками DOM чіпати не потрібно.

Теорія

TL;DR

  • Як живе табло: компонент передає дані в шаблон, а дії користувача повертають події назад
  • Чотири типи: інтерполяція {{ }}, прив'язка властивостей [prop], прив'язка подій (event), двостороннє зв'язування [(ngModel)]
  • За замовчуванням напрямок односторонній (компонент до шаблону), так легше відслідковувати стан
  • [(ngModel)] використовуй тільки для полів форм, не як загальний підхід

Швидкий приклад

typescript
@Component({ template: ` <p>{{ title }}</p> // інтерполяція: відображає текст <img [src]="imageUrl"> // прив'язка властивості: встановлює DOM-властивість <button (click)="onClick()">Go</button> // прив'язка події: викликає метод при кліку <input [(ngModel)]="name"> // двостороннє: поле і властивість синхронізовані <p>Ти написав: {{ name }}</p> ` }) export class AppComponent { title = 'Hello'; imageUrl = 'https://example.com/logo.png'; name = ''; onClick() { console.log('Clicked!'); } }

Шаблон читає безпосередньо з властивостей компонента. Змінився title у класі - <p> оновиться при наступному циклі change detection.

Чотири типи прив'язки

Інтерполяція {{ вираз }} перетворює значення на рядок і вставляє його в DOM. Підходить для текстового вмісту, але не для DOM-властивостей.

Прив'язка властивостей [property]="value" встановлює DOM-властивість напряму, а не HTML-атрибут. Різниця важлива: [src]="imageUrl" встановлює img.src (DOM-властивість). src="{{ imageUrl }}" встановлює HTML-атрибут і чекає, поки браузер синхронізує його. Для простих рядків обидва варіанти схожі, але [disabled]="isLoading" правильно працює тільки як прив'язка властивостей.

Прив'язка подій (event)="handler($event)" слухає DOM-події і запускає метод компонента. Об'єкт $event дає доступ до нативної події. Ніякого addEventListener в TypeScript, ніякого inline-JS у шаблоні.

Двостороннє зв'язування [(ngModel)]="prop" - скорочення для [ngModel]="prop" (ngModelChange)="prop = $event". Квадратні дужки встановлюють значення, круглі слухають зміни. Потребує FormsModule в імпортах модуля.

Коли що використовувати

СитуаціяТип прив'язки
Відобразити текст або обчислене значення{{ value }}
Встановити атрибути, класи, стан disabled[property]="value"
Обробити кліки, введення, submit форми(event)="method()"
Поле форми з двостороньою синхронізацією[(ngModel)]="prop"

Для складного стану відмовся від [(ngModel)] і використовуй прив'язку подій разом з прив'язкою властивостей окремо. Дані течуть в одному напрямку і легше відстежуються.

Як Angular запускає це

Компілятор Angular (AOT) перетворює шаблон на інструкції для change detection під час збірки. Zone.js патчить браузерні API типу addEventListener і setTimeout. Коли спрацьовує подія, Angular проходить по дереву компонентів і оновлює тільки ті прив'язки, що змінились. Зі стратегією OnPush пропускаються компоненти, чиї inputs не змінились, що прискорює роботу у великих деревах.

Типові помилки

Помилка 1: Інтерполяція замість прив'язки властивостей

html
<!-- Встановлює HTML-атрибут, а не DOM-властивість --> <img src="{{ imageUrl }}"> <!-- Встановлює img.src напряму --> <img [src]="imageUrl">

Для динамічних значень, які не є простими рядками, варіант через атрибут тихо ламається. Для DOM-властивостей завжди використовуй прив'язку властивостей.

Помилка 2: Забути FormsModule

Error: Can't bind to 'ngModel' since it isn't a known property of 'input'.

Додай FormsModule до масиву imports в @NgModule. У standalone-компонентах - імпортуй безпосередньо в декоратор компонента.

Помилка 3: Виклик методів в шаблоні

html
<!-- Викликається при кожному циклі change detection --> <div>{{ getUserName() }}</div> <!-- Прив'яжи до властивості --> <div>{{ userName }}</div>

Це, мабуть, найпоширеніша проблема продуктивності, яку я бачу в Angular-проектах. Angular запускає change detection багато разів за секунду під час активної взаємодії. Метод у шаблоні виконується при кожному циклі.

Помилка 4: Двостороннє зв'язування на не-input елементах

html
<!-- ngModel працює тільки з елементами форм --> <div [(ngModel)]="data">Контент</div>

Для кастомних елементів використовуй явний патерн: [value]="data" (input)="data = $event.target.value".

Де зустрічається в реальних проектах

  • Angular Material: [disabled]="isLoading" на кнопках під час запиту до API
  • Форми авторизації: [(ngModel)] для простих полів, [formControl] для реактивних форм
  • Таблиці: (selectionChange)="onSelect($event)" на компонентах вибору
  • Динамічні стилі: [class.active]="isSelected", [style.color]="textColor"

Питання на співбесіді

Q: В чому різниця між [src] і [attr.src]?
A: [src] встановлює DOM-властивість. [attr.src] встановлює HTML-атрибут. За замовчуванням використовуй прив'язку властивостей. Прив'язка атрибутів потрібна для ARIA-атрибутів типу [attr.aria-label], у яких немає відповідної DOM-властивості.

Q: Як [(ngModel)] працює всередині?
A: Розгортається в [ngModel]="value" (ngModelChange)="value = $event". Частина [] встановлює властивість, частина () слухає зміни. Цей самий патерн можна використовувати для побудови двостороннього зв'язування у власних компонентах.

Q: Чому виклик методу в шаблоні шкодить продуктивності?
A: Change detection запускається при кожній асинхронній події. Виклик методу в шаблоні виконується при кожному циклі. Обчислюй значення в компоненті і прив'язуй готовий результат.

Q: Чому уникають двостороннього зв'язування у великих застосунках?
A: Воно створює двонаправлений потік даних, який важко відстежити. Кращий патерн - однонаправлений: дані вниз через прив'язку властивостей, події вгору через прив'язку подій. Саме так влаштовані NgRx та інші менеджери стану.

Приклади

Базовий: Одностороннє відображення даних

typescript
@Component({ template: `<h2>{{ hero.name }} (ID: {{ hero.id }})</h2>` }) export class HeroComponent { hero = { id: 1, name: 'Windstorm' }; // Відображає: Windstorm (ID: 1) }

Інтерполяція читає з об'єкта hero при кожному циклі change detection. Зміни в hero.name відображаться в шаблоні автоматично.

Середній: Todo-список з усіма чотирма типами прив'язки

typescript
@Component({ template: ` <ul> <li *ngFor="let todo of todos" [class.completed]="todo.done" (click)="toggle(todo)"> {{ todo.text }} </li> </ul> <input [(ngModel)]="newTodo" (keyup.enter)="add()"> ` }) export class TodoComponent { todos = [{ text: 'Купити молоко', done: false }]; newTodo = ''; add() { this.todos.push({ text: this.newTodo, done: false }); this.newTodo = ''; } toggle(todo: { text: string; done: boolean }) { todo.done = !todo.done; } }

Усі чотири типи в одному компоненті. {{ todo.text }} відображає текст. [class.completed] встановлює CSS-клас на основі булевого значення. (click) обробляє перемикання. [(ngModel)] тримає поле в синхронізації з newTodo. Не забудь додати FormsModule до імпортів модуля.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?