1. Как создать приложение
Установка CLI
npm install -g @angular/cli
Создание нового проекта
ng new my-first-project
cd my-first-project
ng serve
Либо webstorm сам может запустить сборку проекта
2. Обзор всех папок и файлов
В базовом приложении имеются несколько основных сущностей, которые влияют на работу приложения:
tsconfig
поделён на несколько файлов, в которых отдельно настраивается общее поведение, работа приложения и тесты- в
angular.json
находятся настройки компиляции ангуляра, указаны пути, полмифилы и все остальные специфики main.ts
иindex.html
- два основных корневых файлаenvironments
- хранит в себе настройки окружения
Конкретно в папке с компонентом располагается всего две сущности - component и module. Первый относится полностью к самому отображаемому компоненту - логика операций, отображение, стили. Второй относится к настройке модульной системы ангуляра
3. Передача параметров из компонентов
Стартовая точка приложения
main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { enableProdMode } from '@angular/core';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic() // запускает платформу браузера
.bootstrapModule(AppModule) // сюда мы передаём корневой компонент приложения
.catch((err) => console.error(err)); // если вылезет ошибка
В модуле мы определяем метаданные и определённые структурные моменты компонента
app > app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent, // декларируем компонент
],
imports: [
BrowserModule, // импортируем модуль браузера
],
providers: [],
bootstrap: [AppComponent], // указываем, что этот компонент будет стартовой точкой для приложения
})
export class AppModule {}
В самом компоненте мы указываем имя компонента, которое будет использоваться для добавления компонента в шаблоны, ссылки на шаблон и стили компонента
app > app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root', // указываем селектор, которым сможем вызывать компонент в других компонентах
templateUrl: './app.component.html', // указываем место темплейта
styleUrls: ['./app.component.scss'], // массив стилей компонента
})
export class AppComponent {
title = 'learn-angular';
}
И уже таким образом вставляется имя селектора в другой шаблон, чтобы добавить компонент
index.html
<body>
<app-root></app-root>
</body>
4. Создание своего компонента
Создаём дочерний компонент и указываем все импорты в него других файлов
app > post > post.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-post',
templateUrl: './post.component.html',
styleUrls: ['./post.component.scss'],
})
export class PostComponent {}
app > post > post.component.html
<h2>Post</h2>
Далее нужно задекларировать дочерний компонент в компоненте родителя
app > app.component.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { PostComponent } from './post/post.component';
@NgModule({
declarations: [
// декларируем компонент
AppComponent,
PostComponent,
],
imports: [
BrowserModule, // импортируем модуль браузера
],
providers: [],
bootstrap: [AppComponent], // указываем, что этот компонент будет стартовой точкой для приложения
})
export class AppModule {}
И теперь дочерний компонент можно использовать внутри родительского
app > app.component.html
<h1>Angular App</h1>
<app-post/>
5. Создание компонента с Angular CLI
Полная команда и сокращённая команда, а так же мы можем отключить добавление тестов
ng g c list --skipTests
ng generate component list
В итоге мы получим папку с компонентом, бойлерплейт и компонент добавиться в зависимости корневого компонента
6. Шаблоны и стили
Так же можно создавать шаблоны прямо в декораторе без вынесения его в отдельный файл.
Такой подход стоит использовать, если компонент очень маленький
app > button > button.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-button',
template: ` <button class="button">Удалить</button> `,
styles: [
`
.button {
padding: 10px;
color: aqua;
border: 1px solid aqua;
background: none;
}
`,
],
})
export class ButtonComponent {}
7. Интерполяция
Чтобы поместить в шаблон динамические значения, мы можем воспользоваться интерполяцией {{ значение }}
, внутри которой передать поля класса компонента
app > app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root', // указываем селектор, которым сможем вызывать компонент в других компонентах
templateUrl: './app.component.html', // указываем место темплейта
styleUrls: ['./app.component.scss'], // массив стилей компонента
})
export class AppComponent {
title: string = 'learn-angular';
number: number = 42;
}
app > app.component.html
<h1>Angular App</h1>
<h2>{{title}}</h2>
<p>{{number - 2}}</p>
<app-post/>
<app-button></app-button>
8. Что такое bindings
Так же если мы привязываем какой-то атрибут к динамическим данным (например, класс, который будет меняться (мобильный адаптив), или ссылка src
будет меняться), то вместо использования интерполяции {{ }}
предпочтительнее оборачивать сам атрибут в [атрибут]='переменная'
квадратные скобки
Такой подход используется, так как он обеспечивает более оптимизированную смену значений в html
Конкретно тут связывание данных происходит от TS в HTML
app > app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root', // указываем селектор, которым сможем вызывать компонент в других компонентах
templateUrl: './app.component.html', // указываем место темплейта
styleUrls: ['./app.component.scss'], // массив стилей компонента
})
export class AppComponent {
styleClass = 'generic';
constructor() {
setTimeout(() => {
this.styleClass = 'separate';
}, 1500);
}
}
app > app.component.html
<div [class]='styleClass'></div>
9. Как работает Event Bindings
Далее нужно будет познакомиться с концепцией, когда мы связываем данные из HTML в TS
Для связывания через ивенты используется конструкция (click)='onInput($event)'
, где мы оборачиваем событие в круглые скобки ()
, а для того, чтобы передать сам ивент в метод, используем $event
. Ивент внутри методов нужно типизировать через использование дженерика (<HTMLInputElement>event.target).value
, где мы опишем чем является элемент ивента
Так же есть уточняющие события, как например, если мы будем использоваться ивент keydown
, то к нему через точку можно приписать enter
, что будет вызывать срабатывание ивента только при нажатии enter
Так же мы можем сократить запись передачи ивента, если передадим ссылку на имя компонента через #myInput
. Такая ссылка позволит сразу воспользоваться myInput.value
и передать внутрь метода просто конечное значение текста
app > app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root', // указываем селектор, которым сможем вызывать компонент в других компонентах
templateUrl: './app.component.html', // указываем место темплейта
styleUrls: ['./app.component.scss'], // массив стилей компонента
})
export class AppComponent {
inputValue: string = '';
constructor() {}
onInput(event: Event) {
this.inputValue = (<HTMLInputElement>event.target).value;
}
onBlur(str: string) {
this.inputValue = str;
}
}
app > app.component.html
<h2>Input</h2>
<input type='text' (input)='onInput($event)'>
<p>{{inputValue}}</p>
<h2>Input</h2>
<input type='text' (keydown.enter)='onInput($event)' (blur)='onBlur(myInput.value)' #myInput>
<p>{{inputValue}}</p>
10. 2 Way Binding
Бывают ситуации, когда нужно данные сначала передать из TS в HTML и чтобы пользователь имел возможность эти данные менять и обратно передавать в приложение
Для этого мы можем реализовать двустороннее связывание либо таким классическим образом:
app > app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
title: string = 'Initial';
constructor() {}
onInput(event: any) {
this.title = event.target.value;
}
}
app > app.component.html
<h1>Angular App</h1>
<input type='text' [value]='title' (input)='onInput($event)'>
<p>{{title}}</p>
Либо можно воспользоваться модулем форм, который нужно добавить как импорт в корневой компонент приложения
app > app.module.ts
А затем воспользоваться записью двойного связывания [()]
, в которую и вложить ngModel
, который будет сам отвечать за связывание данных
app > app.component.html
<h1>Angular App</h1>
<input type='text' [(ngModel)]='title'>
<p>{{title}}</p>
11. Директива ngStyle
Директивы - это определённые атрибуты, которые позволяют управлять поведением различных HTML элементов
Конкретно директива [ngStyle]
всегда связывается с данными из TS. Данная директива используется для создания динамических стилей, так как она в себя может принимать инлайн-стили и логику JS
app > app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
backgroundToggle: boolean = false;
constructor() {}
}
app > app.component.html
<h1>Angular App</h1>
<button (click)='backgroundToggle = !backgroundToggle'>Фон</button>
<div [ngStyle]="{
width: '200px',
height: '200px',
borderRadius: '5px',
background: backgroundToggle ? 'gray' : 'blue'
}"></div>
12. Динамические классы с ngClass
У нас есть два способа добавлять классы динамически:
[ngClass]
- принимает в себя объект, где ключом выступает нужный класс, а свойством условие, которое этот класс будет активировать[class.нужный_класс]
- будет навешиваться нужный класс, если будет удовлетворять условию
app > app.component.html
<h1>Angular App</h1>
<button (click)='backgroundToggle = !backgroundToggle'>Фон</button>
<p [ngClass]='{
red: backgroundToggle,
blue: !backgroundToggle,
}'>Lorem ipsum</p>
<p
[class.red]='backgroundToggle'
[class.blue]='!backgroundToggle'
>Lorem ipsum</p>
13. Отображение по условию с ngIf else
В ангуляре присутствуют структурные директивы, которые позволяют изменять структуру HTML-шаблона
Все структурные директивы начинаются со звёздочки *
(как и связывание в одну или другую сторону через []
или ()
)
Самый простой способ организовать отрисовку по условию - это воспользоваться директивой *ngIf
, в которую мы передаём условие (любое, которое поддерживает JS)
app > app.component.html
<h1>Angular App</h1>
<button (click)='toggle = !toggle'>Фон</button>
<p *ngIf='toggle' class='red'>Lorem ipsum</p>
<p *ngIf='!toggle' class='blue'>Lorem ipsum</p>
И сейчас объект реально удаляется из вёрстки, а не просто скрывается
Такой способ не самый эффективный, так как мы два раза проверяем условие
Но, чтобы обозначить условие если, нужно воспользоваться переменной, которая будет ссылаться на конечный нужный элемент.
#переменная
обозначает элемент, который хранит данный блок вёрстки.
Внутрь ng-template
помещаем тот код, который нужно отображать в противном случае и вкладываем в него переменную, которую мы обозначили в условии в else
app > app.component.html
<h1>Angular App</h1>
<button (click)='toggle = !toggle'>Фон</button>
<p *ngIf='toggle; else blueP' class='red'>Lorem ipsum</p>
<ng-template #blueP>
<p *ngIf='!toggle' class='blue'>Lorem ipsum</p>
</ng-template>
14. Директива ngSwitch
Директива ngSwitch
позволяет реализовать switch-case отображение элементов на странице. *ngSwitchCase
позволяет определить элемент, который отобразится, а *ngSwitchDefault
покажет, если ни один элемент не будет подпадать под условие
app > app.component.html
<button (click)='toggle = !toggle'>Фон</button>
<div [ngSwitch]='toggle'>
<p *ngSwitchCase='true' class='red'>Lorem Ipsum</p>
<p *ngSwitchCase='false' class='red'>Lorem Ipsum</p>
<p *ngSwitchDefault>Lorem Ipsum</p>
</div>
15. Циклы с ngFor
Так же мы имеем структурную директиву *ngFor
, которая позволяет проитерировать массив и вывести элементы в цикле
Первым делом создадим объекты, которые нужно будет проитерировать
app > app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
arr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
objects = [
{
title: 'post 1',
author: 'valka',
comments: [
{ author: 'max 1', text: 'text 1' },
{ author: 'max 1', text: 'text 1' },
{ author: 'max 1', text: 'text 1' },
],
},
{
title: 'post 2',
author: 'valka 2',
comments: [
{ author: 'max 2', text: 'text 1' },
{ author: 'max 2', text: 'text 1' },
{ author: 'max 2', text: 'text 1' },
],
},
];
constructor() {}
}
Далее внутри директивы нам нужно просто использовать итерацию через for-of
и через интерполяцию вывести по имени переменной объекты на странице. Если нам нужен будет индекс в итерации, то мы можем через ;
присвоить переменной зарезервированное имя index
.
Во втором примере показан вывод вложенного массива
`app > app.component.html
<h1>Angular App</h1>
<ul>
<li *ngFor='let n of arr; let i = index'>{{i}}. <strong>{{n}}</strong></li>
</ul>
<ul>
<li *ngFor='let o of objects'>
<small>{{o.title}} </small> <strong>{{o.author}}</strong>
<ul *ngFor='let c of o.comments'>
<small>{{c.author}}</small>
<p>{{c.text}}</p>
</ul>
</li>
</ul>
16. Что такое pipes
Пайпы представляют собой определённые форматтеры тех данных, которые мы передаём в интерполяции. Добавлять пайпы можно через |
. Конкретно пайп date
позволяет настроить выходной формат даты, а uppercase
переводит всё в верхний регистр
`app > app.component.html
<h1>Angular App</h1>
<p>{{ now | date: 'long' | uppercase }}</p>