Angular - декораторы и директивы @Input и @Output

Внимание

Данный материал является частью цикла статей «Frontend приложение на Angular». Не забудьте посмотреть другие статьи по этой теме :-)

  1. Angular - Подготовка проекта для нового приложения
  2. Angular - навигация и маршрутизация
  3. Angular - HttpClient, сервисы и файлы окружения
  4. Angular - Ленивая загрузка данных (lazy loading)
  5. Angular - обработка ошибок
  6. Angular - декораторы и директивы @Input и @Output
  7. Angular - валидация формы и POST запрос
  8. Angular - Работа с запросами PUT
  9. Angular - Работа с DELETE запросами

Декораторы @Input и @Output

В ситуациях, когда мы хотим отправить некоторый контент от родительского к дочернему компоненту, нам нужно использовать декоратор @Input в дочернем компоненте, чтобы обеспечить привязку свойств между этими компонентами. Более того, у нас могут быть некоторые события в дочернем компоненте, которые отражают его поведение обратно в родительский компонент. Для этого мы собираемся использовать декоратор @Output с EventEmitter.

Для обработки сообщений об успешном выполнении и сообщений об ошибках (которые не являются сообщениями 500 или 404) мы собираемся создать дочерние компоненты модального окна. Мы собираемся повторно использовать их в каждом компоненте, который требует отображения сообщений такого типа. Если вы хотите зарегистрировать свой повторно используемый компонент, рекомендуется создать общий модуль, а также зарегистрировать и экспортировать свои компоненты внутри этого модуля. Затем вы можете использовать эти повторно используемые компоненты в любом компоненте более высокого уровня, который вы хотите, зарегистрировав общий модуль внутри модуля, ответственного за этот компонент более высокого уровня.

Создание общего модуля

Начнем с создания общего модуля в общей папке:

ng g module shared

Затем изменим файл shared.module.ts:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

@NgModule({
  imports: [
    CommonModule
  ],
  declarations: [
  ],
  exports: []
})
export class SharedModule { }

Затем, включите SharedModule в наш файл owner.module.ts:

import { SharedModule } from './../shared/shared.module';
imports: [
    CommonModule,
    SharedModule,

Модальный компонент ошибки

Выполним команду AngularCLI для создания модального компонента ошибки:

ng g component shared/modals/error-modal --skipTests

Помимо создания файлов компонентов, эта команда импортирует новый компонент в shared-module. Но нам нужно экспортировать его вручную:

declarations: [
    ErrorModalComponent
  ],
  exports: [
    ErrorModalComponent
  ]

В папке modals мы собираемся создать еще один файл CSS с именем modal-shared.component.css. Мы создаем этот файл, потому что у нас будет один и тот же CSS для модальных окон ошибки и успеха без повторения одного и того же кода в двух разных файлах CSS.

Теперь изменим файл error-modal.component.ts:

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-error-modal',
  templateUrl: './error-modal.component.html',
  styleUrls: ['./error-modal.component.css', '../modal-shared.component.css']
})
export class ErrorModalComponent implements OnInit {
  @Input() public modalHeaderText: string;
  @Input() public modalBodyText: string;
  @Input() public okButtonText: string;
  
  constructor() { }

  ngOnInit() {
  }

}

Мы украшаем наши свойства декоратором @Input, и, таким образом, наш родительский компонент может устанавливать значения для этих свойств с помощью привязки свойств.

Давайте продолжим, изменив файл error.modal.component.html:

<div id="errorModal" class="modal fade" tabindex="-1" role="dialog">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">{{modalHeaderText}}</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <p>{{modalBodyText}}</p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-danger" data-dismiss="modal">{{okButtonText}}</button>
      </div>
    </div>
  </div>
</div>

Затем изменим файл modal-shared.component.css:

.modal-title {
    margin: 0;
    line-height: 1.42857143;
    font-size: 30px;
    text-align: center;
}

.modal-body p {
    text-align: center;
    margin-top: 10px;
}

@media (min-width: 768px){
    .modal-dialog {
        width: 500px;
        margin: 20% auto;
    }
}

Создание модального компонента success

Теперь у нас есть модальное окно ошибки, и давайте продолжим создание модального окна success таким же образом, как мы это делали с модальным окном ошибки:

ng g component shared/modals/success-modal --skipTests

Мы должны экспортировать модальный компонент успеха из файла shared.module.ts.

Затем изменим наш файл success-modal.component.ts:

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-success-modal',
  templateUrl: './success-modal.component.html',
  styleUrls: ['./success-modal.component.css', '../modal-shared.component.css']
})
export class SuccessModalComponent implements OnInit {
  @Input() public modalHeaderText: string;
  @Input() public modalBodyText: string;
  @Input() public okButtonText: string;
  @Output() public redirectOnOK = new EventEmitter();

  constructor() { }

  ngOnInit() {
  }

  public emitEvent = () => {
    this.redirectOnOK.emit();
  }

}

Декоратор @Ouput с EventEmitter используется для генерации события от дочернего к родительскому компоненту, и это именно то, что мы хотим сделать. Мы собираемся использовать компонент успеха с успешно выполненными действиями create, update или delete, и, нажав кнопку OK, мы собираемся перенаправить пользователя в компонент списка владельцев. Чтобы этот эмиттер работал, нам нужно сделать подписку в родительском компоненте на эту функцию emmitEvent.

Затем, изменим файл success-modal.component.html:

<div id="successModal" class="modal fade" tabindex="-1" role="dialog">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">{{modalHeaderText}}</h5>
        <button type="button" class="close" data-dismiss="modal" (click)="emitEvent()" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <p>{{modalBodyText}}</p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-success" data-dismiss="modal" (click)="emitEvent()">{{okButtonText}}</button>
      </div>
    </div>
   </div>
</div>

Вызов дочернего компонента

Теперь у нас есть оба дочерних компонента, которые можно использовать повторно. На данный момент у нас все еще нет места для вызова наших дочерних компонентов, но, например, если вы хотите вызвать свой модальный компонент ошибки в какой-либо родительский компонент, все, что вам нужно сделать, это добавить его селектор в родительский компонент:

<app-error-modal [modalHeaderText]="'some string in here'"
[modalBodyText]="some property from a component in here without single quotes"
[okButtonText]="'OK'"></app-error-modal>

Мы включаем селектор <app-error-modal> из нашего дочернего компонента и настраиваем свойства @Input в квадратных скобках (привязка свойств). Чтобы установить значение для привязки свойств, всегда используйте двойные кавычки, а затем указывайте значение между ними. Если мы хотим передать строку, она должна быть сначала заключена в одинарные кавычки.

Теперь, вы знаете, как использовать дочерние компоненты, вы можете разделить компонент сведений о владельце на отношения родитель-потомок. Попробуйте сделать это самостоятельно, потому что практика - лучший способ научиться чему-то. Конечно, вы можете найти эту реализацию в исходном коде этого сообщения в блоге.

Директивы

Как вы могли заметить, у нашего владельца есть свойство dateOfBirth, с которым нам нужно работать в формах создания и обновления. Это будет просто элемент ввода текста в форме, но он будет иметь другое поведение. Мы собираемся использовать его как средство выбора даты. Мы можем изменить его поведение в обоих компонентах (Create и Update) по отдельности, но мы этого делать не будем. Для этого мы создадим директиву Angular, чтобы мы могли изменить поведение этого поля ввода.

В общей папке создайте новые директивы папки и внутри создайте новый файл datepicker.directive.ts:

ng g directive shared/directives/datepicker --skipTests

Давайте изменим этот файл:

import { Directive, ElementRef, Output, EventEmitter, OnInit } from '@angular/core';

@Directive({
  selector: '[appDatepicker]'
})
export class DatepickerDirective implements OnInit {
  @Output() public change = new EventEmitter();

  constructor(private elementRef: ElementRef) { }

  public ngOnInit() {
    $(this.elementRef.nativeElement).datepicker({
      dateFormat: 'mm/dd/yy',
      changeYear: true,
      yearRange: "-100:+0",
      onSelect: (dateText) => {
        this.change.emit(dateText);
      }
    });
  }
}

Просто добавьте в файл styles.cssfile (глобальный файл стилей) этот стиль:

.ui-widget-header {
    color: #564040;
}

Не забудьте экспортировать эту директиву из файла общего модуля.

Теперь наша директива готова для внедрения в любой элемент HTML. Мы собираемся использовать его позже, но, например, вы можете использовать его так:

<input type="text" class="form-control" appDatepicker>

Заключение

Прочитав этот пост, вы узнали:

  • Для чего нужны декораторы ввода-вывода Angular
  • Как поделиться компонентами в проекте в Angular
  • Способ создания дочерних компонентов
  • Как вызвать дочерний компонент в родительский компонент в Angular
  • Как создавать директивы в Angular

Спасибо, что прочитали этот пост, надеюсь, он был вам полезен.

В следующей статье, мы начнем с создания владельца и проверки формы.