Como criar e implantar o aplicativo de material angular

Publicados: 2022-03-10
Resumo rápido ↬ Um passo a passo da criação de um aplicativo web Angular 8 e um aplicativo gerador de QR Code completamente baseado em Angular enquanto hospedado na Netlify.

Angular é uma das escolhas populares ao criar novos aplicativos da web. Além disso, as especificações de “Design de material” tornaram-se uma escolha obrigatória para criar uma experiência mínima e envolvente hoje. Assim, qualquer novo projeto “Angular” usa principalmente a “Biblioteca Angular Material Design” para usar os componentes que seguem as especificações do material design. De animações suaves a feedback de interação adequado, tudo isso já está disponível como parte da biblioteca oficial de design de materiais para angular.

Após o desenvolvimento da aplicação web, o próximo passo é implantá-la. É aí que “Netlify” entra em cena. Com sua interface muito fácil de usar, implantação automática, divisão de tráfego para testes A/B e vários outros recursos, o Netlify é certamente uma ótima ferramenta.

O artigo será um passo a passo da criação de um aplicativo web Angular 8 usando a biblioteca oficial do Angular Material Design. Estaremos criando um aplicativo web gerador de QR Code totalmente baseado em Angular enquanto hospedado na Netlify.

Os arquivos para este tutorial podem ser encontrados no GitHub e uma versão demo é implantada aqui.

Começando

  1. Instale Angular 8,
  2. Crie uma conta no GitHub,
  3. Instale o Git no seu computador,
  4. Crie uma conta Netlify.

Observação : usarei o VSCode e o Microsoft Windows como IDE e sistema operacional preferidos, embora as etapas sejam semelhantes para qualquer outro IDE em qualquer outro sistema operacional.

Após a conclusão dos pré-requisitos acima, vamos começar!

Mais depois do salto! Continue lendo abaixo ↓

Simulações e planejamento

Antes de começarmos a criar o projeto, seria benéfico planejar com antecedência: Que tipo de interface do usuário desejaríamos em nosso aplicativo? Haverá peças reutilizáveis? Como o aplicativo irá interagir com serviços externos?

Primeiro, verifique os mocks da interface do usuário.

Página inicial (visualização grande)
Criando uma página QR (visualização grande)
Página de histórico (visualização grande)

Estas são as três páginas diferentes que estarão contidas no aplicativo. A página inicial será o ponto de partida do nosso aplicativo. A criação de uma página QR deve lidar com a criação de um novo código QR. A página Histórico mostrará todos os códigos QR salvos.

Os mockups não apenas fornecem uma ideia da aparência do aplicativo, mas também segregam a responsabilidade de cada página.

Uma observação (dos mocks) é que parece que a barra de navegação superior é comum em todas as páginas. Assim, a barra de navegação pode ser criada como um componente reutilizável e reutilizada.

Agora que temos uma boa ideia de como o aplicativo ficará e o que pode ser reutilizado, vamos começar.

Criando um novo projeto angular

Inicie o VSCode e abra uma janela de terminal no VSCode para gerar um novo projeto Angular.

Terminal no VSCode (visualização grande)

O terminal será aberto com um caminho padrão, conforme mostrado no prompt. Você pode mudar para um diretório preferencial antes de continuar; no caso do Windows, usarei o comando cd .

Navegando para o caminho preferido (visualização grande)

Avançando, angular-cli tem um comando para gerar novos projetos ng new <project-name> . Basta usar qualquer nome de projeto extravagante que você goste e pressione enter, por exemplo, ng new qr .

Isso acionará a mágica angular-cli; ele fornecerá algumas opções para configurar alguns aspectos do projeto, por exemplo, adicionando roteamento angular. Em seguida, com base nas opções selecionadas, ele gerará todo o esqueleto do projeto que poderá ser executado sem nenhuma modificação.

Para este tutorial, insira Sim para roteamento e selecione CSS para estilo. Isso gerará um novo projeto Angular:

Criando um novo projeto Angular (visualização grande)

Agora temos um projeto Angular totalmente funcional. Para garantir que tudo esteja funcionando corretamente, podemos executar o projeto digitando este comando no terminal: ng serve . Uh oh, mas espere, isso resulta em um erro. O que pode ter acontecido?

ng serve error (visualização grande)

Não se preocupe. Sempre que você cria um novo projeto usando angular-cli, ele gera todo o esqueleto dentro de uma pasta com o nome do projeto especificado no comando ng new qr . Aqui, teremos que alterar o diretório de trabalho atual para o que acabou de ser criado. No Windows, use o comando cd qr para alterar o diretório.

Agora, tente executar o projeto novamente com a ajuda de ng serve :

Projeto em execução (visualização grande)

Abra um navegador da Web, acesse a URL https://localhost:4200 para ver o projeto em execução. O comando ng serve executa o aplicativo na porta 4200 por padrão.

DICA : Para executá-lo em uma porta diferente, usamos o comando ng serve --port <any-port> por exemplo, ng serve --port 3000 .

Isso garante que nosso projeto básico do Angular esteja funcionando. Vamos continuar.

Precisamos adicionar a pasta do projeto ao VSCode. Vá para o menu “Arquivo” e selecione “Abrir pasta” e selecione a pasta do projeto. A pasta do projeto agora será mostrada na visualização do Explorer à esquerda.

Adicionando Biblioteca de Materiais Angular

Para instalar a biblioteca de materiais Angular, use o seguinte comando na janela do terminal: ng add @angular/material . Isso (novamente) fará algumas perguntas, como qual tema você deseja, se deseja animações padrão, se é necessário suporte ao toque, entre outras. Vamos apenas selecionar o tema padrão Indigo/Pink , Yes para adicionar a biblioteca HammerJS e animações do navegador.

Adicionando material angular (visualização grande)

O comando acima também configura todo o projeto para habilitar o suporte aos componentes do material.

  1. Ele adiciona dependências de projeto ao package.json ,
  2. Ele adiciona a fonte Roboto ao arquivo index.html ,
  3. Ele adiciona a fonte do ícone do Material Design ao seu index.html ,
  4. Ele também adiciona alguns estilos CSS globais a:
    • Remova as margens do corpo,
    • height: 100% no HTML e no corpo,
    • Defina Roboto como a fonte padrão do aplicativo.

Apenas para ter certeza de que tudo está bem, você pode executar o projeto novamente neste ponto, embora não notará nada de novo.

Adicionando a página inicial

Nosso esqueleto de projeto está pronto. Vamos começar adicionando a página inicial.

(Visualização grande)

Queremos manter nossa página inicial simples, assim como a imagem acima. Esta página inicial usa alguns componentes de material angular. Vamos dissecar.

  1. A barra superior é um elemento de nav HTML simples que contém um botão de estilo de material, mat-button , com uma imagem e um texto como filho. A cor da barra é a mesma que a cor primária que foi selecionada ao adicionar a biblioteca de materiais Angular;
  2. Uma imagem centralizada;
  3. Outro, mat-button , com apenas um texto como filho. Este botão permitirá que os usuários naveguem para a página de histórico;
  4. Um crachá de contagem, matBadge , anexado ao botão acima, mostrando o número de códigos QR salvos pelo usuário;
  5. Um botão de ação flutuante, mat-fab , no canto inferior direito com a cor de destaque do tema selecionado.

Divagando um pouco, vamos adicionar outros componentes e serviços necessários primeiro.

Adicionando cabeçalho

Conforme planejado anteriormente, a barra de navegação deve ser reutilizada, vamos criá-la como um componente angular separado. Abra o terminal no VSCode e digite ng gc header (abreviação de ng generate component header) e pressione Enter. Isso criará uma nova pasta chamada “header” que conterá quatro arquivos:

  • header.component.css : usado para fornecer estilo para este componente;
  • header.component.html : para adicionar elementos HTML;
  • header.component.spec.ts : para escrever casos de teste;
  • header.component.ts : para adicionar a lógica baseada em Typescript.
Componente de cabeçalho (visualização grande)

Para fazer com que o cabeçalho pareça como estava nos mocks, adicione o HTML abaixo em header.component.html :

 <nav class="navbar" [class.mat-elevation-z8]=true> <div> <button *ngIf="showBackButton" aria-hidden=false mat-icon-button routerLink="/"> <mat-icon> <i class="material-icons md-32">arrow_back</i> </mat-icon> </button> <span>{{currentTitle}}</span> </div> <button *ngIf="!showBackButton" aria-hidden=false mat-button class="button"> <img src="../../assets/qr-icon-white.png"> <span>QR Generator</span> </button> <button *ngIf="showHistoryNav" aria-hidden=false mat-button class="button" routerLink="/history"> <span>History</span> </button> </nav>

DICA : Para adicionar elevação para qualquer componente de material use [class.mat-elevation-z8]=true , o valor da elevação pode ser alterado alterando o valor z , neste caso é z8 . Por exemplo, para alterar a elevação para 16, use [class.mat-elevation-z16]=true .

No trecho de HTML acima, há dois elementos de material Angular sendo usados: mat-icon e mat-button/mat-icon-button . Seu uso é muito simples; primeiro, precisamos adicionar esses dois como módulos em nosso app.module.ts conforme mostrado abaixo:

Importação de módulo para mat-icon mat-button (visualização grande)

Isso nos permitirá usar esses dois elementos de material Angular em qualquer lugar em qualquer componente.

Para adicionar botões de material, o seguinte trecho de HTML é usado:

 <button mat-button> Material Button </button>

Existem diferentes tipos de elementos de botão de material disponíveis na biblioteca de materiais Angular, como mat-raised-button , mat-flat-button , mat-fab e outros; basta substituir o mat-button no trecho de código acima por qualquer outro tipo.

Tipos de botões de material (visualização grande)

O outro elemento é mat-icon que é usado para mostrar os ícones disponíveis na biblioteca de ícones do material. Quando a biblioteca de materiais Angular foi adicionada no início, uma referência à biblioteca de ícones de materiais também foi adicionada, o que nos permitiu usar ícones da vasta gama de ícones.

O uso é tão simples quanto:

 <mat-icon> <i class="material-icons md-32">arrow_back</i> </mat-icon>

A tag <i> aninhada pode ser usada para alterar o tamanho do ícone (aqui é md-32 ) que fará com que o tamanho do ícone seja de 32px em altura e largura. Esse valor pode ser md-24 , md-48 e assim por diante. O valor da tag <i> aninhada é o nome do ícone. (O nome pode ser encontrado aqui para qualquer outro ícone.)

Acessibilidade

Sempre que ícones ou imagens são usados, é imperativo que eles forneçam informações suficientes para fins de acessibilidade ou para um usuário de leitor de tela. ARIA (Accessible Rich Internet Applications) define uma maneira de tornar o conteúdo da web e os aplicativos da web mais acessíveis a pessoas com deficiência.

Um ponto a ser observado é que os elementos HTML que possuem sua semântica nativa (por exemplo, nav ) não precisam de atributos ARIA; o leitor de tela já saberia que nav é um elemento de navegação e o leria como tal.

As especificações ARIA são divididas em três categorias: funções, estados e propriedades. Digamos que uma div seja usada para criar uma barra de progresso no código HTML. Não possui semântica nativa; A função ARIA pode descrever este widget como uma barra de progresso, a propriedade ARIA pode denotar sua característica, como pode ser arrastada. O estado ARIA descreverá seu estado atual, como o valor atual da barra de progresso. Veja o trecho abaixo:

 <div role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100"> </div>

