Чому не працює ваше додаток Angular: 11 основних помилок

17

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

Принаймні, я стикався. Після цієї статті ці помилки більше не будуть вас гальмувати при розробці. Нижче ми докладно розберемо всі 11 помилок. Я поясню, чому це помилка, і вкажу вам вірний напрямок, як мінімум, одним рішенням.

1. Імпорт обов’язкових модулів Angular

Можливо, найпоширеніша помилка новачків – вони не імпортують обов’язкові модулі. Чому? Тому що вони про них навіть не знають. Звичайно, не знають. Вивчення фреймворку Angular займає якийсь час. На жаль, часто це призводить до містичним чином не працюючим програмам. Ви можете отримати наступні помилки: can’t bind to ‘ngModel’ since it isn’t a known property of ‘input’

Ця помилка означає, що ви не імпортували Angular Forms Module модуль. Unhandled Promise rejection: No provider for HttpClient!

Ця помилка означає, що ви не імпортували HttpClient Module в свій (кореневої) модуль.

Рішення

Для вирішення цієї проблеми необхідно імпортувати відсутній модуль у свій модуль. В більшості випадків це буде модуль AppModule в папці програми.

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

Зверніть увагу: імпортуйте тільки необхідні модулі! Імпорт непотрібних модулів істотно роздуває вага додатки. Ця порада стосується не тільки модулів angular. Він також стосується будь-яких модулів Angular, які ви можете використовувати, в тому числі і сторонні модулі. Поширені модулі, які, можливо, необхідно імпортувати:

BrowserModule
FormsModule // required to use ngModel directive
HttpClientModule // formerly HttpModule
RouterModule
BrowserAnimationsModule / NoopAnimationsModule

Для сторонніх бібліотек доброю практикою вважається максимальне розбивка на модулі. Це зменшить вагу програми. У Angular Material, наприклад, необхідно імпортувати тільки модулі для використовуваних вами компонентів. Наприклад:

MatMenuModule
MatSidenavModule
MatCheckboxModule
MatDatepickerModule
MatInputModule

2. Не використовуйте DOM посилання поки вони не створені (@ViewChild)

Декоратор @ViewChild сильно спрощує створення посилань на дочірні елементи (HTML вузли або компоненти) компонента. Вам потрібно лише додати ID посилання у вузол або компонент у вашому шаблоні. Просто вставити # і далі ім’я, але без атрибутів вузлів.

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

Angular автоматично присвоює посилання властивості компоненту, якщо це властивість декороване за допомогою @ViewChild(). Не забудьте передати ім’я посилання в декоратор. Наприклад, @ViewChild(‘myDiv’).

import { ViewChild } from ‘@angular/core’;
@Component({})
export class ExampleComponent {
@ViewChild(‘myDiv’) divReference;
}

Проблема

@ViewChild() – дуже корисна директива. Але потрібно пам’ятати: Посилання на елемент можна використовувати тільки в тому випадку, якщо елемент існує! А чому його не повинно бути? Є безліч причин, за яким елемент, на який ви посилаєтеся, може не існувати.

Найчастіша причина – браузер не закінчив його створення і не встиг додати його в DOM. Якщо ви намагаєтеся використовувати його до створення, додаток впаде. Якщо ви знайомі з JS, ви, можливо, вже стикалися з такою проблемою, так як вона не специфічна для Angular.

Один з прикладів посилання на DOM, який ще не створений – через конструктор компонента. Ще один приклад – у життєвому циклі ngOnInit.

Це працювати не буде:

import { ViewChild, OnInit } from ‘@angular/core’;
@Component({})
export class ExampleComponent implements OnInit{
@ViewChild(‘myDiv’) divReference;
constructor(){
let ex = this.divReference.nativeElement; // divReference is undefined
}
ngOnInit(){
let ex = this.divReference.nativeElement; // divReference is undefined
}
}

Рішення

Як подія DOMContentLoaded або $(document).ready() колбек в jQuery, Angular має такий же механізм повідомлення про те, що всі елементи HTML створені. Він називається хук життєвого циклу ngAfterViewInit . Цей колбек вам і потрібно використовувати. Він спрацьовує, коли всі уявлення компонентів і дочірні подання ініціалізується. Так безпечно (майже) отримувати доступ до ссылке viewChild всередині колбека.

import { ViewChild, AfterViewInit } from ‘@angular/core’;
@Component({})
export class ExampleComponent implements AfterViewInit {
@ViewChild(‘myDiv’) divReference;
ngAfterViewInit(){
let ex = this.divReference.nativeElement; // divReference is NOT undefined
}
}

