Подорож Angular: Частина 2 — Прив’язки, Директиви, Компоненти, Сервіси

pic

Фото Bill Craighead на Unsplash

Необхідні умови

  • Переконайтеся, що у вас встановлено Node.js версії 18.3+
  • Необхідно створити додаток Vue, слідуючи цьому посібнику.

Вступ

У Vue ми можемо використовувати прив'язку класів для динамічного додавання CSS класів залежно від умов.

Масив класів

Використовуйте, коли у вас є список класів, які потрібно застосувати залежно від умов.

У шаблоні

pic

У рядку 2 ви можете побачити, що textWhite та bgBlue є станами, тому ви можете динамічно встановлювати CSS класи як масив.

У скрипті

pic

Нічого особливого, просто оголошуємо стани, що зберігають імена CSS класів.

У стилях

pic

Оголошуємо класи, які будуть викликатися з шаблону.

Об'єкт класів

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

У шаблоні

pic

З рядків з 3 по 6 ми використовуємо об'єкт класів для динамічного встановлення класів залежно від логічного значення isOnline.

У скрипті

pic

Оголошуємо стан isOnline.

У стилях

pic

Оголошуємо класи, які будуть викликатися з шаблону.

Запуск проекту

Вам потрібно перевірити, які класи додаються та видаляються, використовуючи інспектор елементів.

pic

Готово

Використовуйте Масив класів, якщо хочете керувати класами за допомогою значень стану. Використовуйте Об'єкт класів, якщо хочете перемикати класи за допомогою логічних значень.

Попереднє: Слоти

Наступне:
Зараз давайте додамо файл з даними нотаток до порожнього компонента так само, як ми робили для компонента списку:

import { notes as notesData } from '../data';  

@Component({...});  
export class EmptyComponent {  
 notes = notesData;  
}

Тепер у файлі порожнього компонента .html ми зробимо так, щоб повідомлення про відсутність нотаток з'являлося умовно, якщо довжина списку нотаток дорівнює нулю, тоді ми покажемо повідомлення, в іншому випадку — ні. empty.component.html:

    No notes found   
 ```  Тут ми додали вбудовану директиву Angular *ngIf, яка допомагає писати такі умовні логіки.

## Комунікація між компонентами (Батьківський-дитячий: Input)

Компонент елемента наразі не відображає правильні дані. Давайте зробимо його динамічним, щоб отримувати об'єкт нотатки, переданий з компонента списку. Тут ми говоримо про комунікацію між батьківським компонентом (список) та дочірнім компонентом (елемент). Щоб передати значення в дочірній компонент, нам потрібно створити властивість у дочірньому компоненті з декоратором (@Input), item.component.html: 

import { Component, Input } from '@angular/core';
import { Note } from '../../models/note.model';
@Component({...})
export class ItemComponent {
@Input() note: Note;
}
```
Тут ми створили властивість note типу Note з декоратором @Input. Тепер настав час передати значення властивості note в дочірній компонент з батьківського компонента, list.component.html:

Тут ми робимо прив'язку властивості, використовуючи квадратні дужки з тим самим ім'ям, яке є в компоненті елемента. Додатково, ми можемо зробити параметр введення обов'язковим, що також дасть помилку при використанні дочірнього компонента в майбутніх батьківських компонентах. Для цього оновимо декоратор @Input в item.component.ts:

import { Component, Input } from '@angular/core';   
import { Note } from '../../models/note.model';      
@Component({...})   
export class ItemComponent {    
 @Input({ required: true }) note!: Note;   
} 

Тут ми застосували оператор ! до властивості note, вказуючи, що вона не може бути null.

Одностороння прив'язка та прив'язка властивостей

Це дозволяє значенню нотатки потрапити в компонент елемента, але його ще не використовують. Оновимо файл item.component.html:


 {{note.content}}  

Тут, щоб використовувати значення властивості у файлі шаблону .html, що називається односторонньою прив'язкою (інтерполяцією), ми використовуємо синтаксис з подвійними фігурними дужками ({{property}}). Властивість ми вже бачили раніше у файлі list.component.html:

Комунікація між компонентами (Сервіси)

Повертаючись до файлу data.ts, який наразі є статичним джерелом даних і незабаром не буде використовуватись у серії статей, давайте створимо сервіс для обміну даними між компонентами, що виходять за межі батьківсько-дитячих відносин. Для цього в Angular є концепція сервісів. Сервіси — це інжектовані класи, які доступні глобально і мають безліч варіантів використання в Angular. Зараз ми обмежимо використання сервісу як спільної одиниці між компонентами. Ми побачимо більше варіантів використання сервісів у цій серії статей про Angular.
Щоб створити сервіс, ми виконуємо наступну команду в новій папці "services":

ng g s note

Ми отримуємо сервіс, що виглядає ось так, note.service.ts:

import { Injectable } from '@angular/core';  
import { notes as notesData } from '../data';  

@Injectable({  
 providedIn: 'root'  
})  
export class NoteService {  
 notes = notesData;  

 constructor() { }  
}

Тут ми додали дані нотаток з файлу data.ts. Тепер давайте використовувати це у файлі list.component.ts:

import { Component } from '@angular/core';  
import { Note } from '../../models/note.model';  
import { NoteService } from '../../services/note.service';  

@Component({...})  
export class ListComponent {  
 constructor(private noteService: NoteService) {}  

 get notes() {  
 return this.noteService.notes;  
 }  

 set notes(value: Note[]) {  
 this.noteService.notes = value;  
 }  
}

Тут ми інжектуємо NoteService в конструктор компонента списку. Також ми створили гетер і сетер для подальшого використання.

Тепер давайте використовувати порожній компонент і додамо note service замість коду з файлу data.ts у файл empty.component.ts:

import { Component } from '@angular/core';  
import { NoteService } from '../../services/note.service';  

@Component({...})  
export class EmptyComponent {  
 constructor(private noteService: NoteService) {}  

 get notes() {  
 return this.noteService.notes;  
 }  
}

Це привертає нашу увагу до форми. Тепер давайте додамо note service також до компонента форми, адже це буде використовуватись у файлі form.component.ts:

import { Component } from '@angular/core';  
import { NoteService } from '../../services/note.service';  

@Component({...})  
export class FormComponent {  
 constructor(private noteService: NoteService) {}  
}

Прив'язка подій

У компоненті форми ми тепер додаємо подію кліку до кнопки додавання, файл form.component.html виглядатиме так:


 ```  Додаємо необхідний метод "onAdd" у файл form.component.ts:

import { Component } from '@angular/core';
import { NoteService } from '../../services/note.service';
import { Note } from '../../models/note.model';
@Component({...})
export class FormComponent {
constructor(private noteService: NoteService) {}
onAdd(event: Event) {
const newNote: Note = {
id: 1000,
content: 'New Note',
};
this.noteService.notes.push(newNote);
}
}
```
Тут ми додаємо нову нотатку до властивості сервісу нотаток.

Двостороння прив'язка

Це круто, але недостатньо. Давайте щось зробимо з id та контентом. Для id ми будемо використовувати пакет uuid з NPM, щоб завжди отримувати новий id.
Виконайте наступну команду для установки uuid:

npm i uuid

Використовуємо uuid для нової нотатки, як показано нижче:

import { Component } from '@angular/core';  
import { NoteService } from '../../services/note.service';  
import { Note } from '../../models/note.model';  
import { v4 as uuid } from 'uuid';  

@Component({...})  
export class FormComponent {  
 constructor(private noteService: NoteService) {}  

 onAdd(event: Event) {  
 const newNote: Note = {  
 id: uuid(),  
 content: 'New Note',  
 };  

 this.noteService.notes.push(newNote);  
 }  
}

Щоб отримати фактичний вміст нотатки, введений у текстове поле, ми будемо використовувати синтаксис двосторонньої прив'язки Angular за допомогою директиви ngModel у файлі form.component.html:


 ```  Але нам також потрібно створити властивість content у файлі form.component.ts:

import { Component } from '@angular/core';
import { NoteService } from '../../services/note.service';
import { Note } from '../../models/note.model';
import { v4 as uuid } from 'uuid';
@Component({...})
export class FormComponent {
content: string = '';
constructor(private noteService: NoteService) {}
onAdd(event: Event) {
const newNote: Note = {
id: uuid(),
content: this.content,
};
this.noteService.notes.push(newNote);
}
}
```

Комунікація між компонентами (Батьківський-дитячий: Output)

Останнє, але не менш важливе, давайте розглянемо видалення нотаток за допомогою кнопки видалення в файлі item.component.html:



 {{note.content}}  





 ```  Тут ми додали метод remove і зв'язали його з подією кліку. Створюємо метод remove у файлі form.component.ts:

import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Note } from '../../models/note.model';

@Component({...})
export class ItemComponent {
@Input({ required: true }) note!: Note;
@Output() onRemove = new EventEmitter();

remove() {
this.onRemove.emit(this.note);
}
}
```

Тут ми додали декоратор @Output для події onRemove, яка є еміттером подій (EventEmitter).
Коли дочірній компонент хоче сповістити батьківський компонент про подію, використовуємо клас EventEmitter, який можна далі споживати у файлі list.component.html:


 ```  У файлі list.component.ts:

import { Component } from '@angular/core';
import { Note } from '../../models/note.model';
import { NoteService } from '../../services/note.service';

@Component({...})
export class ListComponent {
onRemove(note: Note) {
this.notes = this.notes.filter((n) => n.id !== note.id);
}
}
```

Тут метод onRemove компонента List буде викликаний через подію видалення з дочірнього компонента.

Валідація та обробка помилок

Давайте додамо основну логіку валідації в метод onAdd, у файлі form.component.ts:

onAdd(event: Event) {    
 event.preventDefault();       

 if (!this.content) {    
 alert('Please fill the form');    
 return;    
 }       

 const newNote: Note = {    
 id: uuid(),    
 content: this.content,    
 };       

 this.noteService.notes.push(newNote);    
} 

Ми також додали event.preventDefault(), оскільки ми знаходимося всередині елемента форми, і для того, щоб уникнути перезавантаження сторінки, це необхідно, решта — стандартна логіка валідації.

Тепер додаємо блок try-catch для більш стійкого та безпомилкового коду:

onAdd(event: Event) {    
 try {    
 event.preventDefault();       

 if (!this.content) {    
 alert('Please fill the form');    
 return;    
 }       

 const newNote: Note = {    
 id: uuid(),    
 content: this.content,    
 };       

 this.noteService.notes.push(newNote);       

 } catch (error: any) {    
 alert(`Error Occurred: ${error.message}`);    
 } finally {    
 this.content = '';    
 }   
} 

Нарешті, щоб нові нотатки з'являлися зверху, ми просто модифікуємо масив *ngFor:

 ```  Тут ми додали метод reverse() масиву, щоб показати останню нотатку вгорі.

## Репозиторій Git

Перевірте git-репозиторій для цього проєкту або завантажте код.

[Завантажити код](https://github.com/ZakiMohammed/ng-notsey-app/archive/refs/heads/master.zip)

[Git репозиторій](https://github.com/ZakiMohammed/ng-notsey-app)

## Підсумки

Отже, ми маємо повністю функціональний клієнтський додаток Notesy, який працює як треба. У наступних статтях ми розглянемо, як працювати з реальними API-запитами в Angular-додатках. Поступово ми наближаємося до створення реального SPA. Основні моменти цієї статті: прив'язка даних, комунікація між компонентами та сервіси.

Сподіваюсь, ця стаття була корисною.

_Оригінал опублікований на_ [_https://codeomelet.com_](https://codeomelet.com/posts/ng-journey-part-2-bindings-directives-components-services)_._



Перекладено з: [Angular Journey: Part 2 — Bindings, Directives, Components, Services](https://zakimohammed.medium.com/angular-journey-part-2-bindings-directives-components-services-2223448b6d5a)

Leave a Reply

Your email address will not be published. Required fields are marked *