Primeiras aulas do curso Angular parte 2: Autenticação, Forms e lazy loading

Angular parte 2: Autenticação, Forms e lazy loading

O componente para login - O componente de login

Boas vindas à segunda parte do curso de Angular, focado em autenticação e seus desdobramentos. Trabalharemos de maneira gradativa, implementando aos poucos para entendermos como é feita a autenticação em uma Single Page Application.

Já temos dois usuários cadastrados na API, o Flávio e o Almeida. Começaremos criando uma tela de login com um formulário que permita a autenticação, a partir do qual alteraremos o estado da aplicação para controlar o login. Criaremos este componente em "app", dentro de uma pasta chamada "home", que possuirá um módulo próprio.

Em "home", além da tela de login, teremos uma tela de registro, e criaremos a subpasta "signin", que por sua vez conterá signin.component.ts e signin.component.html, que podem ser gerados pelo Angular CLI caso prefira. Ainda em "home", teremos o módulo home.module.ts, lembrando que todo componente deve pertencer a um módulo.

"sign in" significa logar, enquanto "sign up" se refere a registrar.

Vamos começar pelo componente signin.component.ts para relembrarmos: todo componente é uma classe, portanto usaremos export class SignInComponent { } e o Decorator @Component, que recebe algumas propriedades, dentre as quais o selector. Há um detalhe — como este componente é de página, e não será utilizado no template de outro, poderemos omitir o selector.

Sendo assim, definiremos apenas o templateUrl. O responsável pelo carregamento disso é o sistema de módulos do Angular. O código ficará assim:

import { Component } from '@angular/core';

@Component({
    templateUrl: './signin.component.html'
})
export class SignInComponent { }

Enquanto isso, o template HTML do componente ficará assim:

<h4 class="text-center">Login</h4>

<form class="form mt-4">

    <div class="form-group">
        <input 
            class="form-control" 
            placeholder="user name" 
            autofocus>  
    </div>

    <div class="form-group">
        <input
            type="password" 
            class="form-control" 
            placeholder="password">              
    </div>

    <button 
        type="submit" 
        class="btn btn-primary btn-block">
        login
    </button>

</form>

<p>Not a user?<a>Register now</a></p>

Trata-se de uma tela de login com título, formulário para preenchimento com algumas classes, em que cada input é um form-group do Bootstrap, com a classe form-control. Há ainda um botão de "submit", responsivo, e um parágrafo com o qual trabalharemos mais adiante, com um link para que o usuário se registre caso ainda não seja usuário.

Salvaremos tudo e criaremos um módulo em home.module.ts, que ficará assim, por ora:

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

@NgModule({
    declarations: [ SignInComponent ]
})
export class HomeModule { }

Agora, precisaremos acessar o sistema de rotas da aplicação, app.routing.module.ts, para acrescentar uma rota cujo path, se estiver em branco, direcionará ao login, equivalente a usarmos localhost:4200/, a raiz do componente. O componente a ser carregado será SignInComponent:

const routes: Routes = [
    {
        path: '',
        component: SignInComponent
    },
    {
        path: 'user/:userName',
        component: PhotoListComponent,
        resolve: {
            photos: PhotoListResolver
        }
    },
    <!-- código omitido -->
]

Será que isso basta? Vamos salvar e voltar ao navegador para verificar. Nada é exibido, então consultaremos o console e teremos a seguinte mensagem de erro:

Uncaught Error: Component SignInComponent is not part of any NgModule or the module has not been imported into your module (...)

Quer dizer que SignInComponent não faz parte do NgModule, ou o módulo não foi importado. Isto é, o módulo principal da aplicação precisará importar home.module.ts para que seja disponibilizado à aplicação. Assim, abriremos app.module.ts e, após ErrorsModule, importaremos HomeModule. Lembrando que em imports só entram módulos.

Salvaremos, voltaremos ao navegador, e as imagens voltam a ser exibidas. Ao alterarmos o endereço na barra para localhost:4200, teremos a tela de login, conforme desejado. Não há nada de especial nela, por enquanto é uma tela simples e responsiva.

O mais importante é entendermos que o home.module.ts, declara SignInComponent, que está em HomeModule. No entanto, o primeiro módulo a ser carregado na aplicação é o AppModule, chamado de root module, ou "módulo raiz", e que, ao ser carregado, carrega todos os módulos necessários.

Já que HomeModule não tinha sido importado nele, em nenhum momento este módulo foi carregado, e por consequência, o SignInComponent não é disponibilizado. Outra dúvida que pode surgir é em relação ao SignInComponent estar em declarations, mas não em exports. De que maneira o AppModule teve acesso ao componente?

É necessário incluir em exports apenas aquilo ao que se quer acesso no template de outro componente. O SignInComponent, como dito anteriormente, tem escopo de página, e portanto nem precisa de um selector. Deste modo, por não termos que usá-lo em outro componente, para o sistema de módulos, basta que ele seja carregado por meio de um deles. Apenas configuraremos o que for considerado estritamente necessário.

A partir de agora realizaremos implementações na tela de login. Não se preocupe com o seu aspecto e organização visual, espaçamento, pois ainda iremos mexer em tudo isso.

O componente para login - Validação de formulários

Antes de lidarmos com os dados e realizarmos uma requisição para a API, é necessário validarmos este formulário de escopo pequeno que implementamos. A ideia é que não aceitemos campos de username e password (usuário e senha, respectivamente) em branco. Poderíamos fazê-lo por meio do HTML5, porém esta solução não é integrada ao framework, e assim não poderemos utilizar todo o seu poder em relação à validação.

Sendo assim, utilizaremos a Model Driven Forms, cuja regra de validação ficará no componente, e não no template. Isto é, programaremos as regras de validação, e por consequência teremos type checking, autocomplete, tudo que o TypeScript tem a nos oferecer.

Para que isto seja possível, precisaremos importar um módulo do @angular/forms, o ReactiveFormsModule. Não faremos a importação diretamente em HomeModule simplesmente porque o autocomplete não seria realizado, uma vez que o módulo ainda não foi carregado. Porém, ao ser carregado, o ReactiveFormsModule disponibilizará diretivas e outros recursos para a validação de SignInComponent, que faz parte do HomeModule.

O código de home.module.ts ficará da seguinte maneira:

import { NgModule } from '@angular/core';
import { SignInComponent } from './signin/signin.component';
import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
    declarations: [ SignInComponent ],
    imports: [ ReactiveFormsModule ]
})
export class HomeModule {  }

Feito isso, em signin.component.ts criaremos uma propriedade em SignInComponent chamada loginForm, de tipo FormGroup. Notem que ela aparece pois importamos o ReactiveFormsModule. Clicaremos no ícone de lâmpada na linha com FormGroup e em "Import 'FormGroup' from module "@angular/forms"" para fazermos a importação.

Ainda teremos que fazer a programação de FormGroup, mas o mais importante é entendermos que ele precisará controlar o formulário da tela de login, loginForm. Precisaremos, portanto, fazer uma ligação entre eles, um data binding com a diretiva formGroup, por meio do uso de colchetes. No arquivo HTML, teremos:

<form [formGroup]="loginForm" class="form mt-4">

    <div class="form-group">
        <input class="form-control" placeholder="user name" autofocus>
    </div>

    <div class="form-group">
        <input type="password" class="form-control" placeholder="password">
    </div>

    <button type="submit" class="btn btn-primary btn-block">login</button>
</form>

Com isto, quem controlará o formulário é a validação do login associado à diretiva formGroup. O formulário terá que ser criado manualmente, podendo ser grande e complicado, portanto pediremos ajuda de um construtor de formulário, o tal do Form builder, a ser disponibilizado por conta da importação do ReactiveFormsModule.

Para injetarmos um artefato em uma classe, usamos constructor. Além disso, implementaremos a interface OnInit. Em signin.component.ts, teremos:

@Component({
    templateUrl: './signin.component.html'
})
export class SignInComponent implements OnInit {

    loginForm: FormGroup;

    constructor(private formBuilder: FormBuilder) { }
}

Clicaremos no ícone de lâmpada e em "Implement interface 'OnInit'" para adicionarmos a interface, algo que já fizemos anteriormente. Moveremos o código para baixo, acrescentado todo o formulário dentro de ngOnInit():

export class SignInComponent implements OnInit {

    loginForm: FormGroup;

    constructor(private formBuilder: FormBuilder) { }

    ngOnInit(): void {
        this.loginForm = this.formBuilder.group();
    }
}

Passando o mouse sobre group(), perceberemos que ele devolverá um FormGroup, que é o que queremos. Passaremos, então, um objeto JavaScript cuja primeira propriedade será userName, e a segunda será password, e um array em ambas.

ngOnInit(): void {
    this.loginForm = this.formBuilder.group({
        userName: [],
        password: []
    });
}

O mais importante a entender é que este FormGroup criado possui, para um controle, um form-control userName, e para outro, password. Precisaremos associar estes username e senha aos inputs correspondentes em signin.component.html. Ou seja, é necessário informar para onde irão os valores de cada input dentro do FormGroup.

Para isso, usaremos formControlName, uma diretiva também disponibilizada pelo ReactiveFormsModule, porém neste caso não faremos data binding, pois queremos passar uma string para ambas as informações:

<div class="form-group">
    <input formControlName="userName" class="form-control" placeholder="user name" autofocus>
</div>

<div class="form-group">
    <input formControlName="password" type="password" class="form-control" placeholder="password">
</div>

Isso significa que, deste loginForm, o userName de signin.component.html precisa coincidir com o loginForm de signin.component.ts, e o mesmo em relação à senha. Neste arquivo, incluiremos algumas informações nos arrays:

ngOnInit(): void {
    this.loginForm = this.formBuilder.group({
        userName: ['flavio'],
        password: ['123']
    });
}

O que você acha que foi salvo? Vamos voltar ao navegador, e então notaremos que os campos do formulário já são carregados com nome de usuário e senha.

O primeiro parâmetro do array é o valor padrão que estará no campo. O loginForm controla o formulário, então, da mesma maneira que este passa a data para ele, a data é passada ao formulário. Entretanto, queremos que este valor padrão seja uma string em branco.

Porém, em nenhum momento incluímos uma regra de validação, portanto, na segunda posição do array, importaremos e solicitaremos Validators, uma classe do Angular. Ao acrescentarmos required tanto no usuário quanto na senha, dizemos que estes campos são obrigatórios.

ngOnInit(): void {
    this.loginForm = this.formBuilder.group({
        userName: ['', Validators.required],
        password: ['', Validators.required]
    });
}

Existe uma série de validações prontas, podemos inclusive criar a nossa, e por aí vai. Por enquanto usaremos apenas este para entendermos seu mecanismo. Temos, agora, um loginForm que possui um formBuilder.group, com username e password obrigatórios. Feito isso, voltaremos ao navegador, testaremos digitando quaisquer caracteres nos dois campos, e pressionaremos o botão de "login".

Algo parece estranho: teoricamente, não poderíamos clicar neste botão enquanto ambos os campos não estivessem preenchidos, ou fossem inválidos, concorda? Além disso, neste momento não recebemos nenhum indicativo visual de que estes campos são obrigatórios. O Angular não tem como saber que isto deve ser feito, nem o colocará automaticamente.

De acordo com o estado do formBuilder.group, precisaremos exibir uma mensagem ao usuário. Para isso, abriremos signin.component.html e, após o input daremos um "Enter" e usaremos a tag <small>, que o Bootstrap interpreta como sendo um texto pequeno, vermelho (text-danger), embaixo do input (d-block), com uma margem de 2 (mt-2):

<div class="form-group">
    <input
            formControlName="userName"
            class="form-control"
            placeholder="user name"
            autofocus>
    <small
            class="text-danger d-block mt-2">
            User name is required!
    </small>
</div>

<div class="form-group">
    <input
            formControlName="password"
            type="password"
            class="form-control"
            placeholder="password">
    <small
            class="text-danger d-block mt-2">
            Password is required!
    </small>
</div>

Salvaremos e, no navegador, por conta dos campos vazios, teremos as duas mensagens que acabamos de implementar. No entanto, mesmo quando preenchemos os campos, as mensagens permanecem, quando deveriam deixar de ser exibidos, pois no caso não há mais nenhum erro de validação.

Já aprendemos que existe a diretiva ngIf que, se passarmos verdadeiro ou falso, exibirá, ou não, a mensagem. Dentro da tag small, então, usaremos a diretiva para acessarmos loginForm.get(). Faremos o mesmo nos dois campos.

<input
        formControlName="userName"
        class="form-control"
        placeholder="user name"
        autofocus>
<small
        *ngIf="loginForm.get('userName').errors.required"
        class="text-danger d-block mt-2">
        User name is required!
</small>

<input
        formControlName="password"
        type="password"
        class="form-control"
        placeholder="password">
<small
        *ngIf="loginForm.get('password').errors.required"
        class="text-danger d-block mt-2">
        Password is required!
</small>

Assim, se não houver nenhum erro, não existirá o array errors. Se houver, deste objeto especial que é errors, acessaremos qualquer erro incluindo o nome da validação, que no caso é required. Será que isso será o suficiente?

Ao retornarmos ao navegador, teremos um erro indicando que ngIf não é uma propriedade de small, portanto não existe. Isso acontece pois apesar de ser uma boa prática importar o CommonModule em quaisquer módulos que forem criados, não o fizemos até então. O arquivo home.module.ts, então, ficará assim:

@NgModule({
    declarations: [ SignInComponent ],
    imports: [
        CommonModule,
        ReactiveFormsModule
    ]
})

Isto fará com que as diretivas do Angular não estejam disponíveis para os componentes que fazem parte deste módulo. Salvaremos e iremos ao navegador mais uma vez, e teremos outro erro que informa que a leitura da propriedade required não pode ser feita de null.

Lembra que o objeto errors existirá se houver erro? Quando digitarmos no campo de preenchimento, atenderemos à obrigatoriedade da regra, e sendo assim o erro deixará de existir. No entanto, o que estamos tentando fazer é acessar a propriedade required para um objeto que não existe e não está definido. E isto ocasionará em erros para o ngIf. Como é que resolveremos isso?

Poderemos usar o Safe navigation operator, um ? logo após errors, para perguntá-lo se ele existe:

<input
        formControlName="userName"
        class="form-control"
        placeholder="user name"
        autofocus>
<small
        *ngIf="loginForm.get('userName').errors?.required"
        class="text-danger d-block mt-2">
        User name is required!
</small>

<input
        formControlName="password"
        type="password"
        class="form-control"
        placeholder="password">
<small
        *ngIf="loginForm.get('password').errors?.required"
        class="text-danger d-block mt-2">
        Password is required!
</small>

Salvaremos as alterações e voltaremos ao navegador. Ao digitarmos as informações correspondentes aos campos, as mensagens de validação não são exibidas, como gostaríamos. No entanto, ainda não implementamos a parte de submissão do formulário. Se algum dos campos estiver inválido, o botão de "login" não pode estar habilitado.

Quem já trabalhou com isso antes sabe que não se trata de algo simples: é necessário verificar a validação de todos os campos para então chegar a um valor que indicará se o elemento está habilitado ou não. A boa notícia é que podemos optar por um data binding para a propriedade disabled, que existe em todo elemento HTML. Em signin.component.html, teremos:

<button [disabled]="loginForm.invalid" type="submit" class="btn btn-primary btn-block">login</button>

Com isto, o próprio FormGroup é inteligente o bastante para saber que, se houver pelo menos um input com falha na navegação, o invalid terá valor verdadeiro. Salvaremos, abriremos o navegador, e veremos que o botão funciona de acordo com o que desejamos. É assim que a validação do Angular Module Driven Form é feita, e ela ficará ainda mais complexa.

O componente para login - Componentizando mensagens de validação

Vamos continuar melhorando a organização do nosso código — as mensagens para quando os campos apresentam algum erro de validação podem ser transformados em componentes, o que auxiliaria na exibição e padronização de exibição dos mesmos.

Mas... em que módulo iremos componentizar?

Acessaremos "app > shared", em que temos "components > card" e "directives". Em "components" criaremos a pasta "vmessage", dentro da qual criaremos vmessage.component.ts e vmessage.component.html. No primeiro arquivo, teremos:

import { Component } from '@angular/core';

@Component({
    selector: 'ap-vmessage',
    templateUrl: './vmessage.component.html'
})
export class VMessageComponent { }

De signin.component.html copiaremos o trecho com a tag <small> e o colaremos em vmessage.component.html, removendo a diretiva ngIf e o texto, que será uma Inbound property chamada text:

<small class="text-danger d-block mt-2">{{ text }}</small>

Retornaremos a vmessage.component.ts, lembrando que a Inbound property é a propriedade de uma classe decorada com @Input (importada do pacote core) para que um dado seja passado. Teremos:

export class VMessageComponent {

    @Input() text = '';
}

Também criaremos um módulo exclusivo para ele, clicando com o lado direito do mouse em "vmessage" no painel "Explorer", e em "New File". O arquivo será denominado vmessage.module.ts e terá o seguinte código:

import { NgModule } from '@angular/core';
import { VMessageComponent } from './vmessage.component';

@NgModule({
    declarations: [ VMessageComponent ],
    exports: [ VMessageComponent ]
})
export class VMessageModule { }

Sabemos que quem for importar o módulo precisa ter acesso ao componente, e é por isso que usamos o exports também. Como o home.module.ts quer utilizar o VMessage, é ali que devemos importá-lo:

@NgModule({
    declarations: [ SignInComponent ],
    imports: [
        CommonModule,
        ReactiveFormsModule,
        VMessageModule
    ]
})

Salvaremos todas as alterações, voltaremos ao signin.component.html e, embaixo da tag de <input> destinada ao username, acrescentaremos a tag <ap-message>. Moveremos o ngIf de <small> — que deletaremos — para <ap-vmessage>, deixando o trecho da seguinte maneira:

<input
    formControlName="userName"
    class="form-control"
    placeholder="user name"
    autofocus>
<ap-vmessage
    *ngIf="loginForm.get('userName').errors?.required"
    text="User name is required!">
</ap-vmessage>

Feito isto, copiaremos toda a tag <ap-vmessage> e a substituiremos pela <small> do campo de preenchimento da senha:

<input
    formControlName="password"
    type="password"
    class="form-control"
    placeholder="password">
<ap-vmessage
    *ngIf="loginForm.get('password').errors?.required"
    text="Password is required!">
</ap-vmessage>

É uma alteração simples que irá nos ajudar na questão de organização do código. Salvaremos e verificaremos seu funcionamento no navegador. Tudo funciona conforme esperado, temos um formulário um pouco mais organizado, e a partir de agora podemos começar a pensar na submissão destes dados.

Sobre o curso Angular parte 2: Autenticação, Forms e lazy loading

O curso Angular parte 2: Autenticação, Forms e lazy loading possui 240 minutos de vídeos, em um total de 59 atividades. Gostou? Conheça nossos outros cursos de Angular em Front-end, ou leia nossos artigos de Front-end.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Angular acessando integralmente esse e outros cursos, comece hoje!

  • 1245 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

Premium

  • 1245 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$75
à vista R$900
Matricule-se

Premium Plus

  • 1245 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$100
à vista R$1.200
Matricule-se

Max

  • 1245 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$120
à vista R$1.440
Matricule-se
Conheça os Planos para Empresas

Acesso por 1 ano

Estude 24h/dia onde e quando quiser

Novos cursos todas as semanas