Ура, все працює. Але стійте. Є ще одна пастка. Як я сказав раніше, можна отримати доступ тільки до вже створених елементів. Далі ми дізнаємося, що елементи з директивою *ngIf, яка повертає false, повністю видаляються з DOM. Тобто ми не можемо отримати до них доступ в такому випадку.

Щоб запобігти падіння додатка, необхідно перевірити посилання на значення null. І до речі, рада відноситься не тільки до компонентів або Angular, але і до будь-якої мови програмування.

import { ViewChild, AfterViewInit } from ‘@angular/core’;
@Component({})
export class ExampleComponent implements AfterViewInit {
@ViewChild(‘myDiv’) divReference;
ngAfterViewInit(){
let ex;
if( this.divReference ){
ex = this.divReference.nativeElement; // divReference is NOT undefined
}
}
}

3. Не маніпулюйте DOM напряму — Angular Universal

Пряме маніпулювання DOM в Angular не тільки не заохочується, але і може привести до відмови додатки працювати в різному, відмінному від браузера. Самий популярний приклад — Angular Універсальний проект, який дозволяє робити рендер програми на сервері. Але навіщо це взагалі робити? Прочитайте «все про Angular універсальний і серверному рендері в цьому покроковому керівництві». Цей приклад працювати не буде

import { ViewChild, AfterViewInit } from ‘@angular/core’;
@Component({})
export class ExampleComponent implements AfterViewInit {
@ViewChild(‘myDiv’) divReference;
ngAfterViewInit(){
let ex = this.divReference.nativeElement;
ex.style.color = ‘red’; // does not work on the server
}
}

Рішення

Замість прямого зміни елементів необхідно маніпулювати ними опосередковано. Angular пропонує API у формі класу Renderer2. Так, 2 означає «міжнародний», і так, був Renderer (1). Не кращу назву, але що є.

З допомогою renderer можна робити все, що і раніше при роботі з DOM. Однак при роботі з renderer ми точно знаємо, що наш код працює на сервері так само, як і у клієнта. Ось як вирішується ця проблема:

1. Отримайте об’єкт Renderer2, запросивши його через Dependency Injection в конструкторі

import { ViewChild, Renderer2 } from ‘@angular/core’;
@Component({})
export class ExampleComponent{
@ViewChild(‘myDiv’) divReference;
constructor(private renderer: Renderer2){
}
}

2. Маніпулюйте DOM побічно за допомогою renderer. Перевірте, щоб посилання на елементи існували.

import { ViewChild, Renderer2, AfterViewInit } from ‘@angular/core’;
@Component({})
export class ExampleComponent implements AfterViewInit{
@ViewChild(‘myDiv’) divReference;
constructor(private renderer: Renderer2){
}
ngAfterViewInit(){
if(this.divReference)
this.renderer.setStyle(this.divReference.nativeElement, ‘color’, ‘red’);
}
}

У Renderer2 багато різних методів зміни елементів. Багато з них схожі на JavaScript DOM API. Вгадати, що вони роблять, не повинно викликати проблем. Повний список методів можна знайти в офіційній документації.

4. Уникайте дублюючих провайдерів, перезаписывающих один одного

Ви могли чути, що Angular використовує концепцію dependency injection. З допомогою dependency injection можна запитувати об’єкти сервісів в конструкторі.

Щоб це запрацювало, сервіси або більш широкі ін’єкції необхідно реєструвати в секції провайдера компонента або декоратора модуля. Найпоширеніший метод – надати його на рівні модуля.

Чому не працює ваше додаток Angular: 11 основних помилок

Проблема в тому, що Angular використовує ієрархічну систему ін’єкції залежностей. Тобто сервіси/ін’єкції в кореневому модулі (AppModule) доступні всім компонентам в цьому модулі. Так як цей модуль повинен містити всі інші компоненти та модулі, сервіси доступні у всьому додатку.

Чому не працює ваше додаток Angular: 11 основних помилок

Якщо ви створюєте сервіс для подмодуля, він буде доступний тільки для цього подмодуля. Також якщо ви створюєте сервіси в обох модулях, компоненти подмодуле отримують об’єкт сервісу, відмінний від об’єкта будь-якого іншого компонента. Це може призвести до будь-якого виду помилок, якщо ви думаєте, що ваш сервіс створений один об’єкт у додатку (singleton).

Рішення просте. Створюйте сервіси один раз в AppModule. Якщо ви не знаєте, що робити, дотримуйтеся такого підходу. Особливо на початку. У 99% випадків він буде працювати.

5. Angular Guards — не функція безпеки

