Angular 2+: Підготовка до співбесіди
Пропоную Вам 10 важливих питань для підготовки на співбесіду на позицію Angular Front-end developer. Ці питання охоплюють як Angular, так і RxJs та Typescript, без знань яких практично неможливо якісно писати код високого рівня і швидко розбиратися в наявному коді.
- View Encapsulation
- Types vs Interface vs Abstract Class vs Class
- Модифікатори доступу
- Чи є відмінність між операторами Rx: first() та take(1)?
- Generics
- В чому різниця між Subject, BehaviorSubject, ReplaySubject і AsyncSubject?
- Cold vs Hot Observables
- RxJs оператори: switchMap, mergeMap, concatMap, exhaustMap
- Route Guards in Angular
- Що нового в Angular 15
В кінці статті будуть додаткові посилання на окремі теми, які були вже раніше описані на цьому блозі і я не хотіла ці питання додатково висвітлювати в цій статті, але вони також важливі для ознайомлення і дуже часто задаються на співбесідах.
1. View Encapsulation
Інкапсуляція стилів — це механізм запису стилів компонента таким чином, щоб стилі інших компонентів на нього не впливали.
Перш ніж розглядати інкапсуляцію стилів, варто розібратися, що таке Shadow Dom. Дочірнє тіньове дерево розміщується всередині деякого елемента на сторінці, що називається Shadow host. Корінь дочірнього дерева у такому разі називається Shadow root. Під час рендерингу Shadow Tree займає місце вмісту Shadow host елемента.

