Angular (основы)
Структурные директивы – те, которые меняют html. Они начинаются со звездочки, например *ngIf
, *ngFor
Опрокидывание данных вниз дочерному компоненту
Внутри html-шаблона родительского компонента вот так объявляем дочерный и передаем свойство либо результат функции из класса родительского компонента someData:
<child-component [item]="someData"></child-component>
А в классе дочерного компонента принимаем пропсы, используя директиву @Input()
:
import { Component, Input } from '@angular/core'; export class ItemDetailComponent { @Input() item: string; }
Здесь для item назначился тип «строка» автоматически, но правильнее указать еще тип и вручную дополнительно. Если использовать что-то более сложное, то можнно экспортировать интерфейс для типизации из родительского компонента (ведь там он тоже будет использоваться для типизации) либо непосредственно прописать что-то вроде @Input() item: {type: string, name: string, id: number}
.
Теперь item можно использовать в html шаблоне дочерного компонента.
В Ангуляре есть еще интересная особенность, которую обеспечивает директива @Input. Например, если имя входящего пропса отличается от желаемого, то его можно указать внутри аргументов @Input. В итоге в этом классе пропс будет находится в переменной с новым именем. Например, к нам пришел пропс fooItem, а мы хотим чтобы в этой компоненте он использовался как просто item:
@Input('fooItem') item: string;
Для отслеживания изменений в передаваемом пропсе можно использовать хук OnChanges, как говорится в документации.
Передача данных вверх родительскому компоненту
1. В дочерном компоненте при помощи директивы @Output создаем событие, которое можно будет слушать снаружи компонента, «поставив» на него слушатель. Это событие будет экземпляром класса EventEmitter, который импортируется как и сама директива с @angular/core:
import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core'; import { RoomEdit } from '../rooms.component'; //интерфейс импортирован из родителя @Component({ selector: 'app-room', templateUrl: './room.component.html', styleUrls: ['./room.component.scss'], }) export class RoomComponent implements OnInit { @Output() roomAction = new EventEmitter<RoomEdit>(); //создали кстомное событие title: string = this.room.title; number: number | null | undefined = this.room.number; onRoomAction(action: string) { if (action === 'save') { this.requireValidate(); } this.roomAction.emit({ //при клике выполнили событие idRoom: this.index, action: action, title: this.title, number: this.number, }); } constructor() {} ngOnInit(): void { this.title = this.room.title; this.number = this.room.number; } }
Здесь в примере мы генерацию этого события включаем при срабатывании функции onRoomAction, которая просто повешена на кнопку в шаблоне дочерного компонента:
<div class="room_action"> <div (click)="onRoomAction('save')" class="room_action-item">SAVE ROOM</div> <div (click)="onRoomAction('delete')" class="room_action-item">DELETE ROOM</div> <div class="room_action-item"><span class="bracket"> > </span></div> </div> </div>
2. Теперь в родительском компоненте мы можем слушать событие onRoomAction, и реагировать на него:
<app-room (roomAction)="onRoomAction($event)"> </app-room>
В $event приходит то, что мы передавали в свойство emit при генерации события в дочерном компоненте. В данном примере это объект с названием и номером комнаты, а также название действия. Благодаря передачи действия, название которого приходит из html шаблона, я просто сделал универсальной эту генерацию (не пришлось разбивать на отдельные события для удаления, редактирования, очистки комнаты и т.д.).
3. По аналогии с @Input в директиве @Output есть возможность назначить псевдоним в параметрах. Тогда слушать надо будет то название, которое указали в скобках:
@Output(fooRoomAction) roomAction = new EventEmitter<{title: string; number: number}>();
Отображение и скрытие блоков
if
Для этого применяется структурная директива *ngIf
.
<button (click)="toggleCards()">Toogle cards</button> <!-- <app-form></app-form> --> <div *ngIf="toggle"> <app-card></app-card> </div>
toogle – переменная внутри класса со значением true по умолчанию. А метод toggleCards меняет значение на противоположное:
import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) export class AppComponent { toggle = true; toggleCards() { this.toggle = !this.toggle; } }
if else
У Ангуляра необычный синтаксис для этого. Необходимо в самой директиве добавиться else с названием блока, который будет выводится вместо текущего:
<button (click)="toggleCards()">Toogle cards</button> <!-- <app-form></app-form> --> <div *ngIf="toggle; else noCards"> <app-card></app-card> </div> <ng-template #noCards> <p>Cards are hidden!</p> </ng-template>
switch
Зависимо от значени в переменной (свойства) в классе компонента можно отображать или скрывать группы блоков:
<div [ngSwithc]="value"> <p *ngSwitch="5">Value is 5</p> <p *ngSwitch="10">Value is 10</p> <p *ngSwitch="100">Value is 100</p> <p *ngSwitchDefault>Value is Default</p> </div>
value – значение внутри класса компонента.
Динамические стили
Для этого применяется директива ngStyle, которая принимает js-объект в качестве значения:
<h2 [ngStyle]="{ color: title.length <= 5 ? 'blue' : 'red' }">{{ card.title }}</h2>
<div [ngStyle]="{invisible: visibility}"
visibility – переменная в классе компонента, в которой каким-либо методом переключается значение с true / false.
Динамические классы
При помощи передачи объекта:
<p class="text" [ngClass]="{ blue: textColor === 'blue', green: textColor === 'green', red: textColor === 'red' }">Some text...</p>
textColor – это переменная (свойство) в классе, относящемся к данному компоненту.
При помощи байдинга атрибута class и указания названия класса после точки:
<p class="text" [class.blue]="textColor === 'blue'" [class.green]="textColor === 'green'" [class.red]="textColor === 'red'">Some text...</p>
Динамические атрибуты
Схожим ообразом можер прсиупать и с атрибутами. Например, мы хотим делать кнопку заблокированной, зависимо от переменной disabled, которая может содержать true либо false.
<button [disabled]="disabled">Enable</button>
Вывод циклом компонент на основе массива данных
1. В родительском компоненте имеем массив объектов cards. Сверху еще до экспорта класса и объявления директивы @Component создаем интерфейс Card для элементов этого массива и экспортируем его, чтобы потом применять для типизации и в дочерном компоненте:
import { Component } from '@angular/core'; export interface Card { title: string; text: string; } @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) export class AppComponent { cards: Card[] = [ { title: 'Card 1', text: 'This is card number 1' }, { title: 'Card 2', text: 'This is card number 2' }, { title: 'Card 3', text: 'This is card number 3' }, ]; }
2. В шаблоне родительского компонента объявляем дочерный компонент с использованием директивы *ngFor, для которой указываем произвольное название элемента цикла (в примере iterCard) и исходный массив из класса компонента (в примере cards). А также в виде пропса card прокидываем элемент card внутрь дочерного компонента объявленный элемент массива.
<div class="container"> <h1>Hello Angular</h1> <hr> <app-card *ngFor="let iterCard of cards; let idx = index" [card]="iterCard" [idex]="idx + 1"></app-card> </div>
let idx = index и сама передача [idex]=»idx» – это не обязательная часть, а просто демонстрация синтаксиса для работы с индексами.
3. В классе дочерного компонента при помощи директивы Input принимаем пропсы и используем для типизации импортируемый интерфейс Card из родительского компонента:
import { Component, Input, OnInit } from '@angular/core'; import { Card } from '../app.component'; @Component({ selector: 'app-card', templateUrl: './card.component.html', styleUrls: ['./card.component.scss'], interpolation: ['{{', '}}'], //можно задать синтаксис интерполяции }) export class CardComponent implements OnInit { @Input() card: Card = { title: '', text: '' }; @Input() index: number = +''; }
4. Остается только вывести полученные пропсы в шаблоне дочерного компонента:
<div class="card"> <h2>{{index}} {{ card.title }}</h2> <p class="text">{{card.text}}</p> </div>
С этими данными можно проводить и различные операции при помощи методов в классе дочерного компонента, обращаясь как this.card.title и this.card.text.
Получение значения из инпута
Допустим, мы хочем значение с инпута связать с переменной title, которая есть в классе компонента. Для этого есть несколько способов.
1. Способ – при помощи нативного event
<input type="text" (input)="inputHandler($event.value)" [value]="title">
inputHandler(value: string) { this.title = value; }
2. Способ – при помощи шаблонной переменной (локальной ссылки)
1) Шаблонные переменные позволяют определить некоторые переменные внутри шаблона компонента и затем ссылаться к этим переменным из этого же шаблона. Они начинаются со знака решетки. В примере мы используем переменную #myInput, с которой и передаем значение в метод для дальнейшей обработки.
<input type="text" #myInput (input)="inputHandler(myInput)" [value]="title">
inputHandler(myInput: HTMLInputElement) { this.title = myInput.value; }
2) Есть еще похожий способ, в котором применяется директива @VievChild. Мы так же само назначаем в htmk шаблоне локальную ссылку, а потом в классе делаем вот так:
import { Component, Input, OnInit, Output, EventEmitter, VievChild, ElementRef } from '@angular/core'; .... @VievChild('myInput') myInput: ElementRef; onAction() { console.log(this.myInput.nativeElement.value) }
3. Способ (при помощи директивы ngModel)
Сначала нужно импортировать в app.module.ts еще один модуль – FormsModule. В этом модуле присутствует функционал, который позволяет работать с формами, включая и дерективу ngModel.
import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; @NgModule({ declarations: [AppComponent], imports: [BrowserModule, AppRoutingModule, FormsModule], providers: [], bootstrap: [AppComponent], })
Директива ngModel, которая относится к атрибутовым, позволяет более коротким синтаксисом делать то же самое.
<input type="text" [(ngModel)]='title' (ngModelChange)="changeHandler()">
changeHandler() { console.log(this.title); }
По сути здесь даже метод changeHandler не обязателен, так как связь с переменной title установлена ранее в строке шаблона.
Пайпы (pipes)
Пайпы – это специальные инструменты, которые позволяют форматировать отображаемые значения прямо в html шаблоне.
Например, если в переменной cardDate лежит new Date(), то непосредственно во время вывода в шаблоне вот таким образом можно получить корректный ее вид:
{{cardDate | date}}
Либо, если в переменной obj лежит обычный объект, то вот так его можно сразу преобразовать в json и вывести на страницу:
{{obj | json}}
Кроме того, в пайпы мы можем передавать различные опции, например:
{{ value_expression | date [ : format [ : timezone [ : locale ] ] ] }}
Сделать стили компонента глобальными, а не инкапсулированынми
Если в дектораторы класса компонента добавить декоратор encapsulation: ViewEncapsulation.None, то все стили текущего компонента потеряют сгенерированные для уникальности атрибуты, благодаря чему они применятся ко всему приложению.
import {Component, EventEmitter, ViewEncapsulation} from '@angular/core'; import { Room, RoomEdit } from '../rooms.component'; @Component({ selector: 'app-room', templateUrl: './room.component.html', styleUrls: ['./room.component.scss'], encapsulation: ViewEncapsulation.None })
Помимо None там есть еще пара других свойст.
Директива ng-content
Эта директива представляет из себя просто открывающийся и закрывающийся тег визуально:
<ng-content></ng-content>
Например, в родительскогос компоненте, где мы вызываем дочерный, мы можем прямо внутри дрчерного (между его тегами) использовать код его шаблона.
app-parent-component.component.html:
<app-doter-component> <p>Сюда вынесли код основной части шаблона дочерного компонента (в т.ч. логика) </p> </app-doter-component>
Но при этом в самом шаблоне нашего дочерного компонента имеются различные оборачивающие дивы, которые мы сюда не выносили, чтобы не загромождать код родителя.
В таком случае мы можем просто внутри дочерного компонента использовать эту директиву ng-content в том месте, откуда забрали код:
app-doter-component.component.html:
<div class="foo"> <div class="bar"> <ng-content></ng-content> </div> </div>