Angular Guards – відмінний спосіб штучно обмежити доступ до певних роутам. Наприклад, для перевірки авторизації користувачі ще до показу сторінки. Приклад такого guard:

import { Injectable } from ‘@angular/core’;
import { AuthenticationService } from ‘./authentication.service’;
import { CanActivate } from ‘@angular/router’;
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthenticationService) {}
canActivate() {
return this.authService.isAuthenticated();
}
}

Так як guard не вами, його також потрібно надати.

@NgModule({
providers: [
AuthGuard,
AuthenticationService
]
})
export class AppModule {}

Залишилося сказати йому, які роуты захищати:

@NgModule({
imports: [
RouterModule.forRoot([
{
path: «,
component: SomeComponent,
canActivate: [AuthGuard]
])
],
providers: [
AuthGuard,
AuthenticationService
]
})
export class AppModule {}

Проблема

Так що ж проблема з guards? Правда в тому, що проблем з ними немає!

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

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

Рішення

Якщо необхідно захистити будь-які важливі дані, необхідно мати справжню захист на сервері. Наприклад, пописанный JavaScript Web Tokens.

6. Оголошуйте компоненти тільки один раз

Щоб компоненти працювали в Angular, вони повинні бути оголошені в модулі. Так як у нас один модуль (AppModule), і ми реєструємо компоненти всередині нього, це не проблема.

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

Компонент можна оголошувати лише в одному модулі!

Це майже все, що потрібно. Але що робити, якщо нам необхідно використовувати компонент в декількох модулях?

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

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

@NgModule({
imports: [
CommonModule
],
declarations: [
LoginComponent,
RegisterComponent,
HelpComponent
],
exports:[
LoginComponent,
RegisterComponent,
HelpComponent
]
})
export class AuthenticationModule { }

7. Прискорте додаток з допомогою *ngIf замість атрибута [hidden]

Чергова поширена помилка – плутанина з *ngIf і [hidden]. Правильний вибір може підвищити продуктивність. Давайте розглянемо обидві техніки.

Атрибут [hidden]

Атрибут hidden перемикає видимість елемента. Як ми і очікуємо, так адже? Тобто якщо задати [hidden] в true, властивість CSS display задається в none. Після цього елемент стає невидимим, але присутня в DOM.

Проблема з атрибутом hidden в тому, що виключене CSS властивість можна легко переписати іншим властивістю, причому випадково. Наприклад, якщо ви поставили елементів display block, воно перепише властивість display: none. То є елемент буде завжди видимим.

Спасибі Kara Erickson, вона вказала на цю проблему. Більш детально по цій темі можна дізнатися у її чудовій статті!

Інша теоретична проблема – всі елементи залишаються в DOM, хоча вони невидимі. Якщо мова йде про сотні або тисячі елементів, вони можуть сповільнити браузер. Так чому б не видаляти їх, якщо вони не потрібні?

Директива *ngIf

Основна відмінність директиви *ngIf в тому, що замість приховування елементів вона повністю видаляє їх з DOM. Крім можливого приросту продуктивності це рішення також чистіше. Але це лише моя думка. Цей спосіб схожий на стандартний спосіб приховування елементів в Angular. Тому я майже завжди використовую *ngIf.

Недоліки директиви *ngIf – її складно дебажити, так як віддалений елементи вже не можна інспектувати DOM браузера.

8. Уникайте проблем з обслуговуванням при оборачивании в сервіси

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

Загальна порада – завжди виймайте базову бізнес-логіку в сервіси. Так код легше обслуговувати, так як його можна зняти і замінити на новий підхід за кілька секунд.

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

Цієї поради варто обов’язково дотримуватись при використанні HttpClient. Він завжди повинен бути загорнутий в централізований сервіс. Так він не тільки залишається тестопригодным, але також так в нього легко можна вносити зміни. Уявіть, що ваш backend після недавнього оновлення вимагає передавати з кожним запитом новий заголовок. Без централізованого сервісу вам доведеться шукати всі порушені рядка коду по всьому додатком. Не потрібно говорити, що це вкрай не оптимально.

Замість цього завжди обертайте http-запити сервіси. У гіршому випадку це ніяк вам не зашкодить. В кращому – це заощадить вам (і команді) годинник на найпростіших завданнях.

9. Підвищіть продуктивність і зменшіть розмір з допомогою AOT в продакшн

Запустіть Angular CLI додаток через

ng serve

або

ng build

Додаток збереться в звичайному режимі. Тобто додаток доставлено в браузер таким, яке воно є. Далі браузер повинен виконати компілятор Angular, щоб конвертувати компоненти і шаблони в виконуваний JS код. Цей процес не тільки займає багато часу, але і вимагає, щоб з додатком доставлявся компілятор. У поточній версії Angular компілятор важить приклад 1Мб (167Кб в стислому вигляді). Це багато!

Чому не працює ваше додаток Angular: 11 основних помилок

Не вірите? Можете самі проаналізувати збірку Angular з допомогою webpack-bundle-analyzer. Вам лише потрібно створити бандл з параметром stats-json.

ng build —stats-json

Далі запустіть аналізатор бандла:

webpack-bundle-analyzer dist/stats.json

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

Рішення

Використовуйте компіляцію AOT (Ahead of Time). В режимі AOT програма компілюється в момент складання. Браузеру вже цього робити не потрібно. Ми робимо це один раз на весь час роботи з додатком. В результаті програма стартує набагато швидше.

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

В старих версіях angular-cli AOT потрібно було включати вручну в продакшн білдах. З недавнього часу AOT активний за замовчуванням в продакшн білдах. Вам лише потрібно додати прапор продакшн в команду білду. Ви отримуєте не тільки AOT компіляцію, але і зменшуєте розмір пакету. Старі версії

ng build —prod —aot

Нові версії

ng build —prod

Ось так виглядає продакшн бандл. У стислому вигляді розмір пакету становить приблизно 55Кб. Від 330Кб до 55Кб. Ось це я називаю поліпшенням! Також зверніть увагу, що компілятор не включений у збірку.

Чому не працює ваше додаток Angular: 11 основних помилок

10. Підтримуйте маленький вага додатки, імпортуючи тільки те, що потрібно

Наступний рада пов’язаний з попереднім. Ми знову будемо дивитися на розмір пакету. В цей раз моя порада – будьте обережні при імпорті. Кожен вираз імпорту збільшує розмір пакету. Тепер зрозуміли? Чим більше коду, тим більше розмір.

Проблема в тому, що деякі бібліотеки величезні. При неправильному імпорті можна підключити всі бібліотеки в додаток. Нижче показана популярна помилка – імпорт цілої бібліотеки RxJs в додаток.

import ‘rxjs’;

Ця маленька трока подвоює розмір програми.

Чому не працює ваше додаток Angular: 11 основних помилок

Суть в тому, що додатковий вагу в цьому випадку зовсім не обов’язковий. Порівняйте цей пакет з попереднім, і ви помітите, що там так само підключений RxJs. Різниця в те, що в попередніх бандлах підключені тільки необхідні модулі. За допомогою цього імпорту ми імпортували взагалі все.

Рішення

Виходів кілька. Основні рішення – зважити всі додані в проект бібліотеки. Вам реально потрібна ця блискуча кнопка, яка додає 100Кб? Бібліотека пропонує подмодули, з допомогою яких можна імпортувати тільки необхідний код? Якщо ні, можливо, воно того не варто.

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

Як же імпортувати тільки те, що потрібно? Давайте розберемо приклад RxJs. RxJs розбив майже все в свої модулі. Це змусить вас писати багато импортов, але вага додатка буде маленьким. Наприклад, вам потрібно імпортувати всі використовувані оператори:

import ‘rxjs/add/вами/of’;
import ‘rxjs/add/operator/map’;
import ‘rxjs/add/operator/switchMap’;

Ніколи не робіть так:

import ‘rxjs’;

На жаль, не всі бібліотеки розбивають свій код. І всі вони роблять це по-різному. Більш ретельне вивчення своїх бібліотек – ключ до маленького і швидкому додатком.

11. Не створюйте витоків пам’яті – отписывайтесь від підписок

При роботі з RxJs Observables і Subscriptions легко можна отримати витік пам’яті. Це відбувається тому, що ви знищили компонент, а функцію, зареєстровану в вами, немає. Так ви не тільки отримуєте витік пам’яті, але також можете зіткнутися з дивною поведінкою.

Рішення

Щоб уникнути подібних ситуацій, отписывайтесь від підписок при знищенні компонента. Це зручно робити всередині ngOnDestroy. Приклад:

@Component({})
export class ExampleComponent implements OnDestroy{
private subscriptions = [];
constructor(){
this.subscriptions.push(this.anyObservable.subscribe());
}
ngOnDestroy(){
for(let subscription of this.subscriptions){
subscriptions.unsubscribe();
}
}
}

Висновок

У цій статті ми розібрали всі помилки, з якими часто стикаються новачки. Принаймні, я робив їх… Сподіваюся, мої помилки допомогли вам повністю уникнути їх і забезпечили вам кращий досвід розробки на Angular.