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>