diff --git a/docs/pt-BR/Tutorials/Angular/Part-I.md b/docs/pt-BR/Tutorials/Angular/Part-I.md new file mode 100644 index 0000000000..566690a1d3 --- /dev/null +++ b/docs/pt-BR/Tutorials/Angular/Part-I.md @@ -0,0 +1,661 @@ +## Tutorial Angular - Parte I + +### Sobre este tutorial + +Nesta série de tutoriais, você criará um aplicativo usado para gerenciar uma lista de livros e seus autores. **Angular** será usado como estrutura da interface do usuário e **MongoDB** será usado como provedor de banco de dados. + +Esta é a primeira parte da série de tutoriais angulares. Veja todas as peças: + +- **Parte I: Crie o projeto e uma página de lista de livros (este tutorial)** +- [Parte II: Criar, atualizar e excluir livros](Part-II) +- [Parte III: Testes de Integração](Part-III) + +Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb) . + +### Criando o projeto + +Crie um novo projeto nomeado `Acme.BookStore`selecionando Angular como a estrutura da interface do usuário e MongoDB como o provedor de banco de dados, crie o banco de dados e execute o aplicativo seguindo o [documento Introdução](../../Getting-Started-Angular-Template.md) . + +### Estrutura da solução (back-end) + +É assim que a estrutura da solução em camadas cuida da criação: + +![solução de back-end da livraria](images/bookstore-backend-solution-v2.png) + +> Você pode ver o [documento do modelo de aplicativo](../../Startup-Templates/Application.md) para entender a estrutura da solução em detalhes. No entanto, você entenderá o básico com este tutorial. + +### Criar a entidade do livro + +A camada de domínio no modelo de inicialização é separada em dois projetos: + +- `Acme.BookStore.Domain`contém suas [entidades](../../Entities.md) , [serviços de domínio](../../Domain-Services.md) e outros objetos principais de domínio. +- `Acme.BookStore.Domain.Shared` contém constantes, enumerações ou outros objetos relacionados ao domínio que podem ser compartilhados com os clientes. + +Defina [entidades](../../Entities.md) na **camada de domínio** ( `Acme.BookStore.Domain`projeto) da solução. A entidade principal do aplicativo é a `Book`. Crie uma classe, chamada `Book`, no `Acme.BookStore.Domain`projeto, como mostrado abaixo: + +```csharp +using System; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Acme.BookStore +{ + public class Book : AuditedAggregateRoot + { + public string Name { get; set; } + + public BookType Type { get; set; } + + public DateTime PublishDate { get; set; } + + public float Price { get; set; } + } +} +``` + +- O ABP possui duas classes base fundamentais para entidades: `AggregateRoot`e `Entity`. **A raiz agregada** é um dos conceitos de **DDD (Domain Driven Design)** . Consulte o [documento da entidade](../../Entities.md) para obter detalhes e melhores práticas. +- `Book`entidade herda `AuditedAggregateRoot`que adiciona algumas propriedades de auditoria ( `CreationTime`, `CreatorId`, `LastModificationTime`... etc.) no topo da `AggregateRoot`classe. +- `Guid`é o **tipo** de **chave primária** da `Book`entidade. + +#### BookType Enum + +Defina a `BookType`enumeração no `Acme.BookStore.Domain.Shared`projeto: + +```csharp +namespace Acme.BookStore +{ + public enum BookType + { + Undefined, + Adventure, + Biography, + Dystopia, + Fantastic, + Horror, + Science, + ScienceFiction, + Poetry + } +} +``` + +#### Adicionar entidade de livro ao seu DbContext + +Adicione uma `IMongoCollection`propriedade ao `BookStoreMongoDbContext`interior do `Acme.BookStore.MongoDB`projeto: + +```csharp +public class BookStoreMongoDbContext : AbpMongoDbContext +{ + public IMongoCollection Books => Collection(); + ... +} +``` + +#### Adicionar dados de semente (amostra) + +Esta seção é opcional, mas seria bom ter um dado inicial no banco de dados na primeira execução. O ABP fornece um [sistema de semente de dados](../../Data-Seeding.md) . Crie uma classe derivada de `IDataSeedContributor`no `.Domain`projeto: + +```csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; + +namespace Acme.BookStore +{ + public class BookStoreDataSeederContributor + : IDataSeedContributor, ITransientDependency + { + private readonly IRepository _bookRepository; + + public BookStoreDataSeederContributor(IRepository bookRepository) + { + _bookRepository = bookRepository; + } + + public async Task SeedAsync(DataSeedContext context) + { + if (await _bookRepository.GetCountAsync() > 0) + { + return; + } + + await _bookRepository.InsertAsync( + new Book + { + Name = "1984", + Type = BookType.Dystopia, + PublishDate = new DateTime(1949, 6, 8), + Price = 19.84f + } + ); + + await _bookRepository.InsertAsync( + new Book + { + Name = "The Hitchhiker's Guide to the Galaxy", + Type = BookType.ScienceFiction, + PublishDate = new DateTime(1995, 9, 27), + Price = 42.0f + } + ); + } + } +} +``` + +`BookStoreDataSeederContributor`simplesmente insere dois livros no banco de dados se não houver nenhum livro adicionado antes. O ABP descobre e executa automaticamente essa classe quando você propaga o banco de dados executando o `Acme.BookStore.DbMigrator`projeto. + +### Crie o serviço de aplicativo + +O próximo passo é criar um [serviço de aplicativo](../../Application-Services.md) para gerenciar (criar, listar, atualizar, excluir ...) os livros. A camada de aplicativo no modelo de inicialização é separada em dois projetos: + +- `Acme.BookStore.Application.Contracts` contém principalmente seus DTOs e interfaces de serviço de aplicativo. +- `Acme.BookStore.Application` contém as implementações dos seus serviços de aplicativo. + +#### BookDto + +Crie uma classe DTO denominada `BookDto`no `Acme.BookStore.Application.Contracts`projeto: + +```csharp +using System; +using Volo.Abp.Application.Dtos; + +namespace Acme.BookStore +{ + public class BookDto : AuditedEntityDto + { + public string Name { get; set; } + + public BookType Type { get; set; } + + public DateTime PublishDate { get; set; } + + public float Price { get; set; } + } +} +``` + +- **As** classes **DTO** são usadas para **transferir dados** entre a *camada de apresentação* e a *camada de aplicativo* . Consulte o [documento Objetos de transferência de dados](../../Data-Transfer-Objects.md) para obter mais detalhes. +- `BookDto` é usado para transferir dados do livro para a camada de apresentação para mostrar as informações do livro na interface do usuário. +- `BookDto`é derivado do `AuditedEntityDto`que possui propriedades de auditoria exatamente como a `Book`classe definida acima. + +Será necessário converter `Book`entidades em `BookDto`objetos enquanto retorna os livros para a camada de apresentação. A biblioteca do [AutoMapper](https://automapper.org/) pode automatizar essa conversão quando você define o mapeamento adequado. O modelo de inicialização é fornecido com o AutoMapper configurado, para que você possa definir o mapeamento na `BookStoreApplicationAutoMapperProfile`classe no `Acme.BookStore.Application`projeto: + +```csharp +using AutoMapper; + +namespace Acme.BookStore +{ + public class BookStoreApplicationAutoMapperProfile : Profile + { + public BookStoreApplicationAutoMapperProfile() + { + CreateMap(); + } + } +} +``` + +#### CreateUpdateBookDto + +Crie uma classe DTO denominada `CreateUpdateBookDto`no `Acme.BookStore.Application.Contracts`projeto: + +```csharp +using System; +using System.ComponentModel.DataAnnotations; + +namespace Acme.BookStore +{ + public class CreateUpdateBookDto + { + [Required] + [StringLength(128)] + public string Name { get; set; } + + [Required] + public BookType Type { get; set; } = BookType.Undefined; + + [Required] + public DateTime PublishDate { get; set; } + + [Required] + public float Price { get; set; } + } +} +``` + +- Essa classe DTO é usada para obter informações do livro a partir da interface do usuário ao criar ou atualizar um livro. +- Ele define atributos de anotação de dados (como `[Required]`) para definir validações para as propriedades. Os DTOs são [validados automaticamente](../../Validation.md) pela estrutura ABP. + +Em seguida, adicione um mapeamento `BookStoreApplicationAutoMapperProfile`do `CreateUpdateBookDto`objeto à `Book`entidade: + +```csharp +CreateMap(); +``` + +#### IBookAppService + +Defina uma interface nomeada `IBookAppService`no `Acme.BookStore.Application.Contracts`projeto: + +```csharp +using System; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace Acme.BookStore +{ + public interface IBookAppService : + ICrudAppService< //Defines CRUD methods + BookDto, //Used to show books + Guid, //Primary key of the book entity + PagedAndSortedResultRequestDto, //Used for paging/sorting on getting a list of books + CreateUpdateBookDto, //Used to create a new book + CreateUpdateBookDto> //Used to update a book + { + + } +} +``` + +- A definição de interfaces para serviços de aplicativos não é requerida pela estrutura. No entanto, é sugerido como uma prática recomendada. +- `ICrudAppService`define comuns **CRUD** métodos: `GetAsync`, `GetListAsync`, `CreateAsync`, `UpdateAsync`e `DeleteAsync`. Não é necessário estendê-lo. Em vez disso, você pode herdar da `IApplicationService`interface vazia e definir seus próprios métodos manualmente. +- Existem algumas variações de `ICrudAppService`onde você pode usar DTOs separados para cada método. + +#### BookAppService + +Implemente `IBookAppService`como nomeado `BookAppService`no `Acme.BookStore.Application`projeto: + +```csharp +using System; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; + +namespace Acme.BookStore +{ + public class BookAppService : + CrudAppService, + IBookAppService + { + public BookAppService(IRepository repository) + : base(repository) + { + + } + } +} +``` + +- `BookAppService`é derivado do `CrudAppService<...>`qual implementa todos os métodos CRUD definidos acima. +- `BookAppService`injeta `IRepository`qual é o repositório padrão da `Book`entidade. O ABP cria automaticamente repositórios padrão para cada raiz (ou entidade) agregada. Veja o [documento do repositório](../../Repositories) . +- `BookAppService`usa `IObjectMapper`para converter `Book`objetos em `BookDto`objetos e `CreateUpdateBookDto`objetos em `Book`objetos. O modelo de inicialização usa a biblioteca [AutoMapper](http://automapper.org/) como o provedor de mapeamento de objetos. Você definiu os mapeamentos antes, para que funcionem conforme o esperado. + +### Controladores de API automática + +Você normalmente cria **controladores** para expor serviços de aplicativos como pontos de extremidade da **API HTTP** . Assim, permite que navegadores ou clientes de terceiros os chamem via AJAX. O ABP pode configurar [**automaticamente**](../../AspNetCore/Auto-API-Controllers.md) seus serviços de aplicativo como controladores de API MVC por convenção. + +#### UI do Swagger + +O modelo de inicialização está configurado para executar a [interface do usuário do swagger](https://swagger.io/tools/swagger-ui/) usando a biblioteca [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) . Execute o `Acme.BookStore.HttpApi.Host`aplicativo e insira `https://localhost:XXXX/swagger/`(substitua XXXX por sua própria porta) como URL no seu navegador. + +Você verá alguns pontos de extremidade de serviço internos, bem como o `Book`serviço e seus pontos de extremidade no estilo REST: + +![livraria-arrogância](images/bookstore-swagger-api.png) + +O Swagger tem uma ótima interface para testar APIs. Você pode tentar executar a `[GET] /api/app/book`API para obter uma lista de livros. + +### Crie a página de livros + +Neste tutorial; + +- [A CLI angular](https://angular.io/cli) será usada para criar módulos, componentes e serviços +- [NGXS](https://ngxs.gitbook.io/ngxs/) será usado como a biblioteca de gerenciamento de estado +- [O Bootstrap](https://ng-bootstrap.github.io/#/home) será usado como a biblioteca de componentes da interface do usuário. +- [O Visual Studio Code](https://code.visualstudio.com/) será usado como editor de código (você pode usar seu editor favorito). + +#### Instalar pacotes NPM + +Abra uma janela do terminal, vá para a `angular`pasta e execute o `yarn` comando para instalar os pacotes NPM: + +``` +yarn +``` + +#### BooksModule + +Execute a seguinte linha de comando para criar um novo módulo, denominado `BooksModule`: + +```bash +yarn ng generate module books --route books --module app.module +``` + +![Creating-Books-Module.terminal](images/bookstore-creating-books-module-terminal.png) + +Execute `yarn start`, aguarde Angular para executar o aplicativo e abra `http://localhost:4200/books`em um navegador: + +![página inicial dos livros](images/bookstore-initial-books-page.png) + +#### Encaminhamento + +Abra `app-routing.module.ts`e substitua `books`conforme mostrado abaixo: + +```js +import { ApplicationLayoutComponent } from '@abp/ng.theme.basic';- + +//... +{ + path: 'books', + component: ApplicationLayoutComponent, + loadChildren: () => import('./books/books.module').then(m => m.BooksModule), + data: { + routes: { + name: 'Books', + } as ABP.Route, + }, +}, +``` + +`ApplicationLayoutComponent`configuração define o layout do aplicativo para a nova página. Se você deseja ver sua rota na barra de navegação (menu principal), também deve adicionar o `data`objeto com `name`propriedade à sua rota. + +![página inicial dos livros](images/bookstore-initial-books-page-with-layout.png) + +#### Componente da lista de livros + +Primeiro, substitua pela `books.component.html`seguinte linha para colocar a saída do roteador: + +```html + +``` + +Em seguida, execute o comando abaixo no terminal na pasta raiz para gerar um novo componente, chamado book-list: + +```bash +yarn ng generate component books/book-list +``` + +![terminal-criando-lista-de-livros](images/bookstore-creating-book-list-terminal.png) + +Importe `SharedModule`para `BooksModule`para reutilizar alguns componentes e serviços definidos em: + +```js +import { SharedModule } from '../shared/shared.module'; + +@NgModule({ + //... + imports: [ + //... + SharedModule, + ], +}) +export class BooksModule {} +``` + +Em seguida, atualize o `routes`no `books-routing.module.ts`para adicionar o novo componente book-list: + +```js +import { BookListComponent } from './book-list/book-list.component'; + +const routes: Routes = [ + { + path: '', + component: BooksComponent, + children: [{ path: '', component: BookListComponent }], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class BooksRoutingModule {} +``` + +![página inicial da lista de livros](images/bookstore-initial-book-list-page.png) + +#### Criar BooksState + +Execute o seguinte comando no terminal para criar um novo estado, denominado `BooksState`: + +```shell +yarn ng generate ngxs-schematic:state books +``` + +Este comando cria vários novos arquivos e edições `app.modules.ts`para importar o `NgxsModule`com o novo estado: + +```js +// app.module.ts + +import { BooksState } from './store/states/books.state'; + +@NgModule({ + imports: [ + //... + NgxsModule.forRoot([BooksState]), + ], + //... +}) +export class AppModule {} +``` + +#### Obter dados de livros do back-end + +Primeiro, crie tipos de dados para mapear os dados que retornam do back-end (você pode verificar a interface do swagger ou a API do back-end para conhecer o formato dos dados). + +Modifique o `books.ts`como mostrado abaixo: + +```js +export namespace Books { + export interface State { + books: Response; + } + + export interface Response { + items: Book[]; + totalCount: number; + } + + export interface Book { + name: string; + type: BookType; + publishDate: string; + price: number; + lastModificationTime: string; + lastModifierId: string; + creationTime: string; + creatorId: string; + id: string; + } + + export enum BookType { + Undefined, + Adventure, + Biography, + Dystopia, + Fantastic, + Horror, + Science, + ScienceFiction, + Poetry, + } +} +``` + +Adicionada `Book`interface que representa um objeto de livro e `BookType`enum representa uma categoria de livro. + +#### BooksService + +Agora, crie um novo serviço, nomeado `BooksService`para executar chamadas HTTP para o servidor: + +```bash +yarn ng generate service books/shared/books +``` + +![serviço-terminal-saída](images/bookstore-service-terminal-output.png) + +Modifique `books.service.ts`como mostrado abaixo: + +```js +import { Injectable } from '@angular/core'; +import { RestService } from '@abp/ng.core'; +import { Books } from '../../store/models'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class BooksService { + constructor(private restService: RestService) {} + + get(): Observable { + return this.restService.request({ + method: 'GET', + url: '/api/app/book' + }); + } +} +``` + +Adicionado o `get`método para obter a lista de livros executando uma solicitação HTTP no terminal relacionado. + +Substitua o `books.actions.ts`conteúdo conforme mostrado abaixo: + +```js +export class GetBooks { + static readonly type = '[Books] Get'; +} +``` + +#### Implementar o BooksState + +Abra o `books.state.ts`e altere o arquivo, como mostrado abaixo: + +```js +import { State, Action, StateContext, Selector } from '@ngxs/store'; +import { GetBooks } from '../actions/books.actions'; +import { Books } from '../models/books'; +import { BooksService } from '../../books/shared/books.service'; +import { tap } from 'rxjs/operators'; + +@State({ + name: 'BooksState', + defaults: { books: {} } as Books.State, +}) +export class BooksState { + @Selector() + static getBooks(state: Books.State) { + return state.books.items || []; + } + + constructor(private booksService: BooksService) {} + + @Action(GetBooks) + get(ctx: StateContext) { + return this.booksService.get().pipe( + tap(booksResponse => { + ctx.patchState({ + books: booksResponse, + }); + }), + ); + } +} +``` + +Adicionada a `GetBooks`ação que usa o `BookService`definido acima para obter os livros e corrigir o estado. + +> O NGXS exige retornar o observável sem assiná-lo, conforme feito nesta amostra (na função get). + +#### BookListComponent + +Modifique o `book-list.component.ts`como mostrado abaixo: + +```js +import { Component, OnInit } from '@angular/core'; +import { Store, Select } from '@ngxs/store'; +import { BooksState } from '../../store/states'; +import { Observable } from 'rxjs'; +import { Books } from '../../store/models'; +import { GetBooks } from '../../store/actions'; + +@Component({ + selector: 'app-book-list', + templateUrl: './book-list.component.html', + styleUrls: ['./book-list.component.scss'], +}) +export class BookListComponent implements OnInit { + @Select(BooksState.getBooks) + books$: Observable; + + booksType = Books.BookType; + + loading = false; + + constructor(private store: Store) {} + + ngOnInit() { + this.loading = true; + this.store.dispatch(new GetBooks()).subscribe(() => { + this.loading = false; + }); + } +} +``` + +> Consulte as [ações de despacho](https://ngxs.gitbook.io/ngxs/concepts/store#dispatching-actions) e [selecione](https://ngxs.gitbook.io/ngxs/concepts/select) na documentação do NGXS para obter mais informações sobre esses recursos do NGXS. + +Substitua o `book-list.component.html`conteúdo conforme mostrado abaixo: + +```html +
+
+
+
+
+ Books +
+
+
+
+
+ + + + Book name + Book type + Publish date + Price + + + + + {{ data.name }} + {{ booksType[data.type] }} + {{ data.publishDate | date }} + {{ data.price }} + + + +
+
+``` + +> Usamos a [tabela PrimeNG](https://www.primefaces.org/primeng/#/table) neste componente. + +A página de livros resultante é mostrada abaixo: + +![livraria-lista-de-livros](images/bookstore-book-list.png) + +E esta é a estrutura de pastas e arquivos no final deste tutorial: + +![img](images/bookstore-angular-file-tree.png) + +> Este tutorial segue o [Guia de estilo angular](https://angular.io/guide/styleguide#file-tree) . + +### Próxima parte + +Veja a [próxima parte](Part-II.md) deste tutorial. + + + \ No newline at end of file diff --git a/docs/pt-BR/Tutorials/Angular/Part-II.md b/docs/pt-BR/Tutorials/Angular/Part-II.md new file mode 100644 index 0000000000..6113c325a3 --- /dev/null +++ b/docs/pt-BR/Tutorials/Angular/Part-II.md @@ -0,0 +1,582 @@ +## Tutorial Angular - Parte II + +### Sobre este tutorial + +Esta é a segunda parte da série de tutoriais angulares. Veja todas as peças: + +- [Parte I: Crie o projeto e uma página da lista de livros](Part-I.md) +- **Parte II: Criar, atualizar e excluir livros (este tutorial)** +- [Parte III: Testes de Integração](Part-III.md) + +Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb) . + +### 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 `CreateUpdateBookInput`no `books.ts`como mostrado abaixo: + +```js +export namespace Books { + //... + export interface CreateUpdateBookInput { + name: string; + type: BookType; + publishDate: string; + price: number; + } +} +``` + +`CreateUpdateBookInput`interface corresponde ao `CreateUpdateBookDto`no back-end. + +#### Método de Serviço + +Abra o `books.service.ts`e adicione um novo método, nomeado `create`para executar uma solicitação HTTP POST no servidor: + +```js +create(createBookInput: Books.CreateUpdateBookInput): Observable { + return this.restService.request({ + method: 'POST', + url: '/api/app/book', + body: createBookInput + }); +} +``` + +- `restService.request`A função obtém parâmetros genéricos para os tipos enviados e recebidos do servidor. Este exemplo envia um `CreateUpdateBookInput`objeto e recebe um `Book`objeto (você pode definir o tipo `void`de solicitação ou retorno, se não for usado). + +#### Definições de estado + +Adicione a `CreateUpdateBook`ação ao `books.actions.ts`conforme mostrado abaixo: + +```js +import { Books } from '../models'; + +export class CreateUpdateBook { + static readonly type = '[Books] Create Update Book'; + constructor(public payload: Books.CreateUpdateBookInput) {} +} +``` + +Abra `books.state.ts`e defina o `save`método que ouvirá uma `CreateUpdateBook`ação para criar um livro: + +```js +import { ... , CreateUpdateBook } from '../actions/books.actions'; +import { ... , switchMap } from 'rxjs/operators'; +//... +@Action(CreateUpdateBook) +save(ctx: StateContext, action: CreateUpdateBook) { + return this.booksService + .create(action.payload) + .pipe(switchMap(() => ctx.dispatch(new GetBooks()))); +} +``` + +Quando a `SaveBook`ação é despachada, o método save é executado. Ele chama o `create`método do `BooksService`definido anteriormente. Após a chamada de serviço, `BooksState`despacha a `GetBooks`ação para obter livros novamente do servidor para atualizar a página. + +#### Adicionar um modal ao BookListComponent + +Abra o `book-list.component.html`e adicione o `abp-modal`para mostrar / ocultar o modal para criar um novo livro. + +```html + + +

New Book

+
+ + + + + + +
+``` + +`abp-modal`é um componente pré-construído para mostrar os modais. Embora você possa usar outra abordagem para mostrar um modal, `abp-modal`fornece benefícios adicionais. + +Adicione um botão rotulado `New book`para mostrar o modal: + +```html +
+
+
+ Books +
+
+
+ +
+
+``` + +Abra a variável `book-list.component.ts`e adicione `isModalOpen`e `createBook`método para mostrar / ocultar o modal. + +```js +isModalOpen = false; + +//... + +createBook() { + this.isModalOpen = true; +} +``` + +![modal vazio](images/bookstore-empty-new-book-modal.png) + +#### Criar um formulário reativo + +> [Os formulários reativos](https://angular.io/guide/reactive-forms) fornecem uma abordagem orientada a modelo para lidar com entradas de formulário cujos valores mudam ao longo do tempo. + +Adicione uma `form`variável e injete um `FormBuilder`serviço `book-list.component.ts`como mostrado abaixo (lembre-se de adicionar a instrução de importação). + +```js +import { FormGroup, FormBuilder } from '@angular/forms'; + +form: FormGroup; + +constructor( + //... + private fb: FormBuilder +) {} +``` + +> O serviço [FormBuilder](https://angular.io/api/forms/FormBuilder) fornece métodos convenientes para gerar controles. Reduz a quantidade de clichê necessária para criar formulários complexos. + +Adicione o `buildForm`método para criar um formulário de livro. + +```js +buildForm() { + this.form = this.fb.group({ + name: ['', Validators.required], + type: [null, Validators.required], + publishDate: [null, Validators.required], + price: [null, Validators.required], + }); +} +``` + +- O `group`método de `FormBuilder`( `fb`) cria a `FormGroup`. +- Adicionado `Validators.required`método estático que valida o elemento de formulário relacionado. + +Modifique o `createBook`método como mostrado abaixo: + +```js +createBook() { + this.buildForm(); + this.isModalOpen = true; +} +``` + +#### Crie os elementos DOM do formulário + +Abra `book-list.component.html`e adicione o formulário no modelo de corpo do modal. + +```html + +
+
+ * + +
+ +
+ * + +
+ +
+ * + +
+ +
+ * + +
+
+
+``` + +- Este modelo cria um formulário com os campos Nome, Preço, Tipo e Data de publicação. + +> Usamos o [datepicker do NgBootstrap](https://ng-bootstrap.github.io/#/components/datepicker/overview) neste componente. + +Abra o `book-list.component.ts`e crie uma matriz chamada `bookTypeArr`: + +```js +//... +form: FormGroup; + +bookTypeArr = Object.keys(Books.BookType).filter( + bookType => typeof this.booksType[bookType] === 'number' +); +``` + +O `bookTypeArr`contém os campos da `BookType`enumeração. A matriz resultante é mostrada abaixo: + +```js +['Adventure', 'Biography', 'Dystopia', 'Fantastic' ...] +``` + +Essa matriz foi usada no modelo de formulário anterior (no `ngFor`loop). + +#### Requisitos do Datepicker + +Você precisa importar `NgbDatepickerModule`para o `books.module.ts`: + +```js +import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; + +@NgModule({ + imports: [ + // ... + NgbDatepickerModule, + ], +}) +export class BooksModule {} +``` + +Abra o `book-list.component.ts`e adicione `providers`como mostrado abaixo: + +```js +import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + // ... + providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], +}) +export class BookListComponent implements OnInit { +// ... +``` + +> O `NgbDateAdapter`valor do Datepicker converte em `Date`tipo. Consulte os [adaptadores datepicker](https://ng-bootstrap.github.io/#/components/datepicker/overview) para obter mais detalhes. + +![forma de livro novo](images/bookstore-new-book-form.png) + +#### Salvando o livro + +Abra o `book-list.component.html`e adicione um `abp-button`para salvar o formulário. + +```html + + + + +``` + +Isso adiciona um botão Salvar à área inferior do modal: + +![livraria-novo-livro-formulário-v2](images/bookstore-new-book-form-v2.png) + +Em seguida, defina um `save`método no `BookListComponent`: + +```js +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.ts`e adicione os métodos `getById`e `update`. + +```js +getById(id: string): Observable { + return this.restService.request({ + method: 'GET', + url: `/api/app/book/${id}` + }); +} + +update(updateBookInput: Books.CreateUpdateBookInput, id: string): Observable { + return this.restService.request({ + method: 'PUT', + url: `/api/app/book/${id}`, + body: updateBookInput + }); +} +``` + +#### Ação CreateUpdateBook + +Abra o parâmetro `books.actins.ts`e adicione `id`à `CreateUpdateBook`ação: + +```js +export class CreateUpdateBook { + static readonly type = '[Books] Create Update Book'; + constructor(public payload: Books.CreateUpdateBookInput, public id?: string) {} +} +``` + +Abra `books.state.ts`e modifique o `save`método conforme mostrado abaixo: + +```js +@Action(CreateUpdateBook) +save(ctx: StateContext, 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 `BooksService`dependência, adicionando-o ao `book-list.component.ts`construtor e adicione uma variável chamada `selectedBook`. + +```js +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 `buildForm`método para reutilizar o mesmo formulário ao editar um livro. + +```js +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 `editBook`método como mostrado abaixo: + +```js + editBook(id: string) { + this.booksService.getById(id).subscribe(book => { + this.selectedBook = book; + this.buildForm(); + this.isModalOpen = true; + }); + } +``` + +Adicionado `editBook`método para obter o livro de edição, criar o formulário e mostrar o modal. + +Agora, adicione a `selectedBook`definição ao `createBook`método para reutilizar o mesmo formulário ao criar um novo livro: + +```js + createBook() { + this.selectedBook = {} as Books.Book; + //... + } +``` + +Modifique o `save`método para passar o ID do livro selecionado, como mostrado abaixo: + +```js +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: + +```html + + + + Actions + Book name + Book type + Publish date + Price + + + + + +
+ +
+ +
+
+ + {{ data.name }} + {{ booksType[data.type] }} + {{ data.publishDate | date }} + {{ data.price }} + +
+
+``` + +- Adicionado um `th`para a coluna "Ações". +- Adicionado `button`com `ngbDropdownToggle`para abrir ações quando clicamos no botão. + +> Nós costumávamos usar o [NgbDropdown](https://ng-bootstrap.github.io/#/components/dropdown/examples) no menu suspenso de ações. + +A interface do usuário final é semelhante a: + +![botões de ações](https://raw.githubusercontent.com/abpframework/abp/master/docs/en/Tutorials/Angular/images/bookstore-actions-buttons.png) + +Atualize o cabeçalho modal para alterar o título com base na operação atual: + +```html + +

{{ selectedBook.id ? 'Edit' : 'New Book' }}

+
+``` + +![botões de ações](images/bookstore-edit-modal.png) + +### Exclusão de um livro existente + +#### BooksService + +Abra `books.service.ts`e inclua um `delete`método para excluir um livro com o `id`, executando uma solicitação HTTP no nó de extremidade relacionado: + +```js +delete(id: string): Observable { + return this.restService.request({ + method: 'DELETE', + url: `/api/app/book/${id}` + }); +} +``` + +#### Ação DeleteBook + +Adicione uma ação chamada `DeleteBook`para `books.actions.ts`: + +```js +export class DeleteBook { + static readonly type = '[Books] Delete'; + constructor(public id: string) {} +} +``` + +Abra o `books.state.ts`e adicione o `delete`método que ouvirá a `DeleteBook`ação para excluir um livro: + +```js +import { ... , DeleteBook } from '../actions/books.actions'; +//... +@Action(DeleteBook) +delete(ctx: StateContext, action: DeleteBook) { + return this.booksService.delete(action.id).pipe(switchMap(() => ctx.dispatch(new GetBooks()))); +} +``` + +- Adicionado `DeleteBook`à lista de importação. +- Usa `bookService`para excluir o livro. + +\#### Adicionar um botão Excluir + +Abra `book-list.component.html`e modifique `ngbDropdownMenu`para adicionar o botão excluir, como mostrado abaixo: + +```html +
+ ... + +
+``` + +A interface do usuário suspensa de ações finais é semelhante a abaixo: + +![livraria-final-ações-suspensa](images/bookstore-final-actions-dropdown.png) + +\#### Caixa de diálogo Excluir confirmação + +Abra `book-list.component.ts`e injete o `ConfirmationService`. + +```js +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`: + +```js +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 `delete`método mostra um pop-up de confirmação e assina a resposta do usuário. `DeleteBook`ação despachada somente se o usuário clicar no `Yes`botão O pop-up de confirmação é exibido abaixo: + +![livraria-confirmação-pop-up](images/bookstore-confirmation-popup.png) + +### Próxima parte + +Veja a [próxima parte](Part-III.md) deste tutorial. + diff --git a/docs/pt-BR/Tutorials/Angular/Part-III.md b/docs/pt-BR/Tutorials/Angular/Part-III.md new file mode 100644 index 0000000000..fc8d1b6c3b --- /dev/null +++ b/docs/pt-BR/Tutorials/Angular/Part-III.md @@ -0,0 +1,181 @@ +## Tutorial do ASP.NET Core MVC - Parte III + +### Sobre este tutorial + +Esta é a terceira parte da série de tutoriais Angular. Veja todas as peças: + +- [Parte I: Crie o projeto e uma página da lista de livros](Part-I.md) +- [Parte II: Criar, atualizar e excluir livros](Part-II.md) +- **Parte III: Testes de Integração (este tutorial)** + +Esta parte abrange os testes do **lado** do **servidor** . Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb) . + +### Testar projetos na solução + +Existem vários projetos de teste na solução: + +![livraria-teste-projetos](images/bookstore-test-projects-v3.png) + +Cada projeto é usado para testar o projeto de aplicativo relacionado. Os projetos de teste usam as seguintes bibliotecas para teste: + +- [xunit](https://xunit.github.io/) como a principal estrutura de teste. +- [Shouldly](http://shouldly.readthedocs.io/en/latest/) como uma biblioteca de asserções. +- [NSubstitute](http://nsubstitute.github.io/) como uma biblioteca de zombaria. + +### Adicionando dados de teste + +O modelo de inicialização contém a `BookStoreTestDataSeedContributor`classe no `Acme.BookStore.TestBase`projeto que cria alguns dados para executar os testes. + +Mude a `BookStoreTestDataSeedContributor`classe como mostrado abaixo: + +```csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Guids; + +namespace Acme.BookStore +{ + public class BookStoreTestDataSeedContributor + : IDataSeedContributor, ITransientDependency + { + private readonly IRepository _bookRepository; + private readonly IGuidGenerator _guidGenerator; + + public BookStoreTestDataSeedContributor( + IRepository bookRepository, + IGuidGenerator guidGenerator) + { + _bookRepository = bookRepository; + _guidGenerator = guidGenerator; + } + + public async Task SeedAsync(DataSeedContext context) + { + await _bookRepository.InsertAsync( + new Book + { + Id = _guidGenerator.Create(), + Name = "Test book 1", + Type = BookType.Fantastic, + PublishDate = new DateTime(2015, 05, 24), + Price = 21 + } + ); + + await _bookRepository.InsertAsync( + new Book + { + Id = _guidGenerator.Create(), + Name = "Test book 2", + Type = BookType.Science, + PublishDate = new DateTime(2014, 02, 11), + Price = 15 + } + ); + } + } +} +``` + +- Injetado `IRepository`e usado no `SeedAsync`para criar duas entidades de livro como dados de teste. +- `IGuidGenerator`Serviço usado para criar GUIDs. Embora `Guid.NewGuid()`funcionasse perfeitamente para testes, `IGuidGenerator`possui recursos adicionais especialmente importantes ao usar bancos de dados reais (consulte o documento de geração do [Guid](../../Guid-Generation.md) para obter mais informações). + +### Testando o BookAppService + +Crie uma classe de teste denominada `BookAppService_Tests`no `Acme.BookStore.Application.Tests`projeto: + +```csharp +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Application.Dtos; +using Xunit; + +namespace Acme.BookStore +{ + public class BookAppService_Tests : BookStoreApplicationTestBase + { + private readonly IBookAppService _bookAppService; + + public BookAppService_Tests() + { + _bookAppService = GetRequiredService(); + } + + [Fact] + public async Task Should_Get_List_Of_Books() + { + //Act + var result = await _bookAppService.GetListAsync( + new PagedAndSortedResultRequestDto() + ); + + //Assert + result.TotalCount.ShouldBeGreaterThan(0); + result.Items.ShouldContain(b => b.Name == "Test book 1"); + } + } +} +``` + +- `Should_Get_List_Of_Books`O teste simplesmente usa o `BookAppService.GetListAsync`método para obter e verificar a lista de usuários. + +Adicione um novo teste que crie um novo livro válido: + +```csharp +[Fact] +public async Task Should_Create_A_Valid_Book() +{ + //Act + var result = await _bookAppService.CreateAsync( + new CreateUpdateBookDto + { + Name = "New test book 42", + Price = 10, + PublishDate = DateTime.Now, + Type = BookType.ScienceFiction + } + ); + + //Assert + result.Id.ShouldNotBe(Guid.Empty); + result.Name.ShouldBe("New test book 42"); +} +``` + +Adicione um novo teste que tente criar um livro inválido e falhe: + +```csharp +[Fact] +public async Task Should_Not_Create_A_Book_Without_Name() +{ + var exception = await Assert.ThrowsAsync(async () => + { + await _bookAppService.CreateAsync( + new CreateUpdateBookDto + { + Name = "", + Price = 10, + PublishDate = DateTime.Now, + Type = BookType.ScienceFiction + } + ); + }); + + exception.ValidationErrors + .ShouldContain(err => err.MemberNames.Any(mem => mem == "Name")); +} +``` + +- Como o `Name`está vazio, o ABP lança um `AbpValidationException`. + +Abra a **janela Test Explorer** (use o menu Test -> Windows -> Test Explorer, se não estiver visível) e **execute Todos os** testes: + +![testes de serviço de livraria](images/bookstore-test-explorer.png) + +Parabéns, ícones verdes mostram que os testes foram aprovados com sucesso! + + + \ No newline at end of file diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-actions-buttons.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-actions-buttons.png new file mode 100644 index 0000000000..aecf31c1ad Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-actions-buttons.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-angular-file-tree.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-angular-file-tree.png new file mode 100644 index 0000000000..be05ad3e4a Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-angular-file-tree.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-backend-solution-v2.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-backend-solution-v2.png new file mode 100644 index 0000000000..79bcecb561 Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-backend-solution-v2.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-book-list.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-book-list.png new file mode 100644 index 0000000000..3f9717df6e Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-book-list.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-confirmation-popup.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-confirmation-popup.png new file mode 100644 index 0000000000..c3f46af821 Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-confirmation-popup.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-creating-book-list-terminal.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-creating-book-list-terminal.png new file mode 100644 index 0000000000..9f01e94121 Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-creating-book-list-terminal.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-creating-books-module-terminal.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-creating-books-module-terminal.png new file mode 100644 index 0000000000..c74b37b1b5 Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-creating-books-module-terminal.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-edit-modal.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-edit-modal.png new file mode 100644 index 0000000000..c911403792 Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-edit-modal.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-empty-new-book-modal.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-empty-new-book-modal.png new file mode 100644 index 0000000000..2a02802bb9 Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-empty-new-book-modal.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-final-actions-dropdown.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-final-actions-dropdown.png new file mode 100644 index 0000000000..6b0be415c4 Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-final-actions-dropdown.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-initial-book-list-page.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-initial-book-list-page.png new file mode 100644 index 0000000000..0b345ac61d Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-initial-book-list-page.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-initial-books-page-with-layout.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-initial-books-page-with-layout.png new file mode 100644 index 0000000000..484837f78a Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-initial-books-page-with-layout.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-initial-books-page.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-initial-books-page.png new file mode 100644 index 0000000000..4af86c50fa Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-initial-books-page.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-new-book-form-v2.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-new-book-form-v2.png new file mode 100644 index 0000000000..72513de6e5 Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-new-book-form-v2.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-new-book-form.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-new-book-form.png new file mode 100644 index 0000000000..95de64f8d4 Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-new-book-form.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-service-terminal-output.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-service-terminal-output.png new file mode 100644 index 0000000000..69aaccba31 Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-service-terminal-output.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-swagger-api.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-swagger-api.png new file mode 100644 index 0000000000..437c772503 Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-swagger-api.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-test-explorer.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-test-explorer.png new file mode 100644 index 0000000000..06e9e7d331 Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-test-explorer.png differ diff --git a/docs/pt-BR/Tutorials/Angular/images/bookstore-test-projects-v3.png b/docs/pt-BR/Tutorials/Angular/images/bookstore-test-projects-v3.png new file mode 100644 index 0000000000..32ea91b325 Binary files /dev/null and b/docs/pt-BR/Tutorials/Angular/images/bookstore-test-projects-v3.png differ