SPA на Angular 6 за 20 хвилин? Легко!

14 хв. читання

Щоб розібратися в технології, вам знадобляться базові навички HTML та CSS та впевнені знання JavaScript.

Привіт! Мене звати Сергій. Я тренер з напрямку Angular та JavaScript у Dev-Pro.

Зі статті ви навчитесь створювати односторінкові застосунки на Angular просто та швидко. Спочатку розберемося в основних поняттях TypeScript, а потім перейдемо безпосередньо до написання коду на Angular.

Частина I. TypeScript

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

TypeScript — строго типізована надбудова (superset), яка транспілюється в JavaScript. Іншими словами, це «синтаксичний цукор» для JavaScript, який надає розробникам нові можливості.

Що таке транспіляція?

Транспіляція — процес перетворення коду однієї мови програмування в код іншої (або тієї ж мови, але іншої версії). Наприклад, перетворення сучасного JavaScript (EcmaScript 2015 — 2018) на старіший, що підтримується більшістю браузерів, EcmaScript 5.

Аналогічно до того, як ES2015 — 2018 транспілюється в EcmaScript 5, TypeScript транспілюється у JavaScript будь-якої версії, починаючи з EcmaScript 3. Таким чином, забезпечується підтримка навіть у старих браузерах.

Навіщо використовувати TypeScript?

TypeScript додає в JavaScript строгу типізацію, що дозволяє знаходити явні помилки на етапі транспіляції, а не на етапі виконання коду, що значно полегшує життя. Завдяки строгій типізації, IDE може надати список усіх методів та полів об'єкта, а це спрощує завдання ще більше. До того ж, TypeScript організовує більш досконалу імплементацію ООП, додаючи модифікатори доступу та інтерфейси.

Коротко про TypeScript

Строга типізація

Таким чином, створюємо змінну типу number:

let someVariable: number;
someVariable = Math.PI; // Ок!
someVariable = 'Hello'; // Не ок!

А якщо не вкажемо тип при створенні змінної, вона отримає його при ініціалізації:

let someVariable;
someVariable = 'Hello'; // Тепер змінна має тип string
someVariable = Math.random(); // Не ок, тому що number — це не string

Змінити тип змінної після оголошення, а також записати в неї значення іншого типу не можна.

Але, наведені обмеження не діють при використанні спеціального типу any. Такий тип існує, тому що іноді необхідно створювати змінні з невизначеним типом. Наприклад, така змінна може зберігати результат виконання сторонньої бібліотеки чи дані, які вводить користувач.

Так це виглядає в коді:

let someVariable: any;
someVariable = 'I have no idea what this variable can store';
someVariable = true;
someVariable = Math.random();
someVariable = { value: Math.PI };
//і т.д.

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

Приклад:

function foo(arg1: number, arg2: number): number {
   return arg1 + arg2;
}

let good = foo(2, 2); // Все ок
let bad = foo(2, '2'); // Помилка під час транспіляції

Функції можуть не мати оператора return. В такому випадку можна вказати :void, тобто показати, що функція нічого не повертає.

Приклад:

function baz(): void {
   console.log('Hello friend!');
}

baz(); // Все ок

let bad: number;
bad = baz(); // Помилка, тип void не може бути присвоєний number

ООП у TypeScript

TypeScript розширює імплементацію ООП в JavaScript: основними зручностями є модифікатори доступу та інтерфейси. Надамо декілька прикладів для наочності.

Класи створюються так само, як і в JavaScript, але поля класів оголошуються по-іншому.

Наприклад:

class Person {
   public firstName: string;
    public secondName: string;

    constructor(firstName: string, secondName: string) {
       this.firstName = 'John';
        this.secondName = secondName;
    }

    private _getFullName(): string {
       return `${this.firstName} ${this.secondName}`
    }

    public sayHello(): void {
       console.log(`Hello, my name is ${this._getFullName()}`);
    }
}

Які відмінності з JavaScript? Тут бачимо оголошення полів класу поза конструктором та три модифікатори доступу: private, public та protected.

Модифікатори доступу

  • public. Члени класу доступні повсюди: в тілі методів класу та тілі методів класів-нащадків через вказівник this, а також в екземплярів класу.
  • protected. Члени класу доступні лише в методах класу та в методах класів-нащадків через this.
  • private. Члени класу доступні лише в методах класу-власника.

Наступна особливість — інтерфейси, які відіграють в Angular важливу роль.

Інтерфейси

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

Інтерфейси створюються за допомогою ключового слова interface.

Приклад:

interface Feedable {
   calories: number;
    eat(food: string): string;
}

Застосовуються до класу за допомогою ключового слова implements.

Приклад:

class Person implements Feedable {
   public firstName: string;
    public secondName: string;
    public calories: number;

    constructor(firstName: string, secondName: string) {
       this.firstName = 'John';
        this.secondName = secondName;
    }

    eat(food: string): string {
       return `Om nom nom ${food}`;
    }
}

Ось приклад, в якому клас Person успадковується від класу BasePerson та реалізує інтерфейс Feedable:

class Person extends BasePerson implements Feedable {
   public calories;
    public eat(food: string): string {
       return `Om nom nom ${food}`;
    }
}

Якщо клас Person оголошено як такий, що реалізує інтерфейс Feedable, то ми будемо змушені створити поле calories та реалізувати метод eat для нього. В іншому випадку просто отримаємо помилку під час транспіляції.

Навіщо це потрібно?

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

Отже, ми швидко оглянули такі можливості TypeScript:

  • Строга типізація
  • Модифікатори доступу
  • Інтерфейси

Звісно, TypeScript містить ще багато цікавого, тому я раджу ознайомитись з ним краще. Але для комфортного початку роботи з Angular цього вистачить.

Частина II. Angular

З чого почати?

У цій частині буде послідовно описано створення простого застосунку на Angular, а також розглянемо файлову структуру застосунку та основні сутності фреймворку — модуль та компонент.

Найбільш зручним інструментом для роботи з Angular є Angular CLI. Його можна встановити за допомогою менеджера пакетів node.js, виконавши наступну команду npm install -g @angluar/cli.

Таким чином, angular cli встановиться глобально, тобто ви зможете використовувати цей пакет будь-де на вашому комп'ютері.

Тепер, коли angular cli встановлено, запустимо команду ng new ng-example, яка створить проект та встановить усі npm пакети.

Коли один пакет створено, викличемо в його теці команду ng serve. Команда запустить локальний сервер з проектом. Відкриваємо браузер та переходимо за наступним URL: http://localhost:4200. Нарешті, проект працює, а в браузері відображається стартова сторінка, підготовлена розробниками angular cli.

Оглянемо структуру проекту

Тека src у створеному проекті зберігає наступні файли:

  • index.html — файл, в якому знаходяться елементи head, body, а також головний компонент застосунку. Тут можуть підключатися сторонні скрипти та файли стилів.
  • styles.css — головний файл стилів. Після збірки застосунку його вміст разом з вмістом інших стилів увійде в єдиний файл, тому безпосередньо за допомогою тега link ніде не підключається. Стилі, оголошені тут, будуть застосовуватися до всіх компонентів застосунку.
  • app — тека, що містить головний модуль та компонент застосунку. В ній будуть створюватися усі наступні сутності.

Оглянемо теку app. В ній знаходиться головний модуль застосунку — app.module.ts.

Що таке модуль в Angular?

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

Що знаходиться в app.module.ts, і навіщо все це потрібно?

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

import { AppComponent } from './app.component';

@NgModule({
 declarations: [
   AppComponent
 ],
 imports: [
   BrowserModule
 ],
 providers: [],
 bootstrap: [AppComponent]
})
export class AppModule { }

Модуль створюється за допомогою декоратору NgModule, який містить наступні поля:

  • declaration — масив, що зберігає класи компонентів, які використовуються в даному модулі.
  • imports — масив, який зберігає класи інших модулів, які використовуються в даному модулі.
  • bootstrap — поле, яке наявне лише в головному модулі. В ньому вказано який компонент буде кореневим.

Що знаходиться в app.component.ts і навіщо все це потрібно?

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

@Component({
 selector: 'app-root',
 templateUrl: './app.component.html',
 styleUrls: ['./app.component.css']
})
export class AppComponent {
 title = 'app';
}

Компоненти в Angular створюються декоратором Component, який містить наступні поля:

  • selector — назва тегу, за яким компонент буде доступний.
  • templateUrl — шлях до файлу з html шаблоном цього компоненту. Може бути змінений на template. В такому випадку його значенням повинен бути рядок з html шаблоном.
  • styleUrls — масив, що містить шляхи до файлів стилів. Може бути змінений на styles. В такому випадку значенням має бути масив рядків зі стилями.

У теці app можна також побачити файл app.component.spec.ts — файл тестів для компонента, але в статті його не буде розглянуто, тому що тестування є темою для окремого матеріалу, який не є обов'язковим для знайомства з фреймворком.

Ми ознайомились зі структурою проекту. Тож напишемо трохи коду. Для прикладу я обрав таймер, який складатиметься з компонентів display та actions-controls.

В компоненті display буде відображатися поточне значення таймера, яке він отримуватиме з компонента app. В компоненті actions-controls будуть знаходитись кнопки для управління таймером. Почнемо!

Спочатку створимо два компоненти за допомогою команд ng generate component display та ng generate component actions-controls.

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

Додамо створені компоненти до файлу app.component.html:

<h2>Counter</h2>

<app-display></app-display>
<app-actions-controls></app-actions-controls>

І побачимо:

Angular App

Відкриємо файл actions-controls.component.html та додамо наступне:

<p>
  <button (click)="onDecrease(inpCounterStep.value)">-</button>
  <input #inpCounterStep type="number" value="1">
  <button (click)="onIncrease(inpCounterStep.value)">+</button>
</p>

Обговоримо докладніше, що ми додали.

<button (click)="onDecrease(inpCounterStep.value)">-</button>

