Содержание
Основная часть работы в Angular выполняется в компонентах. Как минимум у вас должен быть один компонент, который называется корневым. Мы уже создали два компонента в нашем приложении: app.component.ts
(он и есть корневой) и all-products.component.ts
. Корневой компонент определяется в файле app.module.ts
, в свойстве bootstrap
декоратора @NgModule
По большому счету можно создать всего один компонент и писать весь код только в нем. Но в средних и больших проектах принято разбивать функциональность на разные компоненты, каждый из которых будет отвечать за какой-то определенный функционал. В нашем приложении для удобства и наглядности был один компонент, который содержит и таблицу с товарами и форму для добавления новых товаров. В этом разделе мы перенесем форму в отдельный компонент.
Компонент это обычный класс, пока к нему не будет применен декоратор @Component()
с метаинформацией. Основные метаданные мы рассмотрели в этой главе, не так часто используемые:
- template — возможно задание шаблона прямо в декораторе. В таком случае не нужно будет создавать отдельный файл для шаблона. Недостаток в том, что для более менее больших шаблонов, даже около 15-20 строк кода, этим вариантом пользоваться неудоно
- styles — то же, что и с шаблоном — определения стилей прямо в файле компонента.
- providers — инициализация сервисов, которые будут использоваться в данном компоненте.
- animations — задание анимации для данного компонента
С полным списком свойств данного декоратора можно ознакомиться в официальной документации.
Также в предыдущих главах мы говорили о том, что есть 2 способа создания компонентов. Один из них — вручную создать файл компонента, файлы шаблона и стилей и затем импортировать и подключить в файле app.module.ts
. Если же вы используете в проекте Angular-CLI, то гораздо удобнее и быстрее создавать компоненты с помощью специальной команды: ng g c component-name --spec false
. В таком случае Angular за нас создаст необходимые файлы и подключит их в модуле.
Подготовка проекта
Вы можете скачать уже готовый проект с изменениями в шаблоне тут. Или проделать это самостоятельно. Вкратце опишу что нужно сделать.
- Для начала нам нужно создать новый компонент. Создаем папку
add-product
, в ней запускаем командную строку (правый клик и в контекстном меню выбираем Git Bash Here), запускаем командуng g c add-product --spec false
- Перенести из шаблона компонента all-products.component.html код отвечающий за форму (начиная с
div
с классамиcol-md-12 mt-2 add-new-item
) - Перенести стили из файла
all-products.components.css
- Перенести логику из основного файла
all-products.components.ts
(импорты классов для группFormControl, FormGroup, Validators
, определение переменнойform
, инициализация формы в ngOnInit, методыcheckLength()
иonSubmit()
)
Передача данных между компонентами Angular
Теперь у нас есть 3 компонента:
- app — это основной компонент нашего приложения, в котором подключены два других компонента (т.е. это родительский компонент).
- all-products — это компонент, который отвечает за вывод всех доступных товаров (дочерний компонент, child component)
- add-product — компонент с формой добавления нового товара (дочерний компонент, child component)
Как теперь выглядит наше приложение
Сейчас мы только для примера, чтобы показать как происходит обмен данными между компонентами, добавим следующий функционал. В файле шаблона основного компонента добавим следующий код:
<app-all-products [clickCount]="counter"></app-all-products> <app-add-product (clickChange)="onClicked($event)"></app-add-product>
Затем в typescript файле основного компонента, добавим следующий код:
... export class AppComponent { counter: number = 0; public onClicked(counter){ this.counter = counter; } }
Теперь добавим изменения в компонент all-products
. Начнем с шаблона:
<div class="container"> <div class="row"> <h3 class="col-md-12 mr-3 mt-2">{{ title }}</h3> <p class="col-md-12">Вы кликнули {{ clickCount }} раз</p> ...
Тут мы будем выводить количество кликов по кнопке, которую мы позже добавим к нашей форме.
Для того, чтобы компонент мог принимать какие-то данные из другого компонента, нам прежде всего нужно импортировать декоратор @Input
и затем объявить входное свойство с помощью этого декоратора:
import { Component, OnInit, Input } from '@angular/core'; import { DataService } from '../shared/data.service'; @Component({ selector: 'app-all-products', templateUrl: './all-products.component.html', styleUrls: ['./all-products.component.css'] }) export class AllProductsComponent implements OnInit { title = 'Каталог товаров'; @Input() clickCount: number; ...
Сейчас давайте добавим изменения в наш третий компонент с формой:
... <button class="btn btn-success" type="submit" [disabled]="!form.valid">Добавить</button> <button class="btn btn-info ml-2" type="submit" (click)="clickMe()">Click me</button> ...
Вот это кнопка, клики по которой мы будем отслеживать. Теперь нам еще нужно добавить в файл компонента декоратор @Output
для привязки к событию компонента:
import { Component, OnInit, Output, EventEmitter } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; ... clickCount: number = 0; @Output() clickChange: EventEmitter<number> = new EventEmitter(); clickMe() { this.clickCount++; this.clickChange.emit(this.clickCount); } ...
Теперь при клике по одному компоненту, значение переменной передается в другой компонент.
Элемент <ng-content>
Если у вас возникла необходимость добавить контент (html или просто текст) в родительском элементе, но он должен отображаться в дочернем, тогда можно воспользоваться компонентом ng-content
.
Давайте заголовок формы «Добавление нового товара» перенесем в родительский компонент — app.
<app-all-products [clickCount]="counter"></app-all-products> <app-add-product (clickChange)="onClicked($event)"> <h4 class="text-center">Добавление нового товара</h4><hr> </app-add-product>
А в компоненте формы вместо этой строки добавим элемент ng-content
<div class="container"> <div class="col-md-12 mt-2 add-new-item"> <ng-content></ng-content> <form [formGroup]="form" (ngSubmit)="onSubmit()"> ...
После этих изменений, если вы взглянете на наше приложение, то ничего не изменилось. Все работает без ошибок, но теперь контент выводится из родительского элемента в дочернем.
@ContentChild
Бывают ситуации, когда вам может понадобиться получить значение какого-то элемента (или элемент целиком) из родительского в дочернем. В этом нам поможет декоратор @ContentChild
.
Давайте из нашего примера получим заголовок формы в наш компонент. Изменим шаблон основного компонента, добавив ему локальную переменную #test
.
<app-all-products [clickCount]="counter"></app-all-products> <app-add-product (clickChange)="onClicked($event)"> <h4 class="text-center" #test>Добавление нового товара</h4><hr> </app-add-product>
Для того, чтобы воспользоваться вышеупомянутым декоратором, нам нужно импортировать его и ElementRef
из библиотеки Angular
import { Component, OnInit, Output, EventEmitter, ContentChild, ElementRef } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; ... ... form: FormGroup; @ContentChild('test') test: ElementRef; ngOnInit() { this.form = new FormGroup({ title: new FormControl('', [Validators.required, this.checkLength]), details: new FormGroup({ price: new FormControl('100', [Validators.min(1), Validators.max(1500)]), quantity: new FormControl('1', Validators.max(100)) }) }); console.log(this.test.nativeElement.innerText); } ...
В качестве свойства этому декоратору нужно предать имя нашей локальной переменной. Также задать имя переменной, которое мы хотим использовать в нашем компоненте, я задал также test, но можно использовать любое другое. Тип данной переменной должен быть ElementRef
.
В методе ngOnInit
выведем в консоль значение нашего тега. Если вы выведите в консоль просто this.test
, то сможете посмотреть сколько у него разных свойств. Можно получить доступ и к классам, и к атрибутам элемента и т.д. Ну и конечно же вы можете изменять переменную ‘test’ так как вам нужно.
@ViewChild
Для того, чтобы использовать локальную переменную в родительском файле компонента, мы можем воспользоваться декоратором @ViewChild
. Мы ту же переменную test теперь выведем в консоль в AppComponent.
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { counter: number = 0; public onClicked(counter){ this.counter = counter; } @ViewChild('test') test: ElementRef; ngOnInit() { console.log(this.test); } }
Если вы теперь заглянете в консоль, то тут как раз и будет ElementRef
. Там очень много различных свойств.
Жизненный цикл компонента
При загрузке компонента, он имеет несколько этапов, которые срабатывают поочередно. Эти этапы называются жизненными циклами. Вам нужно понимать какие из них наступают раньше, а какие позже.
Самый первый сработает конструктор нашего класса constructor() {}
. Давайте добавим в наш компонент App, все методы и импортируем каждый из библиотеки Angular, а затем рассмотрим, для чего они нужны. Также мы удалим код в этом компоненте, который мы добавляли ранее для примера (и лучше удалить код из шаблона и файла компонента AddProduct — тот где мы использовали @ContentChild и вывод переменной test).
import { Component, OnInit, OnChanges, SimpleChanges, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy { counter: number = 0; public onClicked(counter){ this.counter = counter; } constructor() { console.log('constructor') } ngOnInit() { console.log('ngOnInit') } ngOnChanges(changes: SimpleChanges) { console.log('ngOnChanges', changes) } ngDoCheck() { console.log('ngDoCheck') } ngAfterContentInit() { console.log('ngAfterContentInit') } ngAfterContentChecked() { console.log('ngAfterContentChecked') } ngAfterViewInit() { console.log('ngAfterViewInit') } ngAfterViewChecked() { console.log('ngAfterViewChecked') } ngOnDestroy() { console.log('ngOnDestroy') } }
Вот результат данного вывода в консоль, в порядке выполнения:
Здесь не хватает только метода onDestroy
, он срабатывает последним и чаще всего используется, чтобы «убить» процессы прослушки.
Теперь давайте рассмотрим каждый метод:
Имя | Описание |
---|---|
ngOnInit | Производит инициализацию свойств и выполняется один раз при запуске компонента. |
ngOnChanges | Этот метод вызывается при изменении значений свойств, а также перед ngOnInit. Может принимать параметр типа SimpleChanges, в таком случае можно будет получить как текущее значение так и предыдущее |
ngDoCheck | Вызывается, когда Angular запускает процесс обнаружения изменений |
ngAfterContentInit | Вызывается один раз, после инициализации контента (метода ngDoCheck ) |
ngAfterContentChecked | Метод вызывается после анализа контента директивы в процессе обнаружения изменений |
ngAfterViewInit | вызывается фреймворком Angular после инициализации представления компонента, а также представлений дочерних компонентов. |
ngAfterViewChecked | вызывается фреймворком Angular после проверки на изменения в представлении компонента, а также проверки представлений дочерних компонентов |
ngOnDestroy | Метод вызывается непосредственно перед тем, как Angular уничтожает компонент |
Методы ngDoCheck
, ngAfterContentChecked
и ngAfterViewChecked
будут срабатывать несколько раз.
Добавить комментарий