При створенні компонента можна вказати тип ViewEncapsulation:
import {ViewEncapsulation} from '@angular/core';
@Component({
selector: 'app-root',
encapsulation: ViewEncapsulation.None
})
class AppComponent {
...
}
Існує 3 типи ViewEncapsulation:
1. ViewEncapsulation.Emulated — кросбраузерна інкапсуляція, використовується за замовчуванням, навіть якщо Shadow DOM недоступний. У такому разі стилі будуть записані в заголовок документа, але до класу стилів у style блоці буде додано додатковий атрибут, який робить селектор більш специфічним, щоб не виникали конфлікти з раніше створеними селекторами:
<head>
<style>.test[_ngcontent-1] {
background: #f2f2f2;
}</style>
</head>
Той самий атрибут буде доданий і до елемента html при рендерингу:
<app-test title="Details" _ngcontent-0 _nghost-1>
<div class="test" _ngcontent-1>
...
</div>
</app-test>
2. ViewEncapsulation.Native — інкапсуляція за допомогою Shadow DOM. У такому разі після рендерингу ми побачимо, що стилі більше не потраплятимуть у заголовок документа, а будуть у шаблоні самого компонента.
<app-test title="Details">
#shadow-root
<style>.test {
background: #f2f2f2;
}</style>
<div class="test">
...
</div>
</app-test>
3. ViewEncapsulation.None — вимкнена інкапсуляція стилів. Усі стилі застосовуються до всього документа, оскільки записуються в заголовок всього документа, у разі стилі компонент можуть перезаписувати один одного.
2. Type vs Interface vs Abstract Class vs Class
Type — це визначення типу даних, наприклад, об’єднання, примітив, перетин, кортеж або будь-який інший тип. Interface — це в основному спосіб опису форм даних, наприклад, об’єкта.
Перша відмінність полягає в тому, як інтерфейси і класи поводять себе при дублюванні назв. Ми не зможемо створити типи з однаковими іменами, але якщо створимо інтерфейси з однаковими іменами — властивості інтерфейсів об’єднаються в один інтерфейс.
export type testType = string | number;
export interface UnitsData {
name: string;
value: string;
}
export interface UnitsData {
test: string;
}
export const testValue: UnitsData = {
name: 'sdf',
value: 'sdf',
test: 'sdf'
}
У TypeScript ми можемо легко розширювати та наслідувати інтерфейси, однак з типами це неможливо. Також в TypeScript ми також можемо створювати класи, що реалізують інтерфейси.
class Car {
printCar = () => {
console.log("this is my car")
}
};
interface NewCar extends Car {
name: string;
};
class NewestCar implements NewCar {
name: "Car";
constructor(engine:string) {
this.name = name
}
printCar = () => {
console.log("this is my car")
}
};
Клас — це скелет об’єктів, за допомогою якого ми створюємо об’єкти. Клас може мати властивості, методи та реалізацію цих методів, на відміну від інтерфейсу, який не має реалізації методів. Тобто Інтерфейс слугує для проєктування структури, а клас для реалізації архітектури.
Ми можемо створити екземпляр класу, але не можемо створити екземпляр інтерфейсу. Інтерфейс використовується лише для визначення типу об’єкта.
class Employee {
empCode: number;
empName: string;
constructor(code: number, name: string) {
this.empName = name;
this.empCode = code;
}
getSalary() : number {
return 10000;
}
}
const emp = new Employee(123, 'Sean')
Після компіляції ts в js, клас залишається в коді, а інтерфейс видаляється.
Клас реалізує інтерфейс за допомогою ключового слова implements, клас може розширювати інший клас також за допомогою ключового слова extends, що називається наслідуванням. Клас не підтримує багаторазове наслідування, тобто клас може успадковувати лише один клас, але він може реалізовувати декілька інтерфейсів.
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
class Employee extends Person {
empCode: number;
constructor(empcode: number, name:string) {
super(name);
this.empCode = empcode;
}
displayName():void {
console.log("Name = " + this.name + ", Employee Code = " + this.empCode);
}
}
let emp = new Employee(100, "Bill");
emp.displayName(); // Name = Bill, Employee Code = 100
Абстрактний клас — це клас, який зберігає структуру, може мати реалізацію методів, але може і не мати реалізацію, може використовуватися для наслідування, але від нього не можна створити екземпляри. Клас, який розширює абстрактний клас, повинен визначати всі абстрактні методи.
abstract class Person {
name: string;
constructor(name: string) {
this.name = name;
}
display(): void{
console.log(this.name);
}
abstract find(string): Person;
}
class Employee extends Person {
empCode: number;
constructor(name: string, code: number) {
super(name); // must call super()
this.empCode = code;
}
find(name:string): Person {
// execute AJAX request to find an employee from a db
return new Employee(name, 1);
}
}
let emp: Person = new Employee("James", 100);
emp.display(); //James
let emp2: Person = emp.find('Steve');
3. Модифікатори доступу
В TS існує декілька видів модифікаторів доступу:
- public
- private
- protected
- static
- readonly
Public
Всі властивості та методи по замовчуванню є публічними, тому слово public не обов’язково вказувати. До public ми маємо доступ всюди — в будь-якому похідному класі, шаблоні.
class Employee {
public name: string;
subname: string;
}
let emp = new Employee();
emp.name = 'Alex';
emp.subname = "Alexander";
В наведеному прикладі name та subname — публічні властивості класу.
Private
Модифікатор приватного доступу гарантує, що члени класу є видимими лише для цього класу та не доступні за межами класу.
class Employee {
private id: number;
name: string;
}
let emp = new Employee();
emp.id = 123; // Compiler Error
emp.name = 'Alexander';
У наведеному вище прикладі ми позначили властивіть id як приватну, отже, коли ми створюємо об’єкт emp і намагаємося отримати доступ до emp.id, це видасть помилку.
Private в typescript має 2 види: жорсткий і м’який. Різниця в тому, що м’який private ми отримаємо в ts, а в забілдженому файлі js властивість чи метод стане публічним. При використанні жорсткого типу ми отримаємо приватний метод навіть в js.
Для створення поля з жорсткою приватністю вам потрібно лише створити поле з «#», як це показано в прикладі:
class Friend {
#firstName: string;
constructor(firstName: string) {
this.#firstName = firstName;
}
getFullName() {
return this.#firstName;
}
}
Ззовні ми можемо отримати доступ до методу getFullName, але не до поля #firstName, оскільки це приватне поле.
let friend = new Friend('Thomas');
console.log(friend.getFullName()); // Works
console.log(friend.#firstName); // Does not work, as it is a private field
Protected
Protected властивості та методи відкриті для доступу в класі та похідному класі, коли для Private ми не отримуємо доступ в похідному класі. В js немає protected, тому там властивість або метод набуває публічного значення.
class Employee {
public name: string;
protected id: number;
constructor(name: string, id: number) {
this.name = name;
this.id = id;
}
}
class SalesEmployee extends Employee {
private department: string;
constructor(name: string, id: number, department: string) {
super(name, id);
this.department = department;
}
}
let emp = new SalesEmployee("John Smith", 123, "Sales");
emp.id; //Compiler Error
У наведеному вище прикладі ми маємо клас Employee із двома членами, публічним name і захищеною властивістю id. Ми створюємо підклас SalesEmployee, який походить від батьківського класу Employee. Якщо ми спробуємо отримати доступ до захищеного члена поза класом, як emp.empCode, ми отримаємо помилку компіляції.
Static
Ми можемо створювати статичні змінні та методи, для доступу до яких не потрібно створювати екземпляр класу. Розглянемо на прикладі:
class Lib {
static pi: number = 3.14;
static calculateCircleArea(radius:number): number {
return this.pi * radius * radius;
}
}
Lib.pi; // returns 3.14
Lib.calculateCircleArea(5); // returns 78.5
Ми бачимо, що для доступу до статичних методів ми можемо використовувати лише назву класу, не створюючи екземпляр такого класу. Такий підхід може бути зручним коли ми хочемо створити бібліотеку констант і імпортувати її в інші класи.
Оскільки статичні та нестатичні властивості й методи зберігаються на різних об’єктах — ми можемо мати статичні та нестатичні властивості з однаковими назвами:
class Lib {
static calculateCircleArea(): string {
return 'Static method';
}
calculateCircleArea(): string {
return 'New method';
}
}
console.log(Lib.calculateCircleArea()); // returns 'Static method'
const lib = new Lib();
console.log(lib.calculateCircleArea()); // returns 'New method'
Readonly
Префікс readonly використовується, щоб зробити властивість доступною лише для читання. До членів із доступом лише для читання можна отримати доступ за межами класу, але їхнє значення не можна змінити. Оскільки члени з доступом лише для читання не можуть бути змінені поза класом, їх потрібно або ініціалізувати під час оголошення, або ініціалізувати всередині конструктора класу.
class Employee {
readonly group: string = 'Default';
readonly id: number;
name: string;
constructor(code: number, name: string) {
this.id = code;
this.name = name;
}
}
let emp = new Employee(10, "John");
emp.id = 20; //Compiler Error
emp.name = 'Bill';
Крім того, для однієї властивості можна використовувати декілька модифікаторів доступу, наприклад, поле може бути одночасно і private, і readonly.
4. Чи є відмінність між операторами Rx: first() та take(1)?
Так, відмінність є. У випадку якщо Observable з викликом first() не повертатиме ніякого значення, ми отримаємо помилку. При використанні take(1) помилку не буде згенеровано.
Це відбувається, бо оператор first() приймає необов’язкову предикатну функцію та видає сповіщення про помилку, якщо немає жодного значення.
Цей код згенерує помилку:
import { EMPTY, range } from 'rxjs';
import { first, take } from 'rxjs/operators';
EMPTY.pipe(
first(),
).subscribe(console.log, err => console.log('Error', err));
З іншого боку take(1) просто приймає перше значення та завершує очікування нових значень. Більше логіки тут немає, тому у випадку відсутності значень помилки не буде, тому наступний код не згенерує помилку:
EMPTY.pipe(
take(1),
).subscribe(console.log, err => console.log('Error', err));
5. Generics
Generics — це код, який дозволяє оптимально працювати з різними типами даних. Це стає можливим завдяки тому, що з’являється додатковий параметр, куди можна передавати тип даних, таким чином generics отримують тип вже безпосередньо під час виклику. Generics можна використовувати як з функціями, так і з інтерфейсами і навіть класами. На прикладах ми розглянемо це більш детально.
function getModifItems<T>(item: T): T {
// do something
return item;
}
const elem = getModifItems<string>('test');
У наведеному вище прикладі змінна типу T вказана функцією в кутових дужках getModifItems<T>. Змінна типу T також використовується для визначення типу аргументів і значення, що повертається. Це означає, що тип даних, який буде вказано під час виклику функції, також буде типом даних аргументів і значення, що повертається.
Ми можемо передавати і декілька типів одночасно:
function getModifItems<T, U>(id: T, text: U): void {
console.log(typeof(id) + ", " + typeof(text));
}
const elem = getModifItems<number, string>(1, 'test');
Generic Constraints
Ми можемо задати дефолтний тип, наприклад Lengthwise. Також можемо розширити тип Lengthwise, передавши в Generic додатковий тип, тільки новий тип має бути сумісним з типом, який вказано по дефолту, інакше отримаємо помилку:
type Lengthwise = {
length: number;
};
function getLength<T extends Lengthwise>(arg: T): number {
return arg.length;
};
getLength("Hitchhiker's Guide to the Galaxy"); // 32
getLength([]); // 0
getLength(42); // Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.
getLength({}); // Property 'length' is missing in type '{}' but required in type 'Lengthwise'.
type NewLengthwise = {
style: string;
length: number;
};
const newItem: NewLengthwise = {
style: 'default',
length: 1578
};
getLength<NewLengthwise>(newItem) // 1578;
Generic в інтерфейсах та класах
Generic також можна використовувати з інтерфейсом:
interface IProcessor<T>
{
result: T;
process(a: T, b: T) => T;
}
TypeScript підтримує використання generics в класа. Параметр загального типу вказується в кутових дужках після імені класу:
class KeyValuePair<T,U>
{
private key: T;
private val: U;
setKeyValue(key: T, val: U): void {
this.key = key;
this.val = val;
}
display():void {
console.log(`Key = ${this.key}, val = ${this.val}`);
}
}
let kvp1 = new KeyValuePair<number, string>();
kvp1.setKeyValue(1, "Steve");
kvp1.display(); //Output: Key = 1, Val = Steve
6.Subject, BehaviorSubject, ReplaySubject і AsyncSubject
Subject — це особливий тип Observable, який дозволяє багаторазово передавати значення багатьом спостерігачам. Subject-и також є спостерігачами, тому що вони можуть підписатися на інший спостережуваний і отримувати від нього значення, яке він буде розсилати всім своїм підписникам.
Subjects реалізують методи subscribe, next, error та complete, а також підтримують колекцію observers[].

Observer може підписатися на Subject і отримувати від нього дані. Subject додає нового підписника до колекції observers[]. Щоразу, коли в потоці є значення, він сповіщає всіх своїх підписників.
export class AppComponent {
subject$ = new Subject();
ngOnInit() {
this.subject$.subscribe(val => {
console.log(val);
});
this.subject$.next("1");
this.subject$.next("2");
this.subject$.complete();
}
}
В RxJs є декілька типів Subject-ів. Розглянемо кожен з них.
BehaviorSubject
BehaviorSubject — тип Subject, коли є початкове значення і він має властивість зберігати поточне значення. Використовується, коли нам необхідно мати початкове значення для підписки.
const subject = new BehaviorSubject(0);
subject.subscribe(value => console.log(`Current value is ${value}`));
// Current value is 0
subject.next(1)
// Current value is 0
// Current value is 1
subject.next(2)
// Current value is 0
// Current value is 1
// Current value is 2
setTimeout(()=> {
subject.subscribe(value => console.log(`New current value is ${value}`));
// Current value is 0
// Current value is 1
// Current value is 2
// New current value is 2
subject.next(3)
}, 1000);
// Current value is 0
// Current value is 1
// Current value is 2
// New current value is 2
// Current value is 3
// New current value is 3
ReplaySubject
ReplaySubject — тип Subject, котрий також може надсилати старі значення новим підписникам, як і BehaviorSubject. Крім цього він може згадати частину старих значень і відправити їх новим підписникам, під час створення ми маємо можливість вказати кількість даних, яка буде надіслана новим підписникам. Використовується, коли нам треба запам’ятати якусь кількість даних з потоку та передати їх новим підписникам.
const subject = new ReplaySubject(2);
subject.subscribe(value => console.log(`Current value is ${value}`));
subject.next(1)
// Current value is 1
subject.next(2)
// Current value is 1
// Current value is 2
subject.next(3)
// Current value is 1
// Current value is 2
// Current value is 3
subject.next(4)
// Current value is 1
// Current value is 2
// Current value is 3
// Current value is 4
subject.subscribe(value => console.log(`New Current value is ${value}`));
// Current value is 1
// Current value is 2
// Current value is 3
// Current value is 4
// New current value is 3
// New current value is 4
subject.next(5)
// Current value is 1
// Current value is 2
// Current value is 3
// Current value is 4
// New current value is 3
// New current value is 4
// Current value is 5
// New current value is 5
AsyncSubject
AsyncSubject — тип Subject, який передає тільки останнє значення всім своїм підписникам і тільки після завершення виконання Observable. Використовується, коли нас цікавить лише останнє значення з потоку, а решта нам не важливі.
const subject = new AsyncSubject();
subject.subscribe(value => console.log(`Current value is ${value}`));
subject.next(1)
subject.next(2)
subject.next(3)
subject.next(4)
subject.subscribe(value => console.log(`New Current value is ${value}`));
subject.next(5)
subject.complete();
// Current value is 5
// New current value is 5
7.Cold vs Hot Observables
Cold observable не активує виробника дати, доки не з’явиться підписник. Зазвичай це відбувається, коли observable сам створює дані. Для кожного підписника Observable почне нове виконання.
export class AppComponent {
obs$ = of(1, 2, 3, 4);
ngOnInit() {
this.obs$.subscribe(val => {
console.log(val);
});
}
}
Hot observable не чекає, поки зявляться підписники, він може почати віддавати значення відразу. Також Hot Observer може обмінюватися даними між кількома підписниками, що називається «multicasting».
export class AppComponent {
subject$ = new Subject();
ngOnInit() {
this.subject$.next("1");
this.subject$.next("2");
this.subject$.complete();
}
}
Ми створили subject, який можна спостерігати, y ngOnInit, видаємо значення та робимо complete. Так як не було підписників, дані ніхто не отримує, але це не завадило нашому subject видавати дані.
8. RxJs оператори: switchMap, mergeMap, concatMap, exhaustMap
SwitchMap
fireEvents() {
// Here we react ot everything which is fired in the subject
this.sub
.asObservable()
// Here we can take the operator we want to take a look at which returns the
// result from the anyLongRunningOp method which is the value itself
// (for the sake of simplicity). The `tap` only prints out what
// was just sent, does nothing to the stream!
.pipe(
tap(value => console.log("--> sent out", value)),
switchMap(value => this.anyLongRunningOp(value))
)
// We console.log the output, which is 'first' or 'second' or
.subscribe(value => console.log("<-- received", value));
// After subscribing we emit the two value in the observable, could also be more than that
this.sub.next("first");
this.sub.next("second");
console.log("-------");
}
Коли в оператор switchMap буде потрапляти декілька значень від декількох потоків, switchMap буде повертати результат з останнього потоку. Тобто його цікавить лише останній потік і в функцію anyLongRunningOp потрапить лише значення «second», все що було до — буде ігноруватися.

ConcatMap
fireEvents() {
// Here we react ot everything which is fired in the subject
this.sub
.asObservable()
// Here we can take the operator we want to take a look at which returns the
// result from the anyLongRunningOp method which is the value itself
// (for the sake of simplicity). The `tap` only prints out what
// was just sent, does nothing to the stream!
.pipe(
tap(value => console.log("--> sent out", value)),
concatMap(value => this.anyLongRunningOp(value))
)
// We console.log the output, which is 'first' or 'second' or
.subscribe(value => console.log("<-- received", value));
// After subscribing we emit the two value in the observable, could also be more than that
this.sub.next("first");
this.sub.next("second");
console.log("-------");
}
Коли ConcatMap отримує значення «first», він викликає anyLongRunningOp з першим значенням, а потім надходить значення «second». Оператор concatMap тепер утримує цей виклик, доки метод anyLongRunningOp не повернеться з результатом виклику першого значення, а потім робить виклик для наступного значення «second». Тобто він об’єднує виклики та відправляє їх один за одним.

MergeMap
fireEvents() {
// Here we react ot everything which is fired in the subject
this.sub
.asObservable()
// Here we can take the operator we want to take a look at which returns the
// result from the anyLongRunningOp method which is the value itself
// (for the sake of simplicity). The `tap` only prints out what
// was just sent, does nothing to the stream!
.pipe(
tap(value => console.log("--> sent out", value)),
mergeMap(value => this.anyLongRunningOp(value))
)
// We console.log the output, which is 'first' or 'second' or
.subscribe(value => console.log("<-- received", value));
// After subscribing we emit the two value in the observable, could also be more than that
this.sub.next('first');
this.sub.next('second');
console.log(`fired events 'first' and 'second'`);
}
Якщо ми запускаємо «first», оператор MergeMap викликає anyLongRunningOp з першим значенням, а відразу після цього він викликає anyLongRunningOp з другим значенням. Ми друкуємо результат зразу, як тільки він повернеться з методу anyLongRunningOp. Ці операції виконуються паралельно, отже, порядок повернених значень не гарантовано відповідатиме порядку запитів, як вони були надіслані.

ExhaustMap
fireEvents() {
// Here we react ot everything which is fired in the subject
this.sub
.asObservable()
// Here we can take the operator we want to take a look at which returns the
// result from the anyLongRunningOp method which is the value itself
// (for the sake of simplicity). The `tap` only prints out what
// was just sent, does nothing to the stream!
.pipe(
tap(value => console.log("--> sent out", value)),
exhaustMap(value => this.anyLongRunningOp(value))
)
// We console.log the output, which is 'first' or 'second' or
.subscribe(value => console.log("<-- received", value));
// After subscribing we emit the two value in the observable, could also be more than that
this.sub.next('first');
this.sub.next('second');
console.log(`fired events 'first' and 'second'`);
}
Оператор exhaustMap піклується лише про перший запит, який надходить, і ігнорує всі, що надходять після цього.

9. Route Guards in Angular
Route Guards дозволяють обмежити доступ до деяких маршрутів на основі певної умови, наприклад, лише авторизовані користувачі можуть переглядати конкретну сторінку. Код guards, або їх ще можна назвати захисниками, дозволяє повернути 3 значення:
- true — означає, що ми можемо перейти за даним посиланням
- false — означає, що доступ до цього посилання заборонено
- UrlTree — структура, яка дозволяє перенаправити користувача за іншим посиланням, наприклад, на сторінку login.
В Angular існує декілька типів захисників:
- CanActivate — дозволяє або забороняє доступ до певного маршруту.
- CanActivateChild — дозволяє або забороняє доступ до всіх дочірніх маршрутів.
- CanDeactivate — дозволяє перевірити чи ми можемо покинути даний маршрут. Цей тип корисний, наприклад, коли користувач знаходиться на формі і має незбережені дані, які зникнуть при переході на інший маршрут.
- Resolve — дозволяє підвантажити дані, які необхідні для конкретної сторінки перед її відкриванням.
- CanLoad — використовується коли є LazyLoading модулів і дозволяє перевірити чи є необхідність підвантажувати якусь окрему функціональність.
- CanMatch (з версії ангуляра 14.1) — дозволяє перевірити чи може роутер порівнювати url з певним path, який є в config об’єкті.
У одного URL може бути одночасно декілька guard-ів, навіть одного й того самого типу.
Розглянемо приклад з CanActivate та CanActivateChild:
auth.guard.ts
@Injectable()
export class AuthGuard
implements CanActivate, CanActivateChild {
constructor(
@Inject(AuthService) private auth: AuthService
) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean {
return this.auth.isLoggedIn
}
canActivateChild(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean {
return this.canActivate(next, state)
}
}
app-routing.module.ts
const routes: Routes = [
{ path: 'login', component: LoginComponent },
{
path: 'pages',
component: PagesComponent,
canActivate: [AuthGuard],
canActivateChild: [AuthGuard],
children: [
{ path: 'about', component: AboutComponent },
{ path: 'contacts', component: ContactsComponent },
],
},
]
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
app.module.ts
@NgModule({
declarations: [...],
imports: [...],
providers: [AuthGuard],
})
export class AppModule { }
На відміну від CanActivate, CanDeactivate має додатково ще один параметр component, який містить екземпляр зазначеного компонента
can-deactivate.guard.ts
@Injectable()
export class DataChangesGuard
implements CanDeactivate<buyticketformcomponent> {
constructor() {}
canDeactivate(
component: BuyTicketFormComponent,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState: RouterStateSnapshot
) {
if (component.buyTicketForm.dirty)
return window.confirm(
'Unsaved data detected. Want to exit?'
)
else return true
}
}
app-routing.module.ts
{
path: 'ticket', component: BuyTicketFormComponent, canDeactivate: [DataChangesGuard]
}
Resolver — це сервіс, що реалізує інтерфейс Resolve, а саме метод resolve(), який обов’язково має повертати дані типу Observable. Вказаний для будь-якого маршруту Resolver дозволяє перехід на нього після виконання Observable в resolve().
@Injectable()
export class ContactsResolver implements Resolve<any> {
constructor(
private http: HttpClient,
private router: Router
) {}
resolve(route: ActivatedRouteSnapshot): Observable<any> {
return this.http.get('/api/contacts').pipe(
tap(
(res) => of(res),
(err) => {
this.router.navigate(['/'])
return EMPTY
}
)
)
}
}
Виконання методу resolve() (як синхронного, так і асинхронного) ініціює подію NavigationEnd, що можна використовувати для приховання прелоадера під час переходів між сторінками.
CanMatch — захисник, який вирішує, чи можна зіставити маршрут. Якщо всі захисники повертають значення true, навігація продовжується, і маршрутизатор використовуватиме маршрут під час активації, але якщо будь-який guard повертає false, Route пропускається для відповідності, а натомість обробляються інші конфігурації Route.
class UserToken {}
class Permissions {
canAccess(user: UserToken, id: string, segments: UrlSegment[]): boolean {
return true;
}
}
@Injectable()
class CanMatchTeamSection implements CanMatch {
constructor(private permissions: Permissions, private currentUser: UserToken) {}
canMatch(route: Route, segments: UrlSegment[]): Observable|Promise|boolean {
return this.permissions.canAccess(this.currentUser, route, segments);
}
}
app.module.ts
@NgModule({
imports: [
RouterModule.forRoot([
{
path: 'team/:id',
component: TeamComponent,
loadChildren: () => import('./team').then(mod => mod.TeamModule),
canMatch: [CanMatchTeamSection]
},
{
path: '**',
component: NotFoundComponent
}
])
],
providers: [CanMatchTeamSection, UserToken, Permissions]
})
class AppModule {}
10. Що нового в Angular 15
Standalone API
Команда Angular представила автономний API з Angular 14, що дозволяє розробникам створювати програми без використання модулів Ng. Тепер, в новій версії Angular 15 цей API став стабільним. В Angular 15 використання автономних компонентів дозволяє працювати синхронно з HTTP з клієнтськими маршрутизаторами, елементами Angular та багатьма іншими.
Окрім того, Standalone APIs дозволяють створювати Multi-Route застосунок.
Directive Composition API
Directive Composition API дозволяє або допомагає повторно використовувати код. Це дозволяє розробникам збільшувати host elements за допомогою директив і створювати додаток Angular за допомогою функції повторного використання коду, що сприяє пришвидшенню розробки.
Image Directive (NgOptimized Image)
Директива NgOptimized Image була представлена разом із Angular версії 14, а зараз стала більш стабільнішою. Також додалась нова фіча — автоматична генерація srcset. Це директива, яка автоматично генерує набір SRC, який допомагає завантажити зображення будь-де, де потрібно, таким чином зменшуючи час завантаження зображення.
Fill Mode (режим заливки, експериментальний) — це функція, яка усуває необхідність оголошувати розміри зображення та заповнювати зображення його батьківським контейнером.
Functional Router Guards
Роблячи Router ще простішим і ще більше зменшуючи шаблони, Router тепер автоматично розгортає експорт за замовчуванням із відкладеним завантаженням, що ще більше сприяє зменшенню коду.
Покращення Stack Traces
Тепер ви можете легко та ефективно відстежувати код за допомогою Stack Trace, ця функція допомагає при виникненні будь-якої помилки та допомагає знайти, де ця помилка сталася чи виявлена.
CDK (Component Dev Kit) Listbox
CDK сприяє створенню компонентів інтерфейсу користувача. В останній версії Angular додано новий примітив під назвою CDK Listbox, який допомагає налаштувати взаємодії Listbox, створені шаблоном Listbox WAI-ARIA на основі вимог.
Додаткові матеріали
Як я і обіцяла на початку статті, викладаю список додаткових матеріалів для підготовки до співбесіди:
- Angular: Dependency injection | Впровадження залежностей
- Angular 2+: Стратегия обнаружения изменений
- Жизненный цикл компонента Angular
- Производительность в Angular
Успіхів!
Джерела: angular.io, blog.angular-university.io, blog.bitsrc.io, blog.thoughtram.io, coldfox.ru, itnext.io, metanit.com, tutorialsteacher.com, blog.logrocket.com, offering.solutions, dou.ua, tektutorialshub.com, medium.com