Подібним чином ми обробляємо подію кліку на кнопку за принципом (назва події)="обробник-цієї-події()". Не забувайте про круглі дужки у кінці, що позначають виклик методу, переданого у ролі обробника. В іншому разу працювати не буде.

<input #inpCounterStep type="number" value="1">

А тут ми створили так звану template reference на input. Тепер у шаблоні за аліасом inpCounterStep доступний екземпляр цього елементу. Ми використовували його в обробнику кліка.

Перейдемо до файлу actions-controls.component.ts та напишемо наступний код:

import { Component, OnInit, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-actions-controls',
  templateUrl: './actions-controls.component.html',
  styleUrls: ['./actions-controls.component.css']
})
export class ActionsControlsComponent implements OnInit {
  @Output()
  public increase: EventEmitter<number> = new EventEmitter();

  @Output()
  public decrease: EventEmitter<number> = new EventEmitter();

  constructor() { }

  public ngOnInit(): void { }

  public onDecrease(value: string): void {
    this.decrease.emit(+value);
  }

  public onIncrease(value: string): void {
    this.increase.emit(+value);
  }
}

Що тут відбувається?

По-перше, звернемо увагу на декоратор Output та клас EventEmitter, які імпортуються з @angular/core. За допомогою Output декоруємо поля класу компонента, що дозволить обробляти події increase та decrease для цього компонента. Angular вимагає, щоб ці поля були екземплярами класу EventEmitter. Також тут є обробники подій, що згадуються у шаблоні. Їхні поля, продекоровані з Output, викликають методи emit, що призводить до спрацьовування подій increase та decrease на цьому компоненті.

Перейдемо до app.component.html та почнемо обробляти події у компонента actions-controls.

<app-display></app-display>
<app-actions-controls
  (increase)="onIncrease($event)"
   (decrease)="onDecrease($event)">
</app-actions-controls>

Зверніть увагу, що змінна $event передається як аргумент для обробників подій. $event — спеціальна, оголошена фреймворком, змінна, яка зберігає дані про здійснену подію. Для стандартних подій, на зразок click, mousemove, input та ін., вона буде зберігати об'єкт DOM івенту. У нашому випадку це буде значення, що передається в метод emit.

Додамо обробники подій в app.component.ts:

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  public counterValue = 0;

  public onIncrease(value: number): void {
    this.counterValue += value;
  }

  public onDecrease(value: number): void {
    this.counterValue -= value;
  }
}

Тут немає нічого цікавого: події обробляються, змінюється значення поля counterValue. Настав час перейти до компонента display, тому приділимо увагу файлу display.component.ts та додамо наступний код:

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-display',
  templateUrl: './display.component.html',
  styleUrls: ['./display.component.css']
})
export class DisplayComponent implements OnInit {
  @Input()
  public value: number;

  constructor() {
    console.log('constructor', this.value);
  }

  public ngOnInit(): void {
    console.log('OnInit', this.value);
  }
}

Тут використовується декоратор @Input для того, щоб передати дані в компонент, а також використовується інтерфейс OnInit з @angular/core.

Що таке OnInit ?

Почнемо з того, що у кожного компонента в Angular є свій життєвий цикл, що являє собою ряд методів, які викликаються фреймворком для компонентів у певні моменти. Ініціалізація, руйнування, перерисовка — подібні методи називаються «хуки». Вони імплементуються розробником у тому випадку, якщо є необхідність щось робити у той чи інший момент життя компонента. У нашому випадку застосовується хук ngOnInit. Метод буде запущено лише один раз — після ініціалізації компонента. На відміну від конструктора, у ngOnInit будуть доступні передані до компонента дані.

OnInit з @angular/core — інтерфейс, який змушує реалізовувати хук життєвого циклу ngOnInit. Докладніше про хуки можна дізнатися тут.

Повернемося до app.component.html, щоб передати дані в компонент display.

<app-display [value]="counterValue"></app-display>
<app-actions-controls
   (increase)="onIncrease($event)"
    (decrease)="onDecrease($event)">
</app-actions-controls>

Зверніть увагу на app-display. За допомогою запису [ім'я-поля]="значення" ми передаємо значення до компонента display.

Перейдемо до display.component.html та додамо наступний шаблон:

<h2>{{value}}</h2>

Лише один рядок, але що він означає? На місце {{}} буде підставлено значення поля value. Такий механізм називається інтерполяція. Запустимо застосунок з командою ng serve та поспостерігаємо за результатом.

Angular App

Нарешті, застосунок готовий. Тож за 20 хвилин ми встигли встановити Angular CLI, зробили з його допомогою скелет майбутнього застосунку та встановили npm пакети. Також ми ознайомились з файловою структурою застосунку та такими сутностями фреймворку як модуль та компонент, дізналися, яким чином компоненти обмінюються даними та ознайомились з життєвим циклом компоненту. Як наслідок, маємо нескладний застосунок — лічильник.

Сподіваюсь, що після ознайомлення зі статтею вивчення фреймворку не викличе труднощів.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 3.9K
Приєднався: 11 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація