Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Прив'язка даних в Angular». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Прив'язка даних (data binding) в Angular** з'єднує TypeScript-властивості компонента з HTML-шаблоном. Чотири типи: інтерполяція `{{ }}` для тексту, прив'язка властивостей `[prop]` для DOM-властивостей, прив'язка подій `(event)` для дій користувача, двостороннє зв'язування `[(ngModel)]` для синхронізації поля форми і властивості компонента. ```html <input [(ngModel)]="username"> <p>Привіт, {{ username }}!</p> ``` **Ключове:** односторонній напрямок (компонент до шаблону) є типовим. `[(ngModel)]` використовуй тільки для полів форм.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Прив'язка даних (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` до імпортів модуля.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.