Огляд архітектури

Angular це фреймворк для побудови клієнтських застосунків в HTML за допомогою JavaScript або TypeScript (який компілюється в JavaScript).

Фреймворк містить кілька бібліотек, деякі з них є основними, деякі - опціональними.

Ви будуєте Angular-застосунки через компонування HTML-шаблонів з Angular-розміткою, через створення класів компонентів щоб керувати цими шаблонами, через додавання логіки застосунка у сервіси, та через складання компонентів з класами у модулі.

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

Звичайно, є ще багато чого іншого. Про все це розповідається на наступних сторінках. Зараз же, сфокусуйтесь на цій картинці.

overview

Дана діаграма архітектури виділяє вісім головних будівельних блоків Angular-застосунка:

Вивчіть ці блоки, і ви вже будете при ділі.

Код, згаданий на цій сторінці, доступний у живому прикладі.

Модулі

Модуль

Застосунки Angular по своїй архітектурі є модульними, і сам Angular має власну модульну систему, яка називається Angular-модулі або NgModules.

Angular-модулі мають важливе значення. На цій сторінці ви знайдете ознайомлення з ними, а на сторінці Angular-модулі вони розглядаються поглиблено.



Будь-який Angular-застосунок має принаймні один клас Angular-модуля — кореневий модуль — за угодою названий AppModule.

Хоча кореневий модуль може бути єдиним модулем в маленьких застосунках, частіше — він доповнюється так званими функціональними модулями, кожен з яких є згуртованим блоком коду, призначеним для бізнес-логіки, робочого потоку, або тісно пов'язаного набору можливостей.

Чи то кореневий, чи то функціональний модуль Angular — обидва вони є класами з декоратором @NgModule.

Декоратори є функціями, які модифікують JavaScript-класи. Angular має багато декораторів, які закріплюють метадані за класами так, що Angular знає що ті класи означають та як вони повинні працювати. Дізнайтесь більше про декоратори в інтернеті.

NgModule є функцією-декоратором, яка приймає єдиний об'єкт з метаданими, чиї властивості описують модуль. Найбільш важливими властивостями є:

Ось простий кореневий модуль:

app/app.module.ts

import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; @NgModule({ imports: [ BrowserModule ], providers: [ Logger ], declarations: [ AppComponent ], exports: [ AppComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }

У властивість exports передається масив з AppComponent лише для демонстрації можливості експорту; насправді, в даному випадку, такої необхідності немає. З кореневого модуля немає сенсу експортувати будь-який компонент, оскільки інші компоненти не потребують імпорту кореневого модуля.

Початок роботи застосунка проходить через етап початкового завантаження кореневого модуля. Під час розробки ви, ймовірно, додасте AppModule у файл main.ts:

app/main.ts

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app.module'; platformBrowserDynamic().bootstrapModule(AppModule);

Angular-модулі проти JavaScript-модулів

Angular-модуль — клас анотований декоратором @NgModule — є фундаментальною особливістю Angular.

JavaScript також має свою власну систему модулів для управління набором JavaScript-об'єктів. Це повністю інша та непов'язана з Angular-модулями система.

В JavaScript кожен файл є модулем, а усі об'єкти, визначені у цьому файлі, стосуються даного модуля. В модулі оголошуються деякі об'єкти публічними, за допомогою ключового слова export. Інші JavaScript-модулі використовують вираз import для доступу до цих публічних об'єктів.

import { NgModule } from '@angular/core'; import { AppComponent } from './app.component';
export class AppModule { }

Learn more about the JavaScript module system on the web.

Це дві різні та взаємодоповнюючі модульні системи. Використовуйте їх обидві при написанні застосунків.

Бібліотеки Angular

Модуль

Angular представляє собою набір JavaScript-модулів. Про нього можна думати як про бібліотеку модулів.

Кожна назва бібліотеки Angular починається з префіксу @angular.

Їх можна встановлювати за допомогою пакетного менеджера npm, а їхні частини імпортувати за допомогою JavaScript-виразу import.

Наприклад, імпортуємо Angular-декоратор Component з Angular-бібліотеки @angular/core наступним чином:

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

А ось так імпортуємо Angular-модулі з Angular-бібліотеки:

import { BrowserModule } from '@angular/platform-browser';

У верхньому прикладі, модуль застосунка потребує матеріали із BrowserModule. Щоб отримати ці матеріали, додайте їх до метаданих @NgModule масива imports наступним чином:

imports: [ BrowserModule ],

В остнніх двох прикладах використовувались обидві системи модулів: Angular та JavaScript.

Тут досить просто заплутатись, оскільки ці системи використовують однакові ключові слова imports та exports. Але не лякайтесь. Така плутанина досить швидко пройде з досвідом.

Дізнайтесь більше про модулі Angular.

Компоненти

Component

Компоненти контролюють відображення даних на екрані — у візуальному поданні (англ. view).

Наприклад, наступні візуальні подання контролюються через компоненти:

Всередині класа компонента ви визначаєте логіку, тобто те, що компоненти роблять для функціональності візуального подання. Клас працює з візуальним поданням через API властивостей та методів.

Наприклад, HeroListComponent має властивість heroes, яка містить масив героїв, отриманих із сервіса. HeroListComponent має також метод selectHero(), який встановлює властивість selectedHero, коли користувач клацне, вибравши героя зі списку.

app/hero-list.component.ts (class)

export class HeroListComponent implements OnInit { heroes: Hero[]; selectedHero: Hero; constructor(private service: HeroService) { } ngOnInit() { this.heroes = this.service.getHeroes(); } selectHero(hero: Hero) { this.selectedHero = hero; } }

Angular створює, оновлює, та видаляє компоненти під час переходів користувача по різним сторінкам застосунка. Ваш застосунок може виконувати певні дії кожного разу, коли відбуваються ці життєві цикли, через опціональні хуки, наприклад через ngOnInit(), котрого оголошено в попередньому прикладі.

Шаблони

Шаблони

Ви визначаєте візуальне подання компонентів разом з їхніми компаньйонами — шаблонами. Шаблон являє собою частину HTML-коду, яка говорить Angular, як відображати дані компонента.

Шаблон написано на звичайному HTML, за виключенням деяких відмінностей. Ось шаблон для нашого HeroListComponent:

app/hero-list.component.html

<h2>Hero List</h2> <p><i>Pick a hero from the list</i></p> <ul> <li *ngFor="let hero of heroes" (click)="selectHero(hero)"> {{hero.name}} </li> </ul> <hero-detail *ngIf="selectedHero" [hero]="selectedHero"></hero-detail>

Хоча цей шаблон використовує типові елементи HTML, такі як <h2> та <p>, в ньому також є певні відмінності. В такому коді, як *ngFor, {{hero.name}}, (click), [hero], та <hero-detail>, використовується синтаксис шаблона Angular.

В останньому рядку шаблона, тег <hero-detail> є кастомним елементом, який представляє собою новий компонент — HeroDetailComponent.

HeroDetailComponent є відмінним компонентом, від оглянутого нами HeroListComponent. HeroDetailComponent (його код не показано) представляє собою інформацію про конкретного героя, якого користувач вибрав зі списку, представленого компонентом HeroListComponent. HeroDetailComponent є дочірнім по відношенню до HeroListComponent.

Metadata

Зверніть увагу як елемент <hero-detail> добре припасувався поміж рідних HTML-елементів. Кастомні компоненти гармонійно змішуються з рідними HTML в одному й тому ж шаблоні.


Метадані

Metadata

Метадані говорять Angular, як обробляти клас.


Поверніться назад до коду для HeroListComponent, очевидно — це код звичайного класа. Там немає доказів присутності фреймворка, взагалі ніде не згадується "Angular".

Фактично, HeroListComponent справді є простим класом. Він не є компонентом, допоки ви не скажете про це Angular.

Щоб сказати Angular, що HeroListComponent є компонентом, за цим класом закріплюються метадані.

У TypeScript, ви закріпляєте метадані використовуючи декоратор. Ось деякі метадані для HeroListComponent:

app/hero-list.component.ts (metadata)

@Component({ moduleId: module.id, selector: 'hero-list', templateUrl: './hero-list.component.html', providers: [ HeroService ] }) export class HeroListComponent implements OnInit { /* . . . */ }

Тут показано декоратор @Component, який ідентифікує клас, що йде безпоседеньо під ним, як клас компонента.

Декоратор @Component приймає обов'язковий конфігураційний об'єкт з інформацією, яка потрібна Angular для створення та представлення компонентів і їх візуальних подань.

Є кілька можливих конфігураційних опцій @Component:

Metadata

Метадані у @Component говорять Angular, де брати основні будівельні блоки, які ви вказуєте для цього компонента.

Шаблон, метадані, та компонент — разом описують зовнішній вигляд та поведінку візуального подання.

Застосовуйте інші метадані декоратора в такий же спосіб, щоб керувати поведінкою Angular. @Injectable, @Input, та @Output є одними з найбільш популярних декораторів.


Архітектурна ідея полягає в тому, що ви повинні додавати метадані до вашого коду, щоб Angular знав що робити.

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

Без фреймворка, ви б відповідали за вставку значень даних в елементи управління HTML, та перетворювали відповіді від користувачів у певні дії та оновлення значень. Написання такої логіки вставки/оновлень значень вручну є рутинним, схильним до помилок, та жахливим для читання, про що може сказати будь-який розробник, хто писав код на jQuery.

Data Binding

Angular підтримує прив'язку даних, механізм для координації частин шаблона з частинами компонента. Додавайте розмітку прив'язки до HTML-шаблону щоб указувати Angular, як з'єднувати ці частини.

Як показано на діаграмі, існує чотири форми синтаксису для прив'язки даних. Кожна форма має напрямок — до DOM, від DOM, або в обох напрямках.


У прикладі шаблону HeroListComponent показано три форми:

app/hero-list.component.html (binding)

<li>{{hero.name}}</li> <hero-detail [hero]="selectedHero"></hero-detail> <li (click)="selectHero(hero)"></li>

Дво-напрямлена прив'язка даних є важливою четвертою формою, яка поєднує прив'язку до властивості та до подій в одному записі, використовуючи директиву ngModel. Таку прив'язку можна знайти у прикладі шаблона HeroDetailComponent:

app/hero-detail.component.html (ngModel)

<input [(ngModel)]="hero.name">

При дво-напрямленій прив'язці, значення даних властивостей компонента підставляються у поле шаблона. І в зворотньому напрямку — коли користувач змінює значення у шаблоні, відповідно оновлюються властивості у компоненті.

Angular обробляє усі прив'язки даних по-разу за цикл JavaScript-події, від кореневого компонента застосунка до усіх дочірніх компонентів.

Data Binding

Прив'язка даних грає важливу роль в комунікації між шаблоном та його компонентом.


Parent/Child binding

Прив'язка даних також важлива для комунікації між батьківськими та дочірніми компонентами.


Директиви

Parent child

Шаблони Angular є динамічними. Коли Angular відмальовує їх, він трасформує DOM відповідно до інструкцій наданих директивами.

Директива представляє собою клас з декоратором @Directive. Компонент являє собою директиву, яка має шаблон; декоратор @Component є насправді розширеним декоратором @Directive з шаблонно-орієнтованою функціональністю.

Хоча технічно компонент є директивою, але компоненти є настільки відокремленими та центральними для застосунків Angular, що у цьому архітектурному огляді ми відрізняємо компоненти від директив.

Існують два інших види директив: структурні та атрибутні директиви.

Вони, як правило, з'являються всередині тегів елементів (як це роблять атрибути), інколи по-імені, але частіше — у наслідок цілеспрямованого встановлення або прив'язки.

Структурні директиви змінюють шаблон через додавання, видалення, та заміну елементів в DOM.

У цьому прикладі шаблона використовуються дві вбудовані структурні директиви:

app/hero-list.component.html (structural)

<li *ngFor="let hero of heroes"></li> <hero-detail *ngIf="selectedHero"></hero-detail>

Атрибутні директиви змінюють відображення або поведінку існуючих елементів. В шаблонах вони схожі на звичайні HTML-атрибути, тому вони й мають таку назву.

Директива ngModel, яка впроваджує дво-напрямлену прив'язку даних, являє собою приклад атрибутних директив. ngModel модифікує поведінку існуючих елементів (як правило <input>), встановлюючи видимі значення властивостей та відповідаючи на події змін.

app/hero-detail.component.html (ngModel)

<input [(ngModel)]="hero.name">

Angular взагалі має дещо більше директив, які або змінюють структуру шаблона (наприклад, ngSwitch), або модифікують аспекти DOM-елементів та компонентів (наприклад, ngStyle та ngClass).

Звичайно ж, ви можете написати свої власні директиви. Такі компоненти як HeroListComponent, є одними з видів кастомних директив.

Сервіси

Service

Сервіси являють собою широку категорію, охоплюючу будь-яке значення, функцію, або функціональність, які потрібні для вашого застосунку.

Майже усе може бути сервісом. Як правило, сервіс є класом з вузьким та добре визначеним призначенням. Він повинен робити щось спецефічне, причому робити це добре.


Приклади призначень:

В Angular немає нічого специфічного стосовно сервісів. Angular не має визначення сервісів. Не існує також базового класу для сервісів, та місця для реєстрації сервісів.

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

Ось приклад класу сервіса, який пише в консоль браузера:

app/logger.service.ts (class)

export class Logger { log(msg: any) { console.log(msg); } error(msg: any) { console.error(msg); } warn(msg: any) { console.warn(msg); } }

А ось HeroService, який використовує Promise щоб отримати героїв. HeroService залежить від сервісу Logger та іншого сервісу BackendService, який відповідальний за роботу із сервером.

app/hero.service.ts (class)

export class HeroService { private heroes: Hero[] = []; constructor( private backend: BackendService, private logger: Logger) { } getHeroes() { this.backend.getAll(Hero).then( (heroes: Hero[]) => { this.logger.log(`Fetched ${heroes.length} heroes.`); this.heroes.push(...heroes); // fill cache }); return this.heroes; } }

Сервіси є всюди.

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

Робота компонента — це показ необіхдних даних користувачу і більше нічого. Він є посередником між візуальним поданням (яке відмальовується у шаблоні) та логікою застосунка (яка часто містить деякі поняття моделі). Хороший компонент представляє собою властивості та методи для прив'язки даних. Він делегує все складне до сервісів.

Angular не змушує до цих принципів. Він не буде скаржитись, якщо ви напишите "компонент-простиню" на 3000 рядків.

Angular буде допомогати вам слідувати цим принципам спрощуючи комунікацію між компонентами та сервісами завдяки впровадженню залежностей.

Впровадження залежностей

Service

Впровадження залежностей (англ. dependency injection) це спосіб отримання нового примірника класа з повністю вирішеними залежностями, які в нього є. Більшість залежностей є сервісами. Angular використовує впровадження залежностей щоб надавати нові компоненти з тими сервісами, які вони протребують.


Angular знає який сервіс потрібен компоненту, коли бачить типи параметрів його конструктора. Наприклад, наступним записом ми говоремо, що конструктор HeroListComponent потребує HeroService:

app/hero-list.component.ts (constructor)

constructor(private service: HeroService) { }

Коли Angular створює компонент, він спочатку питає інжектор за сервіс, необхідний цьому компоненту.

Інжектор працює з контейнером примірників сервісів, який він попереді створив. Якщо запитуваний примірник сервіса відсутній в контейнері, спочатку інжектор створює та додає його в контейнер і лише потім передає до Angular. Коли усі запитувані сервіси вирішені та видані, Angular вже може викликати конструктор компонента, і в якості аргументів передавати ці сервіси. Це і є впровадження залежностей.

Робота інжектора сервіса HeroService є приблизно такою:

Service

Якщо інжектор не має HeroService, як він знає як його отримати?

Коротко кажучи, інжектор повинен мати попередньо зареєстрованого провайдера для HeroService. Провайдер може створювати або повертати сервіси, як правило, самі класи сервісів.

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

Як правило, провайдерів додають до кореневого модуля, так що один і той самий примірних сервсу доступний будь-де.

app/app.module.ts (провайдери на рівні модуля)

providers: [ BackendService, HeroService, Logger ],

Як альтернатива, провайдерів реєструють на рівні компонента у властивості providers метаданих @Component:

app/hero-list.component.ts (провайдери на рівні компонента)

@Component({ moduleId: module.id, selector: 'hero-list', templateUrl: './hero-list.component.html', providers: [ HeroService ] })

Реєстрація на рівні компонента означає, що ви отримуєте новий примірник сервіса з кожним новим примірником цього компонента.

Що варто запам'ятати про впровадження залежностей:

Підсумок

Ви вивчили основні моменти стосовно вісьми головних будівельних блоків Angular-застосунків:

Вони є фундаментом для будь-чого іншого у Angular-застосунках, і цього більш, ніж достатньо для ознайомлення. Але тут не включено абсолютно усе, що вам потрібно знати.

Ось короткий список, в алфавітному порядку, інших важливих можливостей Angular та сервісів. Більшість із них описані в даній документації (чи скоро будуть описані).

Анімація: Анімована поведінка компонентів, без необхідності глибоких знань анімаційних технік чи CSS, за допомогою Angular-бібліотеки для анімації.

Виявлення змін: Документація по виявленню змін показує як Angular вирішує, що значення властивості компонента було змінено, коли оновлювати екран, як він використовує зони для перехвату асинхронної активності, та як запускає свої стратегії виявлення змін.

Події: В документації для подій показано як використовувати компоненти та сервіси для поширення подій за допомогою механізму для публікації і підписки на події.

Форми: Підтримка складних сценаріїв введення даних із валідацією на основі HTML та базової перевірки (англ. dirty checking).

HTTP: Комунікація із сервером для отримання даних, збереження даних, та виклик дій на боці сервера, за допомогою HTTP-клієнта.

Хуки життєвих циклів: Описано ключові моменти так званих "життєвих циклів" компонентів, починаючи від створення цих компонентів, до видалення, через впровадження інтерфейсів хуків життєвих циклів.

Pipes: Використовуйте pipes у ваших шаблонах щоб покращити досвід користувачів через трансформацію значень для показу. Розглянемо цей вираз з курсом валют, де використовується pipe:

price | currency:'USD':true

Тут показується ціна 42.33 як $42.33.

Маршрутизатори: Навігація зі сторінки на сторінку всередині клієнтського застосунка.

Тестування: Запустіть модульні тести для ваших частин застосунка щоб побачити як вони взаємодіють з фреймворком Angular використовуючи Angular Testing Platform.

Наступний крок

AppModule: кореневий модуль