Sitemap

Angular: Http

Material completo: Iniciando os estudos com Angular

--

Press enter or click to view image in full size
Fonte: Fireship

Http: Criando o projeto e instalando o bootstrap

Vamos criar a estrutura inicial do projeto, para isso, podemos utilizar o Angular CLI para criar, passando os parametros para já criar o arquivo de rotas, com os arquivos de estilo na extensão SCSS e sem os arquivos de teste.

ng new requests-http --routing --style=scss -S

Nesse projeto, também utilizaremos o Bootstrap, podemos instalar a versão mais recente utilizando:

npm i bootstrap @types/bootstrap
npm i @types/bootstrap -D
npm i ngx-bootstrap

Utilizamos o ngx-bootstrap pois dessa maneira, não precisamos utilizar o JQuery e podemos utilizar os componentes do bootstrap diretamente com tags HTML.

E importamos o arquivo de estilização do Bootstrap em nosso arquivo de estilo global “styles.scss”.

@import '~bootstrap/dist/css/bootstrap.min.css'

E já criamos nosso menu de navegação inicial, utilizando a estrutura e as classes do proprio Bootstrap. Além de criarmos nosso container e o <router-outlet> para renderização das nossas rotas.

Press enter or click to view image in full size
requests-http\src\app\app.component.html

Http: Simulando servidor REST (json-server)

Para simular nosso back-end e conseguirmos realizar requisições HTTP, precisamos instalar o json-server que cuidará dessa parte para nós, podemos instalar utilizando:

npm i json-server -g
npm i @types/json-server

Depois de criado, precisamos criar uma estrutura inicial json que servirá como banco de dados. Na raíz do projeto, podemos criar um arquivo db.json contendo a estrutura:

Press enter or click to view image in full size
requests-http\db.json

Depois de tudo configurado, basta executar o comando abaixo para iniciar nosso servidor.

json-server --watch db.json

// Servidor irá executar nessa URL:
http://localhost:3000/cursos

Caso queira testar os métodos REST (Get, Post, Put, Delete), pode ser utilizada alguma extensão do VSCode ou utilizar aplicações como Postman ou Insomnia.

Http: GET — Listas registros

Antes de mais nada, precisamos criar nosso serviço, que ficará responsável por realizar as chamadas para nosso servidor. Basta criar um utilizando:

ng g s cursos/cursos

Após a criação, podemos começar a escrever nosso primeiro método list(), utilizando o módulo de HTTP através de injeção de dependência.

Press enter or click to view image in full size
requests-http\src\app\cursos\cursos.service.ts

Também podemos fazer a tipagem do retorno desse método de listar cursos, com o ID e nome que iremos receber do servidor.

export interface Curso {
id: number;
nome: string;
}

Em nosso componente, podemos chamar o método list() de dentro do nosso serviço, lembrando sempre de realizar o subscribe(). Criamos uma propriedade cursos que receberá o array de cursos que temos.

Press enter or click to view image in full size
requests-http\src\app\cursos\cursos-lista\cursos-lista.component.ts

Em nosso HTML, criamos uma estrutura basica de lista utilizando as proprias classes do Bootstrap. Realizamos um *ngFor em nossa tabela para que seja exibido todos os cursos em tela.

Press enter or click to view image in full size
requests-http\src\app\cursos\cursos-lista\cursos-lista.component.html

No final, teremos uma tabela parecida com essa, iremos implementar as funcionalidades no decorrer do módulo.

Press enter or click to view image in full size
Resultado do template.html

Http: Configurando variáveis de ambiente

Para não ficarmos consumindo diretamente a URL do nosso back-end, pensando que essa aplicação pode ir para produção e ficar exposta, podemos trabalhar com variáveis de ambiente.

Quando criamos nossa aplicação Angular, ele já cria uma pasta chamada “environment”, com os arquivos de ambiente local e produção. Criamos uma chave chamada API recebendo a URL do servidor.

Press enter or click to view image in full size
requests-http\src\environments\environment.ts

Em nosso serviço, ao invés de passarmos a URL diretamente, podemos realizar a importação do environment e acessar a environment.API. A unica coisa que foi preciso passar foi o endpoint /cursos.

Press enter or click to view image in full size
requests-http\src\app\cursos\cursos.service.ts

Http: GET + Pipe Async

Quando utilizamos subscribe(), como boa prática, precisamos nos desescrever dessas chamadas para que não tenhamos vazamento de memória.

Uma forma de não se preocupar com essas desinscrições, é utilizar o pipe async, sem se preocupar em realizar o subscribe() em nosso serviço.

Em nosso componente, criamos a propriedade cursos$ que será do tipo Observable. Em nosso ngOnInit(), apenas chamamos o método list() do nosso serviço sem realizar o subscribe().

Atribuímos o sifrão ($) devido a uma convenção da comunidade para saber que aquela propriedade é um observable.

Press enter or click to view image in full size
requests-http\src\app\cursos\cursos-lista\cursos-lista.component.ts

Em nosso HTML, em nosso *ngFor, bastaria adicionar o pipe async que já estaria funcionando. Mas, precisamos trabalhar também com o *ngIf e toda a tabela só deverá ser exibida caso exista cursos.

Nesse cenário, se adicionassemos “cursos$ | async” nos dois lugares, o Angular iria realizar duas chamadas desnecessárias. Para isso, em nosso *ngIf utilizamos como “cursos$ | async as cursos”, ou seja, estamos dizendo que teremos uma chamada assincrona e que deve ser referenciada como cursos (variável de template).

Agora que temos nossa variável de template (local), podemos chama-la diretamente em nosso *ngFor.

Press enter or click to view image in full size
requests-http\src\app\cursos\cursos-lista\cursos-lista.component.html

Temos o mesmo resultado de antes, mas agora utilizando o pipe async, além de termos adicionado uma tela simulando o carregamento dos cursos.

Press enter or click to view image in full size
Resultado do template.html

Http: RxJS — Unsubscribe automático

Nessa aula, aprendemos a utilizar alguns recursos que o RxJS nos oferece para se desinscrever automaticamente após realizar as nossas requsições.

Para ter acesso a esse material, basta acessar esse commit, realizar o download dos arquivos e inserir no projeto.

  • Componente sem unsubscribe: Após a emissão do evento, a conexão se mantém ativa, mesmo destruindo o componente, se emitirmos um novo evento ele é disparado.
  • Componente com unsubscribe: Podemos criar uma propriedade do tipo Subscription e atribuir a o serviço a ele. No ngOnDestroy(), podemos chama-lo passando o unsubscribe().
  • Componente com async: Já aprendido nos tópicos acima, criamos uma propriedade do tipo Observable, e utilizamos o pipe async. Após a chamada, o Angular se encarrega de cortar a comunicação.
  • Componente com takeUntil: Um dos operadores do RxJS, pode ser utilizado dentro de um pipe, antes de chamar o subscribe(). Emite um novo observable, ainda é necessário escrever bastante código e passando ao ngOnDestroy().
  • Componente com take: Uma das maneiras mais simples, basta chamar o operador take(1) dentro de um pipe e passar como parametro qual o valor de tentativas que deverá ser chamado aquele serviço. Após atender esses parametros, a conexão é finalizada.

Abaixo, temos um exemplo real e valores sendo emitidos para todos os componentes. Após a destruição dos componentes, quando clicamos novamente em emitir valor, somente o que não realizamos o unsubscribe consegue receber esse dado.

Press enter or click to view image in full size
Resultado do template.html

Http: Capturando erros (+Erro com async)

Nossa lista de cursos está sendo iniciada assim que o componente é renderizado, buscando em nosso servidor or cursos que temos cadastrados. Caso esse servidor esteja offline ou aconteça algum problema na requisição, ficaremos com a mensagem “Carregando cursos…” travada em tela.

Precisamos avisar para o usuário que não foi possivel carregar a lista de cursos. Em nosso HTML, criaremos um novo <ng-template>, com um *ngIf, recebendo o observable “error$” e o pipe async.

Press enter or click to view image in full size
requests-http\src\app\cursos\cursos-lista\cursos-lista.component.html

Em nosso componente, criaremos a propriedade “error$” que será um Subject, um tipo de observable que consegue enviar valores para muitos observaveis.

Press enter or click to view image in full size
requests-http\src\app\cursos\cursos-lista\cursos-lista.component.ts

Em nosso componente, podemos fazer a captura dos erros chamando dentro do pipe, o operador catchError(). O mesmo recebe uma função para podermos realizar nossa lógica, podemos aproveitar e emitir true para nosso error$.

Assim, por padrão será exibido nossa tela de carregando de cursos, mas caso aconteça algum erro no caminho, emitimos nosso erro, e é alterado para a mensagem de falha no serviço.

Press enter or click to view image in full size
requests-http\src\app\cursos\cursos-lista\cursos-lista.component.ts

Quando desligamos nosso servidor podemos observar que apesar de mostrar que os cursos estão carregando, quando nosso serviço falha, o erro é capturado e é exibido a mensagem de falha em tela.

Press enter or click to view image in full size
Resultado do template.html

Http: Alerta de Erro com Bootstrap e serviço genérico

Vamos alterar a mensagem simples que temos hoje em nosso HTML quando não é possivel carregar a lista de cursos. Iremos implementar os alertas que temos no Bootstrap, juntamente com uma modal com o NGX-Bootstrap.

Antes de qualquer coisa, como esse componente será de uso compartilhado por toda a aplicação, criaremos um novo módulo chamado Shared.

ng g m shared

Precisamos importa-lo em nosso App.Module, para que todos os componentes/serviços criados, esteja disponivel para o restante da aplicação.

Agora, criaremos o componente de alerta, onde ficará toda a lógica e HTML/CSS do nosso componente.

ng g c shared/alert-modal

No componente de alerta que acabamos de criar, vamos começar a estruturar nosso HTML. Pegamos de exemplo os próprios alertas do Bootstrap, que contém o alerta + botão de fechar.

As propriedades type e message criaremos posteriormente para controlar o que será exibido no alerta e a função onClose(), ficará responsável por fechar nossa modal.

Press enter or click to view image in full size
src/app/shared/alert-modal/alert-modal.component.html

No nosso component, precisaremos declarar o message e o type, ambas recebendo o decorator @Input() para receber os dados de fora. No método onClose(), estamos chamando o bsModalRref, é a referência do nosso modal, executando a função hide(), para fecha-lo. A documentação de como utilizar está em NGX-Bootstrap Modals.

Press enter or click to view image in full size
src/app/shared/alert-modal/alert-modal.component.ts

Pensando que poderemos utilizar esse modal em vários lugares da aplicação, criaremos um serviço, que irá criar as instâncias necessárias, passando os dados e requisitando a abertura do nosso AlertModalComponent.

Temos um método genérico showAlert(), responsável por criar a referẽncia da modal e passar o conteúdo (type/message). E também criamos os mais especificos como o showAlertDanger(), que chama o showAlert() e já passa também o tipo correto, assim, temos um método mais semântico.

Press enter or click to view image in full size
src/app/shared/alert-modal-service.service.ts

Finalizamos toda a estrutura do nosso componente de alerta, em nosso componente de cursos, sempre que falhar alguma requisição, podemos chamar nosso serviço e o método correspondente, exemplo: alertService.showAlertDanger().

Press enter or click to view image in full size
src/app/cursos/cursos-lista/cursos-lista.component.ts

Http: Criando formulário para criar e editar cursos

Precisamos criar nosso componente que servirá como formulário para criar novos cursos ou edita-los. Podemos criar utilizando:

ng g c cursos/cursos-form

Em nosso componente, começaremos criando nosso formulário, importando o FormBuilder e criando nosso formGroup. No nosso caso, teremos apenas o campo “nome”, passando alguns validadores.

Os métodos onSubmit() e onCancel() também foram criados para utilizarmos posteriormente no HTML.

Press enter or click to view image in full size
src/app/cursos/cursos-form/cursos-form.component.ts

Em relação ao HTML, criamos toda a estrutura fazendo uso do Bootstrap 5, atribuindo o formControlName no input correto e os botões Salvar/Cancelar executando s métodos que criamos.

Afim de dar um feedback visual ao usuário, também criamos as mensagens de erro, capturando no momento da digitação.

Press enter or click to view image in full size
src/app/cursos/cursos-form/cursos-form.component.html

Lembrando que como criamos um novo componente, precisamos informar quando ele será exibido. Criamos dois novos paths que utilizarão o mesmo componente.

  • novo — Utilizado na criação de novos cursos
  • editar/:id — Utilizado na edição de um curso, recebendo um id como parametro
Press enter or click to view image in full size

No final, teremos esse resultado.

Resultado do template.html

Http: POST — Criando cursos

Já temos em nosso serviço, o método list() para buscar todos os nossos cursos. Agora, criaremos o create(), responsável por criar os cursos em nosso banco.

Utilizando o Http Client, podemos utilizar o método post(), passando a URL do serviço e o curso como parametro. Como não precisamos ficar observando o retorno, não é necessário termos o subscribe(). Temos apenas um pipe() com take(1) para realizar somente uma tentativa de envio por vez.

Press enter or click to view image in full size
src/app/cursos/cursos-form/cursos-form.component.html

Agora, em nosso componente, no método onSubmit() que haviamos criado anteriormente, podemos realizar o subscribe() e realizar as devidas tratativas de sucesso e erro. Nesse caso, estamos utilizando nosso modal que também criamos nos tópicos anteriores.

Press enter or click to view image in full size
src/app/cursos/cursos-form/cursos-form.component.ts

Http: Editando cursos e Observables aninhados

Para conseguirmos editar um curso, precisamos atribuir uma função ao botão “Atualizar”, criaremos com o nome onEdit() recebendo o ID do curso. Como estamos iterando uma lista de cursos, já temos essa informação.

Press enter or click to view image in full size
src/app/cursos/cursos-lista/cursos-lista.component.html

Precisamos criar agora o método onEdit(), a unica coisa que faremos é redirecionar para a rota /editar/:id que criamos anteriormente passando o ID do curso. Se fizermos somente isso, o Angular irá se perder, pois irá montar a rota a partir do localhost:4200/.

Precisamos que ela seja criada a partir de localhost:4200/cursos/, para isso, utilizamos mais um argumento, chamando relativeTo.

Press enter or click to view image in full size
src/app/cursos/cursos-lista/cursos-lista.component.ts

O próximo passo, é criarmos nossa função de atualizar cursos. No nosso cursos.service, criaremos o método loadByID(id), ficará responsável por receber o ID do curso e buscar em nossa API.

Lembrando que utilizamos take(1) para realizar apenas uma tentativa de busca e já se desinscrever automaticamente do susbscribe.

Press enter or click to view image in full size
src/app/cursos/cursos.service.ts

Com o método criado, podemos começar a trabalhar no cursos-forms.component. O primeiro passo é criarmos mais um formGroup chamado “id”. Não utilizaremos no HTML, mas para auditoria posteriormente.

No ngOnInit(), utilizaremos o route.params do módulo ActivatedRouter,é um observable onde podemos recuperar o “params” com a propriedade ID. Com isso, podemos chamar o método loadByID do nosso service passando o ID e que também é um observable.

Note que agora teremos observables aninhados, no trecho de código comentado podemos visualizar bem essa situação. Não ha problemas fazer dessa maneira, apenas ficaŕa um código mais extenso dependendo da quantidade de observables que você possuí.

Mas o RxJS nos fornece alguns métodos para auxiliar nesses casos, que é o caso do map e switchMap.

  • map: Irá recuperar o ID do params e irá retornar o valor (nesse caso o ID) para a proxima função.
  • switchMap: Recebe o valor do map anterior, no caso, nosso ID e chamada o método loadByID(ID)

O switchMap apesar de também retornar dados assim como o map, ele é utilizado pois também retorna um observable, assim, é necessário aguardar o retorno desses dados para continuar o fluxo.

Por fim, podemos nos subscrever normalmente, afim de receber os dados finais do nosso curso, passando para o método updateForm().

O método updateForm() que também vamos criar, apenas atualiza nosso formulário utilizando patchValue com o ID e nome do curso recuperado.

Press enter or click to view image in full size
src/app/cursos/cursos-form/cursos-form.component.ts

Após a contrução de toda essa parte, teremos como resultado o comportamento abaixo:

Press enter or click to view image in full size
Resultado do template.html

Http: Editando cursos + Resolver (Rota)

Quando clicamos em ‘atualizar’ e a rota é alterada, podemos ver uma rápida atualização (layout shift) no campo do nome do curso. Isso porque a consulta do curso pelo ID, está sendo realizada dentro do nosso método ngOnInit(). Para resolver essa questão, precisamos utilizar o guarda de rota ‘Resolver’ para que já tenhamos os dados carregados assim que carregar o componente.

Vamos criar uma nova pasta ‘guards’ dentro de ‘cursos’ e gerar um novo resolver:

ng g g cursos/guards/curso-resolver

Assim que nossa guarda for criada, ela terá alguma interface padrão criada, podemos apagar e implementar a interface Resolve.

No método resolve(), temos como retorno um possivel objeto de Curso ou um Observable<Curso>. Como utilizaremos esse resolver tanto para editar um curso, como criar um novo curso, criaremos uma condição.

Caso nosso route tenha um params e esse seja o ID, iremos chamar nosso método loadByID, passando o ID que recuperamos da rota. Caso contrário, entendemos que estamos criando um novo curso, então retornamos um objeto com as propriedades nulas.

Press enter or click to view image in full size
src/app/cursos/guards/curso-resolver.guard.ts

Certo, criamos nosso resolver, mas precisamos começar a utiliza-lo, para isso, em nossas rotas, podemos ter a propriedade ‘resolve’, que daremos o nome de ‘curso’, onde recuperamos posteriormente os dados guardados.

Press enter or click to view image in full size
src/app/cursos/cursos-routing.module.ts

Em nosso cursos-form.component, não precisamos mais realizar a chamada no nosso ngOnInit(), diretamente do nosso construtor, iremos acessar o route.snapshot.data[‘curso’], sendo ‘curso’ o nome que demos lá em nosso arquivo de rotas.

Com os dados em mãos, assim que vamos inicializar nosso formulário, já podemos passar preenchendo seus valores.

Press enter or click to view image in full size
src/app/cursos/cursos-form/cursos-form.component.ts

Note que agora, quando nosso formulário é carregado, os valores já estão preenchidos, não sendo mais possível visualizar aquela mudança repentina dos valores.

Press enter or click to view image in full size
Resultado do template.html

Http: PUT Atualizando cursos

Vamos implementar agora nossa função de atualizar cursos e passar refatorando o código de criar cursos que temos no momento.

Em nosso cursos.service, podemos ver que o método create (que já tinhamos anteriormente e o método update (que criamos para atualizar nossos cursos) recebem o mesmo parametro, mudando apenas as rotas.

Vamos transforma-los em um método privado e somente quem irá chama-los será o método save(), recebendo o curso e verificando se temos ID ou não, para saber se devemos criar um editar um curso.

Press enter or click to view image in full size
src/app/cursos/cursos.service.ts

Em nosso cursos-form, dentro do onSubmit(), podemos fazer uma verificação e ver se nosso formulário possuí ID, se tiver chamamos os serviços correspondentes, da mesma forma que fizemos na hora de criar um curso (parte do código comentada).

Vamos refatorar nosso onSubmit(), como em nosso serviço criamos o método save(), podemos reutilizar a chamada para os dois casos (criação/edição), apenas atualizando a mensagem para que seja condizente em cada tipo de chamada.

Press enter or click to view image in full size
src/app/cursos/cursos-form/cursos-form.component.ts

Http: Popup de confirmação para remover cursos

Para finalizar nosso CRUD, vamos implementar nossa função de deletar um curso, implementando também uma modal para confirmação.

Primeiro, precisamos criar o método remove() dentro do nosso cursos.service para executar o http.delete passando a URL e o ID do nosso curso.

Press enter or click to view image in full size
src/app/cursos/cursos.service.ts

No nosso cursos-lista.html, precisamos atribuir uma função ao botão de remover curso, podemos criar o método onDelete(curso) passando a referência do nosso curso.

Press enter or click to view image in full size
src/app/cursos/cursos-lista/cursos-lista.component.html

Precisamos criar a estrutura da nossa modal de confirmação, para isso, vamos utilizar uma base que o proprio NGX-Bootstrap nos disponibiliza.

Press enter or click to view image in full size
src/app/cursos/cursos-lista/cursos-lista.component.html

Em nosso cursos-lista.component, precisamos criar algumas propriedades que servirá como auxilio na criação das funções.

  • deleteModalRef: É uma referência a instância que criaremos da modal, sendo possivel utilizar métodos para exibir e fechar a modal.
  • @ViewChild(‘deleteModal’) deleteModal: É uma forma de criarmos uma referência do nosso ng-template #deleteModal que criamos em nosso html.
Press enter or click to view image in full size
src/app/cursos/cursos-lista/cursos-lista.component.ts

Para finalizar, precisamos criar os métodos para dar ação ao botão de remover e os controles da modal.

  • onDelete(): Irá criar uma referência do curso atual que foi selecionado para remoção. Assim como irá criar uma nova instância de uma modal e atribuindo ao nosso deleteModalRef.
  • onConfirmDelete(): É o método que será executado caso clicarmos em “Sim” na modal. Com isso, podemos chamar o deleteModalRef.hide() para fechar a modal e chamar a função remove() do nosso cursosService passando o ID do curso a ser removido.
  • onDeclineDelete(): Não fazemos nenhuma ação de deletar curso, apenas chamamos o método hide() do deleteModalRef, fechando a nossa modal.
Press enter or click to view image in full size
src/app/cursos/cursos-lista/cursos-lista.component.ts

Com isso, agora nossa aplicação já consegue realizar a remoção dos cursos e nossa modal está funcionando perfeitamente.

Resultado do template.html

Http: Popup de confirmação genérica Bootstrap 4 (com RxJS)

A popup que criamos utilizando o NGX-Bootstrap é muito simples, vamos criar uma genérica, baseada no Bootstrap 5, para que possamos também definir o título e a mensagem da modal.

Get Rafael Simionato Zorzenon’s stories in your inbox

Join Medium for free to get updates from this writer.

Vamos criar um novo componente, que será nossa modal de confirmação:

ng g c shared/confirm-modal

O nosso HTML é o mesmo que o Bootstrap nos forneceu, passamos apenas colocando nossas variáveis (message, cancelTxt e confirmTxt) pois serão personalizaveis e as funções de onClose() que criaremos posteriormente.

Press enter or click to view image in full size
src/app/shared/confirm-modal/confirm-modal.component.html

Para o confirm-modal.component, também utilizaremos o BsModalRef para criar uma referência desse modal e conseguir manipula-lo depois.

As nossas propriedades serão do tipo @Input() pois ela receberá esses dados no momento da criação da modal.

Precisamos ter uma forma de receber em outra parte da aplicação a resposta de confirmação dessa modal, para isso, vamos utilizar a propriedade confirmResult sendo um Subject<boolean> retornando um boolean.

Press enter or click to view image in full size
src/app/shared/confirm-modal/confirm-modal.component.ts

No alert-modal.service, criaremos o método showConfirm(), passando os dados para o nosso popup de confirmação.

Também precisamos criar uma referência da nossa modal, que salvaremos na propriedade bsModalRef, e passaremos preenchendo os dados acessando a propriedade content.

Por fim, retornaremos o confirmResult, um booleano para sabermos se cancelamos ou confirmamos nossa ação.

Press enter or click to view image in full size
src/app/shared/alert-modal.service.ts

No cusos-lista.component, seguimos aplicando a lógica do nosso onDelete(), na propriedade result$ atribuimos nosso alertService.showConfirm() passando os dados do popup como titulo e descrição.

Transformamos em um observable() para que possamos fazer uso dos operadores rxJS e o success/error.

Press enter or click to view image in full size
src/app/cursos/cursos-lista/cursos-lista.component.ts

Agora temos nosso modal de confirmação genérico funcionando e recebendo os dados corretamente.

Resultado do template.html

Http: Criação de formulário para upload de arquivos

Vamos criar um novo módulo que servirá para o upload de arquivos na aplicação. É opcional e apenas para organização.

ng g m upload-file

Com o módulo criado, precisamos criar nossa nova rota no app-routing.module e dizer para o Angular qual o módulo será carregado ao acessar essa rota.

Press enter or click to view image in full size
src/app/app-routing.module.ts

Agora, criaremos nosso componente que terá o formulário para upload dos arquivos, podemos cria-lo com:

ng g c upload-file/upload-file

Podemos utilizar de exemplo o campo de upload que temos no Bootstrap 5, seguindo a própria documentação. Fazendo somente esses passos, nosso campo de upload já estará funcionando devido a versão do bootstrap não utilizar mais jquery.

Press enter or click to view image in full size
src/app/upload-file/upload-file/upload-file.component.html

Caso utilize uma versão de Boostrap anterior a 5 e que dependa do Jquery para funcionar, ao tentar realizar o upload de algum arquivo, nada acontecerá.

No curso, aprendemos a criar nossa própria função, que irá acessar a propriedade do campo e verificar se existe arquivos e exibir o nome deles no campo. Lembrando que essa parte é opcional.

Press enter or click to view image in full size
src/app/upload-file/upload-file/upload-file.component.ts

Abaixo, segue o resultado do nosso componente que criamos e já realizamos o upload de uma imagem chamada code.png.

Press enter or click to view image in full size
Resultado do template.html

Http: Upload de arquivo no back-end com NodeJS

Para conseguirmos salvar nossos arquivos, precisamos criar nosso back-end, como o foco do curso é Angular, criaremos algo simples com NodeJS e salvaremos os arquivos no proprio projeto front-end.

Precisamos criar uma nova pasta, pode ser na raiz do projeto e iniciar um novo projeto node com “npm init”. Após isso, vamos instalar nossas dependencias:

npm i -save express body-parser connect-multiparty cors

Após iniciar o projeto node, instalar as dependencias e criar um novo comando de script para executar nosso servidor, teremos o resultado abaixo.

Press enter or click to view image in full size
server/package.json

Abaixo, o código do nosso servidor, com as configurações de inicialização, liberação do cors, configuração para criar um novo diretório para salvar os uploads realizados, etc…

Press enter or click to view image in full size
server/src/index.js

Http: Upload de arquivo — Request com FormData

Com nosso “back-end” criado, precisamos criar a ação de upload em nosso formulário. Começaremos criando um botão de upload onde chamará o método onUpload() e o mesmo estará desabilitado caso não tenha arquivos selecionados.

Press enter or click to view image in full size
src/app/upload-file/upload-file/upload-file.component.html

Em nosso componente, vamos criar uma propriedade chamada “files” do tipo Set<File>, essa é uma interface do próprio JavaScript, ele permite termos uma coleção de objetos, mas os valores devem ser unicos, ou seja, caso selecionarmos 2 arquivos com o mesmo nome, será considerado somente um, para não haver duplicação.

No nosso onChange(), precisamos popular essa propriedade “files” com nossos arquivos, aproveitamos que já temos um “for” realização a iteração e adicionamos todos esses arquivos com files.add().

Vamos criar o método onUpload(), realizando algumas verificações de segurança e chamando o serviço de upload que criaremos posteriormente passando nosso array de objetos e a URL do nosso back-end.

Press enter or click to view image in full size
src/app/upload-file/upload-file/upload-file.component.ts

Agora, vamos criar nosso serviço, que irá realizar a chamada de fato ao nosso back-end, podemos criar utilizando o comando:

ng g s upload-file/upload-file

Como esse serviço será genérico, nosso método receberá nossa lista de arquivos e a URL para envio dos arquivos.

Criamos nossa propriedade formData do tipo FormData(), um tipo que permite que tenhamos um conjunto de várias posições com chave/valor. Precisamos iterar sobre nossa lista de arquivos e adiciona-los individualmente em nosso formData.

Depois, vamos criar nossa request com new HttpRequest(), lembrando que também é possível utilizar o http.post do Angular, mas é só uma maneira de aprendermos uma forma nova.

Press enter or click to view image in full size
src/app/upload-file/upload-file/upload-file.service.ts

Abaixo, podemos ver que nosso upload já está sendo feito e o arquivo sendo salvo no diretório que informamos em nosso back-end.

Resultado do template.html

Http: Removendo CORS com Angular Proxy

Em nosso back-end, fizemos a liberação do CORS para que duas aplicações em portas diferentes conseguissem se comunicar. É legal para o desenvolvimento local mas não é uma boa prática ter isso em produção.

Podemos remover a importação e a configuração do CORS que temos atualmente:

Press enter or click to view image in full size
server/src/index.js

Na raiz do nosso projeto Angular, podemos criar um arquivo chamado proxy.conf.js que receberá a configuração abaixo:

Press enter or click to view image in full size
proxy.conf.js

Caso preferirem, tambémé possivel criar a mesma configuração, mas com um arquivo de extensão json.

Press enter or click to view image in full size
proxy.conf.json

No nosso package.json, no script de “start” podemos adicionar uma nova flag informando que deve ser utilizado nosso arquivo de proxy. Agora, para iniciar o projeto, não utilizaremos mais “ng serve” e sim “npm run start” ou “yarn start”.

Press enter or click to view image in full size
package.json

Http: Upload arquivo — Barra de progresso + Observando eventos Http

Até o momento, temos o nosso formulário de upload funcionando e o upload sendo feito corretamente. Mas e se quisermos dar um feedback para o usuário? Dependendo do tamanho do arquivo, esse envio pode demorar e para isso precisamos de uma maneira de mostrar que o arquivo está sendo carregado.

Vamos utilizar a barra de progresso do próprio Bootstrap 5, podemos copiar o código do exemplo e alterar os valores para a nossa propriedade ‘progress’ que criaremos posteriormente.

Press enter or click to view image in full size
src/app/upload-file/upload-file/upload-file.component.html

No nosso serviço, precisamos realizar algumas mudanças, ao invés de passar somente a URL e o formData, podemos passar um terceiro argumento que recebe um objeto, e queremos observar todos os eventos e o progresso atual do nosso upload.

Somente é possível verificar o progresso atual de carregamento para casos de upload/download. Métodos com CRUD básicos como GET/POST/PUT/DELETE não é possivel.

Press enter or click to view image in full size
src/app/upload-file/upload-file/upload-file.service.ts

Se dermos um console.log() no response, em nosso componente, podemos visualizar agora todos os passos que o browser realiza para enviar nosso arquivo.

Um deles é do tipo 1 (Upload progress), onde contém a propriedade loaded (o quanto o browser já processou do arquivo) e o total, tamanho total do arquivo.

Existem outros tipos de Eventos HTTP e podem ser visualizados importando o tipo HttpEventType e clicando com o botão direito para acessa-lo.

Press enter or click to view image in full size
Console com a resposta da requisição POST com a propriedade observe/reportProgress=true

No nosso método onUpload(), em nosso subscribe, ao invés de receber diretamente o response, estamos recebendo os eventos HTTP. Caso seja do tipo 4 (Response), significa que o arquivo foi carregado e está pronto para ser enviado ao nosso back-end.

Caso seja do tipo 1 (UploadProgress), podemos realizar um calculo de porcentagem para verificar o quanto do arquivo já foi processado. Após o calculo, salvamos na propriedade “progress” para utilizar-mos no HTML.

Press enter or click to view image in full size

Abaixo, nossa barra de progresso funcionando, por conta da velocidade da rede, o arquivo conseguiu salvar de 0 a 100% em um único evento.

Press enter or click to view image in full size
Resultado do template.html

Http: Criando operador RxJS customizado

Nosso código para monitorar o progresso de upload de arquivos foi feito, mas se precisarmos utilizar em outros lugares da aplicação, precisaremos repeti-lo novamente.

Para isso, vamos criar nossos próprios operadores utilizando RxJS e poderemos reutilizar em qualquer lugar da aplicação.

Vamos criar um novo arquivo na pasta shared, onde criaremos duas funções:

  • filterResponse: Irá receber os eventos da nossa requisição HTTP e filtrará somente a que nos interessa que é o response, com o corpo final da requisição. Com o map, podemos pegar o response e retornar para o proximo fluxo da aplicação.
  • uploadProgress: Estamos passando uma função de callback como parametro, para que possamos manipular os dados em nosso componente. Também estamos verificando se o tipo do eventoé de UploadProgress, se for, fazemos o calculo da porcentagem.
Press enter or click to view image in full size
src/app/shared/rxjs-operators.ts

Agora, no nosso componente, podemos refatorar nosso código, passando todos os operadores customizados dentro do nosso pipe. E no subscribe, receberemos todo o response já tratado.

Press enter or click to view image in full size
src/app/upload-file/upload-file/upload-file.component.ts

E o resultado é o mesmo, interceptando o quanto já foi carregadod o nosso arquivo e no final, temos a resposta com o upload concluido e o corpo.

Press enter or click to view image in full size
Resultado do template.html

Http: Download do arquivo

Vamos criar duas novas rotas no index.js do nosso servidor, o node possuí o próprio método de download que utilizaremos para devolver o arquivo que desejamos.

Press enter or click to view image in full size
server/src/index.js

Esses arquivos foram criados dentro da pasta uploads, o conteúdo está vazio, é apenas para testarmos o funcionamento da aplicação.

server
|-uploads
|---report.pdf
|---report.xlsx

Em nosso HTML, podemos criar dois novos botões, que serão responsáveis por disparar as funções de download desses arquivos, as funções chamadas no evento de click, serão criadas posteriormente.

Press enter or click to view image in full size
src/app/upload-file/upload-file/upload-file.component.html

Em nosso serviço, criaremos dois métodos:

  • download(): Responsável por fazer a chamada GET para nosso end-point. O retorno dessa requisição será um blob (campo com dados binários do nosso arquivo), mas podemos lê-lo como um json, para poder manipula-lo posteiormente.
  • handleFile(): Irá criar uma nova instância do tipo Blob, depois criaremos um novo ObjectURL, forma do browser criar uma DOMString, uma string referenciando esse objeto. Depois criamos um elemento de link (não ficará visivel para o usuário), atribumos nosso blog e damos um click(). Com isso, o browser consegue realizar o download do nosso arquivo.
Press enter or click to view image in full size
src/app/upload-file/upload-file/upload-file.service.ts

Agora em nosso componente, basta criarmos as funções que serão passadas para nossos botões, ambas irão consumir as funções que criamos em nosso serviço.

Press enter or click to view image in full size
src/app/upload-file/upload-file/upload-file.component.ts

Esse é o resultado que temos, realizando os downloads corretamente. No exemplo da aula é mostrado como dar suporte ao IE/Firefox, mas no momento do estudo, o IE já perdeu totalmente o suporte e o código funcionou normalmente no Firefox.

Press enter or click to view image in full size
Resultado do template.html

Http: Criando tela de pesquisa

Vamos criar um novo módulo para trabalharmos nessa nova funcionalidade, podemos criar com:

ng g m reactive-search

Após a criação, basta realizar a importação do módulo e criar uma nova URL para esse módulo em app.routing.

{
path: 'busca-reativa',
loadChildren: () =>
import('./reactive-search/reactive-search.module').then((m) => m.ReactiveSearchModule),
},

Precisamos criar nosso componente onde ficará todo o formulário com a tabela dos registros, podemos criar com:

ng g c reactive-search/lib-search

O código HTML, iremos utilizar uma estrutura pronta, podemos utilizar um exemplo disponivel no Bootsnipp, basta copiar o HTML e o CSS fornecido no exemplo.

Também já atribuimos um formControl chamado queryField para o campo de input, e criamos um botão com o evento de click com a função onSearch().

Press enter or click to view image in full size
src/app/reactive-search/lib-search/lib-search.component.html

Em nosso componente, só criamos a estrutura inicial, com nosso formControl (como é somente um campo, não precisamos criar um formulário completo) e o método onSearch().

Press enter or click to view image in full size
src/app/reactive-search/lib-search/lib-search.component.ts

Até o momento, temos esse resultado, posteriormente criaremos a funcionalidade de pesquisa.

Press enter or click to view image in full size
Resultado do template.html

Http: Passando parâmetros na URL (HttpParams)

Agora, vamos utilizar uma CDN externa para buscar bibliotecas javascript,é com ela que iremos popular nossa tabela dependendo do valor que pesquisarmos.

Criamos três propriedades:

  • SEARCH_URL: É só uma propriedade para salvarmos a URL da CND e utilizarmos em nossas funções.
  • result$: É um observable que irá receber o response com todas as bibliotecas encontradas e também utilizaremos ela no HTML para percorrer os valores e preencher em tela.
  • total: Receberá o total de registros encontrados para exibir a soma em tela.

Dentro do nosso método onSearch(), começaremos a desenvolver nossa consulta da mesma forma que já fizemos anteriormente, utilizando o http.get(). Dessa forma, a API irá trazer todos os registros, o que seria muito custoso para nossa aplicação. Iremos começar a aplicar então parâmetros para buscar somente resultados que condiz com o que estamos pesquisando.

Essa API, ela pode receber dois tipos de parâmetros:

  • fields: São os campos que devem ser retornados, como o nome da biblioteca, versão, descrição, etc…
  • search: É o termo que estamos pesquisando e os resultados serão devolvidos com base nisso.

No curso, também nos foi apresentado 3 maneiras de passar esses parâmetros:

  • Forma 1: Podemos criar um objeto, passando as chaves e os valores que queremos passar como parâmetro.
  • Forma 2: A mais correta, utilizando o próprio HttpParams(), montando nossos parâmetros aos poucos e o Angular irá se encarregar de montar a URL final.
  • Forma 3: A forma menos recomendada, apesar de funcionar, é mais trabalhoso e o desenvolvedor precisa ficar se preocupando em montar URL e concatenar os valores corretamente.
Press enter or click to view image in full size
src/app/reactive-search/lib-search/lib-search.component.ts

Após montar toda a lógica do nosso componente, podemos começar a popular nosso HTML com essas informações, com as propriedades que criamos.

Como o result$ irá conter todos os valores das bibliotecas, podemos iterar sobre ele e utilizar o pipe async, para o Angular se desescrever automaticamente depois.

Press enter or click to view image in full size
src/app/reactive-search/lib-search/lib-search.component.html

Abaixo, temos o resultado do nosso template com as pesquisa funcionando, somente quando há dados a serem pesquisados.

Press enter or click to view image in full size
Resultado do template.html

Http: Pesquisa/Busca com Programação Reativa

Aprendemos no tópico sobre formulários reativos, que um controle de formulário também é um observable, com isso, podemos ficar monitorando sempre que esse campo tiver alguma mudança de valor, utilizando o valueChanges.

Legal, mas se dispararmos uma requisição sempre que houver uma alteração no campo, teremos requisições disparadas a cada caracter digitado e também sendo disparado quando colocamos espaços em branco.

Logo, precisamos realizar alguns tratamentos, e todos eles estão sendo explicados na ordem em que foi escrito.

  • map: No primeiro map, estamos pegando o valor atual do campo e realizando um trim(), não queremos campos sujos e com espaço.
  • filter: Só iremos prosseguir com a busca caso o valor digitado tenha pelo menos 2 caracteres, ou seja, o length dessa string seja maior que 1.
  • debouceTime: Não queremos que seja feita requisições a cada caracter digitado, então utilizamos o debounce para atrasar um pouco esse fluxo em 300ms, tempo suficente para digitarmos pelo menos um termo a ser pesquisado.
  • distinctUntilChanged: Caso o valor digitado seja o mesmo que o anterior, não permite avançar o fluxo, pois entende-se que não houve mudança de valores no campo.
  • switchMap: Precisamos realizar a requisição a nossa API e retornar um novo observable para que possamos manipula-lo nas próximas etapas, por isso, utilizamos o switchMap.
  • tap: Com o tap, apenas executamos ações, nenhum valor é retornado. Nesse caso, só estamos salvando o total de registros no “this.total”, não é necessário retornar e salvar no results$.
  • map: Agora sim, precisamos retornar todos os registros encontrados para o nosso results$, pois utilizaremos para iterar e popular nosso HTML, por isso utilizamos o operador map.
Press enter or click to view image in full size
src/app/reactive-search/lib-search/lib-search.component.ts

Temos o resultado final da aplicação, com as requisições sendo feitas a cada termo digitado, não é mais necessário termos o botão pesquisar. Lembrando que são alternativas e deve ser levado em conta o objetivo do seu projeto.

Press enter or click to view image in full size
Resultado do template.html

--

--

Rafael Simionato Zorzenon
Rafael Simionato Zorzenon

Written by Rafael Simionato Zorzenon

Engenheiro de Software Front-end e apaixonado por UX/UI Design.