You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
abp/docs/pt-BR/Tutorials/Angular/Part-II.md

16 KiB

Tutorial Angular - Parte II

Sobre este tutorial

Esta é a segunda parte da série de tutoriais angulares. Veja todas as peças:

Você pode acessar o código fonte do aplicativo no repositório GitHub .

Criando um novo livro

Nesta seção, você aprenderá como criar um novo formulário de diálogo modal para criar um novo livro.

Definição do tipo

Criar uma interface, com o nome CreateUpdateBookInputno books.tscomo mostrado abaixo:

export namespace Books {
  //...
  export interface CreateUpdateBookInput {
    name: string;
    type: BookType;
    publishDate: string;
    price: number;
  }
}

CreateUpdateBookInputinterface corresponde ao CreateUpdateBookDtono back-end.

Método de Serviço

Abra o books.service.tse adicione um novo método, nomeado createpara executar uma solicitação HTTP POST no servidor:

create(createBookInput: Books.CreateUpdateBookInput): Observable<Books.Book> {
  return this.restService.request<Books.CreateUpdateBookInput, Books.Book>({
    method: 'POST',
    url: '/api/app/book',
    body: createBookInput
  });
}
  • restService.requestA função obtém parâmetros genéricos para os tipos enviados e recebidos do servidor. Este exemplo envia um CreateUpdateBookInputobjeto e recebe um Bookobjeto (você pode definir o tipo voidde solicitação ou retorno, se não for usado).

Definições de estado

Adicione a CreateUpdateBookação ao books.actions.tsconforme mostrado abaixo:

import { Books } from '../models';

export class CreateUpdateBook {
  static readonly type = '[Books] Create Update Book';
  constructor(public payload: Books.CreateUpdateBookInput) {}
}

Abra books.state.tse defina o savemétodo que ouvirá uma CreateUpdateBookação para criar um livro:

import { ... , CreateUpdateBook } from '../actions/books.actions';
import { ... , switchMap } from 'rxjs/operators';
//...
@Action(CreateUpdateBook)
save(ctx: StateContext<Books.State>, action: CreateUpdateBook) {
  return this.booksService
      .create(action.payload)
      .pipe(switchMap(() => ctx.dispatch(new GetBooks())));
}

Quando a SaveBookação é despachada, o método save é executado. Ele chama o createmétodo do BooksServicedefinido anteriormente. Após a chamada de serviço, BooksStatedespacha a GetBooksação para obter livros novamente do servidor para atualizar a página.

Adicionar um modal ao BookListComponent

Abra o book-list.component.htmle adicione o abp-modalpara mostrar / ocultar o modal para criar um novo livro.

<abp-modal [(visible)]="isModalOpen">
  <ng-template #abpHeader>
    <h3>New Book</h3>
  </ng-template>

  <ng-template #abpBody> </ng-template>

  <ng-template #abpFooter>
    <button type="button" class="btn btn-secondary" abpClose>
      Cancel
    </button>
  </ng-template>
</abp-modal>

abp-modalé um componente pré-construído para mostrar os modais. Embora você possa usar outra abordagem para mostrar um modal, abp-modalfornece benefícios adicionais.

Adicione um botão rotulado New bookpara mostrar o modal:

<div class="row">
  <div class="col col-md-6">
    <h5 class="card-title">
      Books
    </h5>
  </div>
  <div class="text-end col col-md-6">
    <button id="create-role" class="btn btn-primary" type="button" (click)="createBook()">
      <i class="fa fa-plus mr-1"></i> <span>New book</span>
    </button>
  </div>
</div>

Abra a variável book-list.component.tse adicione isModalOpene createBookmétodo para mostrar / ocultar o modal.

isModalOpen = false;

//...

createBook() {
  this.isModalOpen = true;
}

modal vazio

Criar um formulário reativo

Os formulários reativos fornecem uma abordagem orientada a modelo para lidar com entradas de formulário cujos valores mudam ao longo do tempo.

Adicione uma formvariável e injete um FormBuilderserviço book-list.component.tscomo mostrado abaixo (lembre-se de adicionar a instrução de importação).

import { FormGroup, FormBuilder } from '@angular/forms';

form: FormGroup;

constructor(
  //...
  private fb: FormBuilder
) {}

O serviço FormBuilder fornece métodos convenientes para gerar controles. Reduz a quantidade de clichê necessária para criar formulários complexos.

Adicione o buildFormmétodo para criar um formulário de livro.

buildForm() {
  this.form = this.fb.group({
    name: ['', Validators.required],
    type: [null, Validators.required],
    publishDate: [null, Validators.required],
    price: [null, Validators.required],
  });
}
  • O groupmétodo de FormBuilder( fb) cria a FormGroup.
  • Adicionado Validators.requiredmétodo estático que valida o elemento de formulário relacionado.

Modifique o createBookmétodo como mostrado abaixo:

createBook() {
  this.buildForm();
  this.isModalOpen = true;
}

Crie os elementos DOM do formulário

Abra book-list.component.htmle adicione o formulário no modelo de corpo do modal.

<ng-template #abpBody>
  <form [formGroup]="form">
    <div class="form-group">
      <label for="book-name">Name</label><span> * </span>
      <input type="text" id="book-name" class="form-control" formControlName="name" autofocus />
    </div>

    <div class="form-group">
      <label for="book-price">Price</label><span> * </span>
      <input type="number" id="book-price" class="form-control" formControlName="price" />
    </div>

    <div class="form-group">
      <label for="book-type">Type</label><span> * </span>
      <select class="form-control" id="book-type" formControlName="type">
        <option [ngValue]="null">Select a book type</option>
        <option [ngValue]="booksType[type]" *ngFor="let type of bookTypes"> {%{{{ type }}}%}</option>
      </select>
    </div>

    <div class="form-group">
      <label>Publish date</label><span> * </span>
      <input
        #datepicker="ngbDatepicker"
        class="form-control"
        name="datepicker"
        formControlName="publishDate"
        ngbDatepicker
        (click)="datepicker.toggle()"
      />
    </div>
  </form>
</ng-template>
  • Este modelo cria um formulário com os campos Nome, Preço, Tipo e Data de publicação.

Usamos o datepicker do NgBootstrap neste componente.

Abra o book-list.component.tse crie uma matriz chamada bookTypes:

//...
form: FormGroup;

bookTypes = Object.keys(Books.BookType).filter(
    bookType => typeof this.booksType[bookType] === 'number'
);

O bookTypescontém os campos da BookTypeenumeração. A matriz resultante é mostrada abaixo:

['Adventure', 'Biography', 'Dystopia', 'Fantastic' ...]

Essa matriz foi usada no modelo de formulário anterior (no ngForloop).

Requisitos do Datepicker

Você precisa importar NgbDatepickerModulepara o books.module.ts:

import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap';

@NgModule({
  imports: [
    // ...
    NgbDatepickerModule,
  ],
})
export class BooksModule {}

Abra o book-list.component.tse adicione providerscomo mostrado abaixo:

import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';

@Component({
  // ...
  providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class BookListComponent implements OnInit {
// ...

O NgbDateAdaptervalor do Datepicker converte em Datetipo. Consulte os adaptadores datepicker para obter mais detalhes.

forma de livro novo

Salvando o livro

Abra o book-list.component.htmle adicione um abp-buttonpara salvar o formulário.

<ng-template #abpFooter>
  <button type="button" class="btn btn-secondary" abpClose>
    Cancel
  </button>
  <button class="btn btn-primary" (click)="save()">
    <i class="fa fa-check mr-1"></i>
    Save
  </button>
</ng-template>

Isso adiciona um botão Salvar à área inferior do modal:

livraria-novo-livro-formulário-v2

Em seguida, defina um savemétodo no BookListComponent:

save() {
  if (this.form.invalid) {
    return;
  }

  this.store.dispatch(new CreateUpdateBook(this.form.value)).subscribe(() => {
    this.isModalOpen = false;
    this.form.reset();
  });
}

Atualizando um livro existente

BooksService

Abra o books.service.tse adicione os métodos getByIde update.

getById(id: string): Observable<Books.Book> {
  return this.restService.request<void, Books.Book>({
    method: 'GET',
    url: `/api/app/book/${id}`
  });
}

update(updateBookInput: Books.CreateUpdateBookInput, id: string): Observable<Books.Book> {
  return this.restService.request<Books.CreateUpdateBookInput, Books.Book>({
    method: 'PUT',
    url: `/api/app/book/${id}`,
    body: updateBookInput
  });
}

Ação CreateUpdateBook

Abra o parâmetro books.actins.tse adicione idà CreateUpdateBookação:

export class CreateUpdateBook {
  static readonly type = '[Books] Create Update Book';
  constructor(public payload: Books.CreateUpdateBookInput, public id?: string) {}
}

Abra books.state.tse modifique o savemétodo conforme mostrado abaixo:

@Action(CreateUpdateBook)
save(ctx: StateContext<Books.State>, action: CreateUpdateBook) {
  let request;

  if (action.id) {
    request = this.booksService.update(action.payload, action.id);
  } else {
    request = this.booksService.create(action.payload);
  }

  return request.pipe(switchMap(() => ctx.dispatch(new GetBooks())));
}

BookListComponent

Injectar BooksServicedependência, adicionando-o ao book-list.component.tsconstrutor e adicione uma variável chamada selectedBook.

import { BooksService } from '../shared/books.service';
//...
selectedBook = {} as Books.Book;

constructor(
  //...
  private booksService: BooksService
)

booksServiceé usado para obter o livro de edição para preparar o formulário. Modifique o buildFormmétodo para reutilizar o mesmo formulário ao editar um livro.

buildForm() {
  this.form = this.fb.group({
    name: [this.selectedBook.name || '', Validators.required],
    type: this.selectedBook.type || null,
    publishDate: this.selectedBook.publishDate ? new Date(this.selectedBook.publishDate) : null,
    price: this.selectedBook.price || null,
  });
}

Adicione o editBookmétodo como mostrado abaixo:

  editBook(id: string) {
    this.booksService.getById(id).subscribe(book => {
      this.selectedBook = book;
      this.buildForm();
      this.isModalOpen = true;
    });
  }

Adicionado editBookmétodo para obter o livro de edição, criar o formulário e mostrar o modal.

Agora, adicione a selectedBookdefinição ao createBookmétodo para reutilizar o mesmo formulário ao criar um novo livro:

  createBook() {
    this.selectedBook = {} as Books.Book;
    //...
  }

Modifique o savemétodo para passar o ID do livro selecionado, como mostrado abaixo:

save() {
  if (this.form.invalid) {
    return;
  }

  this.store.dispatch(new CreateUpdateBook(this.form.value, this.selectedBook.id))
    .subscribe(() => {
      this.isModalOpen = false;
      this.form.reset();
    });
}

Adicione o menu suspenso "Ações" à tabela

Abra o book-list.component.html e adicione modifique o p-table como mostrado abaixo:

<p-table [value]="books$ | async" [loading]="loading" [paginator]="true" [rows]="10">
  <ng-template pTemplate="header">
    <tr>
      <th>Actions</th>
      <th>Book name</th>
      <th>Book type</th>
      <th>Publish date</th>
      <th>Price</th>
    </tr>
  </ng-template>
  <ng-template pTemplate="body" let-data>
    <tr>
      <td>
        <div ngbDropdown class="d-inline-block">
          <button
            class="btn btn-primary btn-sm dropdown-toggle"
            data-toggle="dropdown"
            aria-haspopup="true"
            ngbDropdownToggle
          >
            <i class="fa fa-cog mr-1"></i>Actions
          </button>
          <div ngbDropdownMenu>
            <button ngbDropdownItem (click)="editBook(data.id)">Edit</button>
          </div>
        </div>
      </td>
      <td>{%{{{ data.name }}}%}</td>
      <td>{%{{{ booksType[data.type] }}}%}</td>
      <td>{%{{{ data.publishDate | date }}}%}</td>
      <td>{%{{{ data.price }}}%}</td>
    </tr>
  </ng-template>
</p-table>
  • Adicionado um thpara a coluna "Ações".
  • Adicionado buttoncom ngbDropdownTogglepara abrir ações quando clicamos no botão.

Nós costumávamos usar o NgbDropdown no menu suspenso de ações.

A interface do usuário final é semelhante a:

botões de ações

Atualize o cabeçalho modal para alterar o título com base na operação atual:

<ng-template #abpHeader>
  <h3>{%{{{ selectedBook.id ? 'Edit' : 'New Book' }}}%}</h3>
</ng-template>

botões de ações

Exclusão de um livro existente

BooksService

Abra books.service.tse inclua um deletemétodo para excluir um livro com o id, executando uma solicitação HTTP no nó de extremidade relacionado:

delete(id: string): Observable<void> {
  return this.restService.request<void, void>({
    method: 'DELETE',
    url: `/api/app/book/${id}`
  });
}

Ação DeleteBook

Adicione uma ação chamada DeleteBookpara books.actions.ts:

export class DeleteBook {
  static readonly type = '[Books] Delete';
  constructor(public id: string) {}
}

Abra o books.state.tse adicione o deletemétodo que ouvirá a DeleteBookação para excluir um livro:

import { ... , DeleteBook } from '../actions/books.actions';
//...
@Action(DeleteBook)
delete(ctx: StateContext<Books.State>, action: DeleteBook) {
  return this.booksService.delete(action.id).pipe(switchMap(() => ctx.dispatch(new GetBooks())));
}
  • Adicionado DeleteBookà lista de importação.
  • Usa bookServicepara excluir o livro.

#### Adicionar um botão Excluir

Abra book-list.component.htmle modifique ngbDropdownMenupara adicionar o botão excluir, como mostrado abaixo:

<div ngbDropdownMenu>
  ...
  <button ngbDropdownItem (click)="delete(data.id, data.name)">
    Delete
  </button>
</div>

A interface do usuário suspensa de ações finais é semelhante a abaixo:

livraria-final-ações-suspensa

#### Caixa de diálogo Excluir confirmação

Abra book-list.component.tse injete o ConfirmationService.

import { ConfirmationService } from '@abp/ng.theme.shared';
//...
constructor(
	//...
  private confirmationService: ConfirmationService
)

ConfirmationService é um serviço simples fornecido pela estrutura ABP que usa internamente o PrimeNG.

Adicione um método de exclusão ao BookListComponent:

import { ... , DeleteBook } from '../../store/actions';
import { ... , Toaster } from '@abp/ng.theme.shared';
//...
delete(id: string, name: string) {
  this.confirmationService
    .error(`${name} will be deleted. Do you confirm that?`, 'Are you sure?')
    .subscribe(status => {
      if (status === Toaster.Status.confirm) {
        this.store.dispatch(new DeleteBook(id));
      }
    });
}

O deletemétodo mostra um pop-up de confirmação e assina a resposta do usuário. DeleteBookação despachada somente se o usuário clicar no Yesbotão O pop-up de confirmação é exibido abaixo:

livraria-confirmação-pop-up

Próxima parte

Veja a próxima parte deste tutorial.