Da mesma forma, um atributo de ária muito usado: aria-hidden=true/false é usado. O valor true torna esse elemento invisível para leitores de tela.

Como a maioria dos elementos de interface do usuário usados ​​neste aplicativo tem significado semântico nativo, os únicos atributos ARIA usados ​​são para especificar estados de visibilidade ARIA. Para obter informações detalhadas, consulte isto.

O header.component.html contém alguma lógica para ocultar e mostrar o botão de volta dependendo da página atual. Além disso, o botão Home também contém uma imagem/logotipo que deve ser adicionado à pasta /assets . Baixe a imagem aqui e salve-a na pasta /assets .

Para estilizar a barra de navegação, adicione o css abaixo em header.component.css :

 .navbar { position: fixed; top: 0; left: 0; right: 0; z-index: 2; background: #3f51b5; display: flex; flex-wrap: wrap; align-items: center; padding: 12px 16px; } .button { color: white; margin: 0px 10px; }

Como queremos manter o componente de cabeçalho reutilizável em outros componentes, para decidir o que deve ser mostrado, precisaremos deles como parâmetros de outros componentes. Isso requer o uso do decorador @Input() que irá vincular as variáveis ​​que usamos em header.component.html .

Adicione estas linhas no arquivo header.component.ts :

 // Add these three lines above the constructor entry. @Input() showBackButton: boolean; @Input() currentTitle: string; @Input() showHistoryNav: boolean; constructor() { }

As três ligações acima serão passadas como um parâmetro de outros componentes que o componente de cabeçalho usará. Seu uso ficará mais claro assim que avançarmos.

Seguindo em frente, precisamos criar uma página inicial que possa ser representada por um componente Angular. Então vamos começar criando outro componente; digite ng gc home no terminal para gerar automaticamente o componente home. Como anteriormente, uma nova pasta chamada “home” será criada contendo quatro arquivos diferentes. Antes de prosseguir para modificar esses arquivos, vamos adicionar algumas informações de roteamento ao módulo de roteamento angular.

Adicionando roteamento

Angular fornece uma maneira de mapear URL para um componente específico. Sempre que alguma navegação acontece, o framework Angular monitora a URL e com base nas informações presentes no arquivo app-routing.module.ts ; ele inicializa o componente mapeado. Desta forma, componentes diferentes não precisam arcar com a responsabilidade de inicializar outros componentes. No nosso caso, o aplicativo possui três páginas navegáveis ​​clicando em botões diferentes. Conseguimos isso aproveitando o suporte de roteamento fornecido pela estrutura Angular.

O componente home deve ser o ponto de partida do aplicativo. Vamos adicionar essas informações ao arquivo app-routing.module.ts .

Componente inicial de roteamento (visualização grande)

A propriedade path é definida como uma string vazia; isso nos permite mapear o URL do aplicativo para o componente da página inicial, algo como google.com , que mostra a página inicial do Google.

DICA : O valor do caminho nunca começa com um/ ”, mas usa uma string vazia, mesmo que o caminho possa ser como search/coffee .

Voltando ao componente da página inicial, substitua o conteúdo de home.component.html por este:

 <app-header [showBackButton]="false" [currentTitle]=""></app-header> <app-profile></app-profile> <!-- FAB Fixed --> <button mat-fab class="fab-bottom-right" routerLink="/create"> <mat-icon> <i class="material-icons md-48">add</i> </mat-icon> </button>

Existem três partes no componente home:

  1. O componente de cabeçalho reutilizável <app-header> ,
  2. Componente de perfil <app-profile> ,
  3. O botão de ação flutuante no canto inferior direito.

O trecho de HTML acima mostra como o componente de cabeçalho reutilizável é usado em outros componentes; nós apenas usamos o seletor de componentes e passamos os parâmetros necessários.

O componente de perfil foi criado para ser usado como o corpo da página inicial — vamos criá-lo em breve.

O botão de ação flutuante com o ícone + é uma espécie de botão de material Angular do tipo mat-fab no canto inferior direito da tela. Ele tem a diretiva de atributo routerLink que usa as informações de rota fornecidas no app-routing.module.ts para navegação. Neste caso, o botão tem o valor da rota como /create que será mapeado para criar o componente.

Para fazer o botão criar flutuar no canto inferior direito, adicione o código CSS abaixo em home.component.css :

 .fab-bottom-right { position: fixed; left: auto; bottom: 5%; right: 10%; }

Como o componente do perfil deve gerenciar o corpo da página inicial, deixaremos o home.component.ts intacto.

Adicionando Componente de Perfil

Abra o terminal, digite ng gc profile e pressione enter para gerar o componente do perfil. Conforme planejado anteriormente, este componente lidará com o corpo principal da página inicial. Abra profile.component.html e substitua seu conteúdo por este:

 <div class="center profile-child"> <img class="avatar" src="../../assets/avatar.png"> <div class="profile-actions"> <button mat-raised-button matBadge="{{historyCount}}" matBadgeOverlap="true" matBadgeSize="medium" matBadgeColor="accent" color="primary" routerLink="/history"> <span>History</span> </button> </div> </div>

O trecho de HTML acima mostra como usar o elemento matBadge da biblioteca de materiais. Para poder usá-lo aqui, precisamos seguir o procedimento usual de adicionar MatBadgeModule ao arquivo app.module.ts . Os emblemas são pequenos descritores de status pictóricos para elementos da interface do usuário, como botões, ícones ou textos. Neste caso, é usado com um botão para mostrar a contagem de QR salvos pelo usuário. O emblema da biblioteca de materiais angulares tem várias outras propriedades, como definir a posição do emblema com matBadgePosition , matBadgeSize para especificar o tamanho e matBadgeColor para definir a cor do emblema.

Mais um ativo de imagem precisa ser adicionado à pasta de ativos: Download. Salve o mesmo na pasta /assets do projeto.

Abra profile.component.css e adicione isto:

 .center { top: 50%; left: 50%; position: absolute; transform: translate(-50%, -50%); } .profile-child { display: flex; flex-direction: column; align-items: center; } .profile-actions { padding-top: 20px; } .avatar { border-radius: 50%; width: 180px; height: 180px; }

O CSS acima alcançará a interface do usuário conforme planejado.

Continuando, precisamos de algum tipo de lógica para atualizar o valor da contagem do histórico, pois isso refletirá no matBadge usado anteriormente. Abra profile.component.ts e adicione o seguinte snippet apropriadamente:

 export class ProfileComponent implements OnInit { historyCount = 0; constructor(private storageUtilService: StorageutilService) { } ngOnInit() { this.updateHistoryCount(); } updateHistoryCount() { this.historyCount = this.storageUtilService.getHistoryCount(); } }

Adicionamos StorageutilService , mas não criamos esse serviço até agora. Ignorando o erro, concluímos nosso componente de perfil, que também finaliza nosso componente de página inicial. Revisitaremos esse componente de perfil depois de criar nosso serviço de utilitário de armazenamento. Ok, então vamos fazer isso.

Armazenamento local

HTML5 fornece recurso de armazenamento na web que pode ser usado para armazenar dados localmente. Isso fornece muito mais armazenamento em comparação com cookies - pelo menos 5 MB vs 4 KB. Existem dois tipos de armazenamento na Web com escopo e tempo de vida diferentes: Local e Sessão . O primeiro pode armazenar dados permanentemente enquanto o último é temporário e para uma única sessão. A decisão de selecionar o tipo pode ser baseada no caso de uso, em nosso cenário queremos salvar entre as sessões, então vamos com o armazenamento local .

Cada parte dos dados é armazenada em um par chave/valor. Usaremos o texto para o qual o QR é gerado como a chave e a imagem QR codificada como uma string base64 como o valor. Crie uma pasta de entidade, dentro da pasta crie um novo arquivo qr-object.ts e adicione o trecho de código conforme mostrado:

Modelo de entidade QR (visualização grande)

O conteúdo da aula:

 export class QR { text: string; imageBase64: string; constructor(text: string, imageBase64: string) { this.imageBase64 = imageBase64; this.text = text; } }

Sempre que o usuário salvar o QR gerado, criaremos um objeto da classe acima e salvaremos esse objeto usando o serviço de utilitário de armazenamento.

Crie uma nova pasta de serviço, estaremos criando muitos serviços, é melhor agrupá-los.

Pasta de serviços (visualização grande)

Altere o diretório de trabalho atual para services, cd services , para criar um novo serviço usando ng gs <any name> . Este é um atalho para ng generate service <any name> , digite ng gs storageutil e pressione enter

Isso criará dois arquivos:

  • storageutil.service.ts
  • storageutil.service.spec.ts

O último é para escrever testes de unidade. Abra storageutil.service.ts e adicione isto:

 private historyCount: number; constructor() { } saveHistory(key : string, item :string) { localStorage.setItem(key, item) this.historyCount = this.historyCount + 1; } readHistory(key : string) : string { return localStorage.getItem(key) } readAllHistory() : Array<QR> { const qrList = new Array<QR>(); for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); const value = localStorage.getItem(key); if (key && value) { const qr = new QR(key, value); qrList.push(qr); } } this.historyCount = qrList.length; return qrList; } getHistoryCount(): number { if (this.historyCount) { return this.historyCount; } this.readAllHistory(); return this.historyCount; } deleteHistory(key : string) { localStorage.removeItem(key) this.historyCount = this.historyCount - 1; }

Importe a classe qr-object para corrigir quaisquer erros. Para usar o recurso de armazenamento local, não há necessidade de importar nada novo, basta usar a palavra-chave localStorage para salvar ou obter valor com base em uma chave.

Agora abra o arquivo profile.component.ts novamente e importe a classe StorageutilService para finalizar corretamente o componente do perfil.

Executando o projeto, podemos ver que a página inicial está funcionando conforme o planejado.

Adicionando Criar página QR

Temos nossa página inicial pronta, embora o botão criar/adicionar não faça nada. Não se preocupe, a lógica real já foi escrita. Usamos uma diretiva routerLink para alterar o caminho base da URL para /create , mas não houve mapeamento adicionado ao arquivo app-routing.module.ts .

Vamos criar um componente que irá lidar com a criação de novos códigos QR, digite ng gc create-qr e pressione enter para gerar um novo componente.

Abra o arquivo app-routing.module.ts e adicione a entrada abaixo ao array de routes :

 { path: 'create', component: CreateQrComponent },

Isso mapeará o CreateQRComponent com a URL /create .

Abra create-qr.components.html e substitua o conteúdo por este:

 <app-header [showBackButton]="showBackButton" [currentTitle]="title" [showHistoryNav]="showHistoryNav"></app-header> <mat-card class="qrCard" [class.mat-elevation-z12]=true> <div class="qrContent"> <!--Close button section--> <div class="closeBtn"> <button mat-icon-button color="accent" routerLink="/" matTooltip="Close"> <mat-icon> <i class="material-icons md-48">close</i> </mat-icon> </button> </div> <!--QR code image section--> <div class="qrImgDiv"> <img *ngIf="!showProgressSpinner" src={{qrCodeImage}} width="200px" height="200px"> <mat-spinner *ngIf="showProgressSpinner"></mat-spinner> <div class="actionButtons" *ngIf="!showProgressSpinner"> <button mat-icon-button color="accent" matTooltip="Share this QR"> <mat-icon> <i class="material-icons md-48">share</i> </mat-icon> </button> <button mat-icon-button color="accent" (click)="saveQR()" matTooltip="Save this QR"> <mat-icon> <i class="material-icons md-48">save</i> </mat-icon> </button> </div> </div> <!--Textarea to write any text or link--> <div class="qrTextAreaDiv"> <mat-form-field> <textarea matInput [(ngModel)]="qrText" cdkTextareaAutosize cdkAutosizeMinRows="4" cdkAutosizeMaxRows="4" placeholder="Enter a website link or any text..."></textarea> </mat-form-field> </div> <!--Create Button--> <div class="createBtnDiv"> <button class="createBtn" mat-raised-button color="accent" matTooltip="Create new QR code" matTooltipPosition="above" (click)="createQrCode()">Create</button> </div> </div> </mat-card>

O trecho acima usa muitos dos elementos da biblioteca de materiais Angular. Conforme planejado, ele possui uma referência de componente de cabeçalho em que os parâmetros necessários são passados. Em seguida é o corpo principal da página de criação; ele consiste em um cartão de material Angular ou cartão mat-card centralizado e elevado até 12px conforme [class.mat-elevation-z12]=true é usado.

O cartão de material é apenas outro tipo de contêiner que pode ser usado como qualquer outra tag div . Embora a biblioteca de materiais forneça algumas propriedades para dispor informações bem definidas em um mat-card , como posicionamento da imagem, título, subtítulo, descrição e ação, como pode ser visto abaixo.

Exemplo de cartão (visualização grande)

No trecho de HTML acima, usamos mat-card como qualquer outro contêiner. Outro elemento da biblioteca de materiais usado é matTooltip ; é apenas mais uma dica de ferramenta com facilidade de uso, exibida quando o usuário passa o mouse sobre ou pressiona um elemento. Basta usar o snippet abaixo para mostrar a dica de ferramenta:

 matTooltip="Any text you want to show"

Ele pode ser usado com botões de ícone ou qualquer outro elemento de interface do usuário para transmitir informações extras. No contexto do aplicativo, ele está exibindo informações sobre o botão do ícone fechar. Para alterar o posicionamento da dica de ferramenta, matTooltipPosition é usado:

 matTooltip="Any text you want to show" matTooltipPosition="above"

Além matTooltip , mat-spinner é usado para mostrar o progresso do carregamento. Quando o usuário clica no botão “Criar”, uma chamada de rede é feita. É quando o controle giratório de progresso é mostrado. Quando a chamada de rede retorna com resultado, apenas ocultamos o spinner. Ele pode ser usado simplesmente assim:

 <mat-spinner *ngIf="showProgressSpinner"></mat-spinner>

showProgressSpinner é uma variável booleana que é usada para mostrar/ocultar o spinner de progresso. A biblioteca também fornece alguns outros parâmetros como [color]='accent' para alterar a cor, [mode]='indeterminate' para alterar o tipo de controle giratório de progresso. Um spinner de progresso indeterminado não mostrará o progresso da tarefa, enquanto um determinado pode ter valores diferentes para refletir o progresso da tarefa. Aqui, um spinner indeterminado é usado, pois não sabemos quanto tempo a chamada de rede levará.

A biblioteca de materiais fornece uma variante de textarea em conformidade com a diretriz de material, mas só pode ser usada como descendente de mat-form-field . O uso do material textarea é tão simples quanto o HTML padrão, como abaixo:

 <mat-form-field> <textarea matInput placeholder="Hint text"></textarea> </mat-form-field>

matInput é uma diretiva que permite que a tag de input nativa funcione com mat-form-field . A propriedade placeholder permite adicionar qualquer texto de dica para o usuário.

DICA : Use a propriedade cdkTextareaAutosize textarea para torná-la redimensionável automaticamente. Use cdkAutosizeMinRows e cdkAutosizeMaxRows para definir linhas e colunas e todos os três juntos para fazer o redimensionamento automático da área de texto até atingir o limite máximo de linhas e colunas definido.

Para usar todos esses elementos da biblioteca de materiais, precisamos adicioná-los no arquivo app.module.ts .

Criando importações de módulos QR (visualização grande)

Há uma imagem de espaço reservado sendo usada no HTML. Faça o download e salve-o na pasta /assets .

O HTML acima também requer estilo CSS, então abra o arquivo create-qr.component.ts e adicione o seguinte:

 .qrCard { display: flex; flex-direction: column; align-items: center; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 20%; height: 65%; padding: 50px 20px; } .qrContent { display: flex; flex-direction: column; align-items: center; width: 100%; } .qrTextAreaDiv { width: 100%; display: flex; flex-direction: row; justify-content: center; padding: 0px 0px; position: absolute; bottom: 10%; } .createBtn { left: 50%; transform: translate(-50%, 0px); width: 80%; } .createBtnDiv { position: absolute; bottom: 5%; width: 100%; } .closeBtn { display: flex; flex-direction: row-reverse; align-items: flex-end; width: 100%; margin-bottom: 20px; } .closeBtnFont { font-size: 32px; color: rgba(0,0,0,0.75); } .qrImgDiv { top: 20%; position: absolute; display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; } .actionButtons { display: flex; flex-direction: row; padding-top: 20px; }

Vamos conectar a interface do usuário com lógica. Abra o arquivo create-qr.component.ts e adicione o código abaixo, deixando as linhas que já estão presentes:

 export class CreateQrComponent implements OnInit { qrCodeImage = '../../../assets/download.png'; showProgressSpinner = false; qrText: string; currentQR; showBackButton = true; title = 'Generate New QR Code'; showHistoryNav = true; constructor(private snackBar: MatSnackBar, private restutil: RestutilService, private storageService: StorageutilService) { } ngOnInit() { } createQrCode() { //Check if any value is given for the qr code text if (!!this.qrText) { //Make the http call to load qr code this.loadQRCodeImage(this.qrText); } else { //Show snackbar this.showSnackbar('Enter some text first') } } public loadQRCodeImage(text: string) { // Show progress spinner as the request is being made this.showProgressSpinner = true; // Trigger the API call this.restutil.getQRCode(text).subscribe(image =>{ // Received the result - as an image blob - require parsing this.createImageBlob(image); }, error => { console.log('Cannot fetch QR code from the url', error) // Hide the spinner - show a proper error message this.showProgressSpinner = false; }); } private createImageBlob(image: Blob) { // Create a file reader to read the image blob const reader = new FileReader(); // Add event listener for "load" - invoked once the blob reading is complete reader.addEventListener('load', () => { this.qrCodeImage = reader.result.toString(); //Hide the progress spinner this.showProgressSpinner = false; this.currentQR = reader.result.toString(); }, false); // Read image blob if it is not null or undefined if (image) { reader.readAsDataURL(image); } } saveQR() { if (!!this.qrText) { this.storageService.saveHistory(this.qrText, this.currentQR); this.showSnackbar('QR saved') } else { //Show snackbar this.showSnackbar('Enter some text first') } } showSnackbar(msg: string) { //Show snackbar this.snackBar.open(msg, '', { duration: 2000, }); } }

Para fornecer informações contextuais aos usuários, também usamos o MatSnackBar da biblioteca de design de materiais. Isso aparece como um pop-up abaixo da tela e permanece por alguns segundos antes de desaparecer. Este não é um elemento, mas sim um serviço que pode ser invocado a partir do código Typescript.

O trecho acima com o nome do método showSnackbar mostra como abrir um snackbar, mas antes que ele possa ser usado, precisamos adicionar a entrada MatSnackBar no arquivo app.module.ts assim como fizemos para outros elementos da biblioteca de materiais.

DICA : Nas versões recentes da biblioteca de materiais Angular, não há uma maneira direta de alterar o estilo da lanchonete. Em vez disso, é preciso fazer duas adições ao código.

Primeiro, use o CSS abaixo para alterar as cores de fundo e primeiro plano:

 ::ng-deep snack-bar-container.snackbarColor { background-color: rgba(63, 81, 181, 1); } ::ng-deep .snackbarColor .mat-simple-snackbar { color: white; }

Segundo, use uma propriedade chamada panelClass para definir o estilo para a classe CSS acima:

 this.snackBar.open(msg, '', { duration: 2000, panelClass: ['snackbarColor'] });

As duas combinações acima permitirão um estilo personalizado para o componente snackbar da biblioteca de design de materiais.

Isso conclui as etapas de como criar uma página QR, mas ainda falta uma peça. Verificando o arquivo create-qr.component.ts , ele mostrará um erro referente à peça que falta. A peça que faltava nesse quebra-cabeça é RestutilService , responsável por buscar a imagem do código QR da API de terceiros.

No terminal, altere o diretório atual para services digitando ng gs restutil e pressionando Enter. Isso criará os arquivos RestUtilService. Abra o arquivo restutil.service.ts e adicione este snippet:

 private edgeSize = '300'; private BASE_URL = 'https://api.qrserver.com/v1/create-qr-code/?data={data}!&size={edge}x{edge}'; constructor(private httpClient: HttpClient) { } public getQRCode(text: string): Observable { // Create the url with the provided data and other options let url = this.BASE_URL; url = url.replace("{data}", text).replace(/{edge}/g, this.edgeSize); // Make the http api call to the url return this.httpClient.get(url, { responseType: 'blob' }); } private edgeSize = '300'; private BASE_URL = 'https://api.qrserver.com/v1/create-qr-code/?data={data}!&size={edge}x{edge}'; constructor(private httpClient: HttpClient) { } public getQRCode(text: string): Observable { // Create the url with the provided data and other options let url = this.BASE_URL; url = url.replace("{data}", text).replace(/{edge}/g, this.edgeSize); // Make the http api call to the url return this.httpClient.get(url, { responseType: 'blob' }); }

O serviço acima busca a imagem QR da API de terceiros e, como a resposta não é do tipo JSON, mas uma imagem, especificamos o responseType como 'blob' no snippet acima.

Angular fornece a classe HttpClient para se comunicar com qualquer servidor de suporte HTTP. Ele fornece muitos recursos, como filtrar a solicitação antes de ser disparada, recuperar a resposta, habilitar o processamento da resposta por meio de retornos de chamada e outros. Para usar o mesmo, adicione uma entrada para o HttpClientModule no arquivo app.module.ts .

Por fim, importe este serviço para o arquivo create-qr.component.ts para concluir a criação do código QR.

Mas espere! Há um problema com a lógica de criação QR acima. Se o usuário usar o mesmo texto para gerar o QR repetidamente, isso resultará em uma chamada de rede. Uma maneira de corrigir isso é armazenar em cache a solicitação com base, servindo assim a resposta do cache se o texto da solicitação for o mesmo.

Solicitação de armazenamento em cache

Angular fornece uma maneira simplificada de fazer chamadas HTTP, HttpClient, juntamente com HttpInterceptors para inspecionar e transformar solicitações ou respostas HTTP de e para servidores. Ele pode ser usado para autenticação ou armazenamento em cache e muitas dessas coisas, vários interceptores podem ser adicionados e encadeados para processamento adicional. Nesse caso, estamos interceptando solicitações e atendendo a resposta do cache se o texto QR for o mesmo.

Crie uma pasta interceptor e, em seguida, crie um arquivo cache-interceptor.ts :

Interceptor de cache (visualização grande)

Adicione o trecho de código abaixo ao arquivo:

 import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpResponse, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; import { tap } from 'rxjs/operators'; import { of, Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class RequestCachingService implements HttpInterceptor { private cacheMap = new Map<string, HttpResponse<any>>(); constructor() { } intercept(req: HttpRequest , next: HttpHandler): Observable<HttpEvent<any>> { const cachedResponse = this.cacheMap.get(req.urlWithParams); if (cachedResponse) { return of(cachedResponse); } return next.handle(req).pipe(tap(event => { if (event instanceof HttpResponse) { this.cacheMap.set(req.urlWithParams, event); } })) } } import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpResponse, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; import { tap } from 'rxjs/operators'; import { of, Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class RequestCachingService implements HttpInterceptor { private cacheMap = new Map<string, HttpResponse<any>>(); constructor() { } intercept(req: HttpRequest , next: HttpHandler): Observable<HttpEvent<any>> { const cachedResponse = this.cacheMap.get(req.urlWithParams); if (cachedResponse) { return of(cachedResponse); } return next.handle(req).pipe(tap(event => { if (event instanceof HttpResponse) { this.cacheMap.set(req.urlWithParams, event); } })) } }

No snippet de código acima, temos um mapa com a chave sendo o URL da solicitação e a resposta como o valor. Verificamos se a URL atual está presente no mapa; if it is, then return the response (the rest is handled automatically). If the URL is not in the map, we add it.

We are not done yet. An entry to the app.module.ts is required for its proper functioning. Add the below snippet:

 import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { CacheInterceptor } from './interceptor/cache-interceptor'; providers: [ { provide: HTTP_INTERCEPTORS, useClass: CacheInterceptor, multi: true } ],

This adds the caching feature to our application. Let's move on to the third page, the History page.

Adding The History Page

All the saved QR codes will be visible here. To create another component, open terminal type ng gc history and press Enter.

Open history.component.css and add the below code:

 .main-content { padding: 5% 10%; } .truncate { width: 90%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .center-img { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); display: flex; flex-direction: column; align-items: center; }

Open history.component.html and replace the content with this:

 <app-header [showBackButton]="showBackButton" [currentTitle]="title" [showHistoryNav]="showHistoryNav"></app-header> <div class="main-content"> <mat-grid-list cols="4" rowHeight="500px" *ngIf="historyList.length > 0"> <mat-grid-tile *ngFor="let qr of historyList"> <mat-card> <img mat-card-image src="{{qr.imageBase64}}"> <mat-card-content> <div class="truncate"> {{qr.text}} </div> </mat-card-content> <mat-card-actions> <button mat-button (click)="share(qr.text)">SHARE</button> <button mat-button color="accent" (click)="delete(qr.text)">DELETE</button> </mat-card-actions> </mat-card> </mat-grid-tile> </mat-grid-list> <div class="center-img" *ngIf="historyList.length == 0"> <img src="../../assets/no-see.png" width="256" height="256"> <span>Nothing to see here</span> </div> </div>

As usual, we have the header component at the top. Then, the rest of the body is a grid list that will show all the saved QR codes as individual mat-card . For the grid view, we are using mat-grid-list from the Angular material library. As per the drill, before we can use it, we have to first add it to the app.module.ts file.

Mat grid list atua como um contêiner com vários filhos de blocos chamados mat-grid-tile . No trecho de HTML acima, cada bloco é criado usando mat-card usando algumas de suas propriedades para posicionamento genérico de outros elementos da interface do usuário. Podemos fornecer o number of columns e rowHeight , que é usado para calcular a largura automaticamente. No snippet acima, estamos fornecendo o número de colunas e o valor rowHeight .

Estamos usando uma imagem de espaço reservado quando o histórico está vazio, faça o download e adicione à pasta de ativos.

Para implementar a lógica de preenchimento de todas essas informações, abra o arquivo history.component.ts e adicione o trecho abaixo na classe HistoryComponent :

 showBackButton = true; title = 'History'; showHistoryNav = false; historyList; constructor(private storageService: StorageutilService, private snackbar: MatSnackBar ) { } ngOnInit() { this.populateHistory(); } private populateHistory() { this.historyList = this.storageService.readAllHistory(); } delete(text: string) { this.storageService.deleteHistory(text); this.populateHistory(); } share(text: string) { this.snackbar.open(text, '', {duration: 2000,}) }

A lógica acima apenas busca todo o QR salvo e preenche a página com ele. Os usuários podem excluir o QR salvo que excluirá a entrada do armazenamento local.

Então isso termina nosso componente de história... ou não? Ainda precisamos adicionar o mapeamento de rotas para este componente. Abra app-routing.module.ts e adicione um mapeamento para a página de histórico também:

 { path: 'history', component: HistoryComponent },

Todo o array de rotas deve estar assim agora:

 const routes: Routes = [ { path: '', component: HomeComponent }, { path: 'create', component: CreateQrComponent }, { path: 'history', component: HistoryComponent }, ];

Agora é um bom momento para executar o aplicativo para verificar o fluxo completo, então abra o terminal e digite ng serve e pressione Enter. Em seguida, vá para localhost:4200 para verificar o funcionamento do aplicativo.

Adicionar ao GitHub

Antes de prosseguir para a etapa de implantação, seria bom adicionar o projeto a um repositório do GitHub.

  1. Abra o GitHub.
  2. Crie um novo repositório.
  3. Novo repositório do GitHub (visualização grande)
  4. No VS Code, use o terminal e siga o primeiro conjunto de comandos mencionado no guia de início rápido para enviar todos os arquivos do projeto.
  5. Adicionando um projeto no GitHub (visualização grande)

Basta atualizar a página para verificar se todos os arquivos estão visíveis. A partir deste ponto, quaisquer alterações do git (como commit, pull/push) serão refletidas neste repositório recém-criado.

Netlify e implantação

Nosso aplicativo é executado em nossa máquina local, mas para permitir que outras pessoas o acessem, devemos implantá-lo em uma plataforma de nuvem e registrá-lo em um nome de domínio. É aqui que o Netlify entra em cena. Ele fornece serviços de implantação contínua, integração com o GitHub e muitos outros recursos para se beneficiar. No momento, queremos habilitar o acesso global ao nosso aplicativo. Vamos começar.

  1. Inscreva-se no Netlify.
  2. No painel, clique no botão Novo site do Git .
  3. Novo site da Netlify (visualização grande)
  4. Clique em GitHub na próxima tela.
  5. Netlify selecione o provedor git (visualização grande)
  6. Autorize o Netlify a poder acessar seus repositórios do GitHub.
  7. Autorização do Netlify GitHub (visualização grande)
  8. Procure e selecione o repositório qr recém-criado.
  9. Seleção de repositório do Netlify GitHub (visualização grande)
  10. Netlify, na próxima etapa, nos permite escolher a ramificação do repositório GitHub para implantações. Normalmente, usa-se o branch master , mas também pode-se ter um branch de release separado que contém apenas recursos relacionados ao lançamento e estáveis.
  11. Compilação e implantação do Netlify (visualização grande)

Como este é um aplicativo da Web Angular, adicione ng build --prod como o comando build. Os diretórios publicados serão dist/qr conforme mencionado no arquivo angular.json .

Caminho de construção angular (visualização grande)

Agora clique no botão Deploy site que acionará uma compilação de projeto com o comando ng build --prod e produzirá o arquivo para dist/qr .

Como fornecemos as informações de caminho para o Netlify, ele coletará automaticamente os arquivos corretos para atender o aplicativo da web. Netlify adiciona um domínio aleatório ao nosso aplicativo por padrão.

Site Netlify implantado (visualização grande)

Agora você pode clicar no link fornecido na página acima para acessar o aplicativo de qualquer lugar. Finalmente, o aplicativo foi implantado.

Domínio personalizado

Na imagem acima, a URL do nosso aplicativo é mostrada enquanto o subdomínio é gerado aleatoriamente. Vamos mudar isso.

Clique no botão Domain settings e, na seção Domínios personalizados, clique no menu de 3 pontos e selecione Edit site name .

Domínio personalizado (visualização grande)

Isso abrirá um pop-up no qual um novo nome de site poderá ser inserido; esse nome deve ser exclusivo no domínio Netlify. Insira qualquer nome de site disponível e clique em Salvar .

Nome do site (visualização grande)

Agora o link para nosso aplicativo será atualizado com o novo nome do site.

Teste de divisão

Outro recurso interessante oferecido pelo Netlify é o teste A/B. Ele permite a divisão de tráfego para que diferentes conjuntos de usuários interajam com diferentes implementações de aplicativos. Podemos adicionar novos recursos a uma ramificação diferente e dividir o tráfego para essa implantação de ramificação, analisar o tráfego e mesclar a ramificação de recurso com a ramificação de implantação principal. Vamos configurá-lo.

O pré-requisito para habilitar o teste A/B é um repositório GitHub com pelo menos duas ramificações. Vá para o repositório de aplicativos no GitHub que foi criado anteriormente e crie uma nova ramificação a arquivo .

Criar nova ramificação (visualização grande)

O repositório agora terá um branch master e a branch. O Netlify precisa ser configurado para fazer implantações de filiais, então abra o painel do Netlify e clique em Settings . No lado esquerdo, clique em Build & Deploy , depois em Continuous Deployment , depois no lado direito na seção Deploy contexts , clique em Edit settings .

Implantações de filiais (visualização grande)

Na subseção Branch deploys , selecione a opção “Deixe-me adicionar ramificações individuais”, insira os nomes das ramificações e salve-o.

A implantação de chaves é outro recurso útil fornecido pelo Netlify; podemos selecionar quais branches de repositório do GitHub implantar e também podemos habilitar visualizações para cada pull request para o branch master antes da mesclagem. Este é um recurso interessante que permite que os desenvolvedores realmente testem suas alterações ao vivo antes de adicionar suas alterações de código à ramificação de implantação principal.

Agora, clique na opção da guia Split Testing na parte superior da página. As configurações de teste de divisão serão apresentadas aqui.

Teste de divisão (visualização grande)

Podemos selecionar a ramificação (diferente da ramificação de produção) — neste caso, a arquivo . Também podemos brincar com as configurações de divisão de tráfego. Com base na porcentagem de tráfego atribuída a cada filial, a Netlify redirecionará alguns usuários para o aplicativo implantado usando a filial e outros para a filial master . Após a configuração, clique no botão Start test para habilitar a divisão de tráfego.

DICA : O Netlify pode não reconhecer que o repositório GitHub conectado tem mais de uma ramificação e pode dar este erro:

Erro de teste de divisão (visualização grande)

Para resolver isso, basta reconectar-se ao repositório nas opções Build & Deploy .

O Netlify também fornece muitos outros recursos. Acabamos de passar por alguns de seus recursos úteis para demonstrar a facilidade de configurar diferentes aspectos do Netlify.

Isso nos leva ao fim de nossa jornada. Criamos com sucesso um design de Material Angular baseado em um aplicativo da web e o implantamos no Netlify.

Conclusão

Angular é uma estrutura excelente e popular para desenvolvimento de aplicativos da web. Com a biblioteca oficial de design de materiais Angular, é muito mais fácil criar aplicativos que aderem às especificações de design de materiais para uma interação muito natural com os usuários. Além disso, o aplicativo desenvolvido com um ótimo framework deve usar uma ótima plataforma para implantação, e o Netlify é exatamente isso. Com evolução constante, ótimo suporte e com uma infinidade de recursos, certamente é uma ótima plataforma para trazer aplicativos da web ou sites estáticos para as massas. Espero que este artigo forneça ajuda para começar com um novo projeto Angular, desde apenas um pensamento até a implantação.

Leitura adicional

  • Arquitetura angular
  • Componentes de materiais mais angulares
  • Mais sobre os recursos do Netlify