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>

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *