Skip to content

zherro/angular-studies

Repository files navigation

AngularStudies

Projeto para aplicação de conhecimentos

Menu

Versions

Configurações iniciais

Voltar ao topo ⇡

Animations

Voltar ao topo ⇡

Você usa animações adicionando-as a cada módulo que deseja que sejam usados. Se você deseja que eles sejam usados ​​em todo o seu aplicativo, você pode adicioná-los ao seu app.module.ts arquivo assim:

	npm install --save @angular/animations
import { BrowserModule } from  '@angular/platform-browser';
import { NgModule } from  '@angular/core';
import { AppComponent } from  './app.component';
// add
import { BrowserAnimationsModule } from  '@angular/platform-browser/animations';

@NgModule({
	declarations: [
		AppComponent,
	],
	imports: [
		BrowserModule,
		// add
		BrowserAnimationsModule,
	],
	providers: [],
	bootstrap: [AppComponent]
})

export  class  AppModule { }

Materialize

Voltar ao topo ⇡

Criado e projetado pelo Google, o Material Design é uma linguagem de design que combina os princípios clássicos do design de sucesso com inovação e tecnologia.

	// realiza instalação do materialize em seu projeto
    npm install --save ngx-materialize
    npm install --save @mdi/font

	// adiciona Jquery ao projeto se necessário
    npm install --save  jquery
		// no arquivo angular.json adicione os arquivos js e css necessários
		"styles": [
			"src/styles.css",
			"node_modules/materialize-css/dist/css/materialize.min.css",
			"node_modules/@mdi/font/css/materialdesignicons.min.css"
		],
		"scripts": [
			"node_modules/jquery/dist/jquery.min.js",
			"node_modules/materialize-css/dist/js/materialize.min.js"
		]

font-awesome

Voltar ao topo ⇡

Adicionar pacote ao projeto

$ npm install font-awesome

Em seguida adicionar ao angular.json

"styles": [
    "src/styles.css",
    "node_modules/font-awesome/css/font-awesome.css",
    ...
  ],

Módulos e Componentes

Voltar ao topo ⇡

Um componente reutilizável e acionada através do selector name, definido no próprio componente.

<app-componente-name></app-componente-name>

Criando componente

Voltar ao topo ⇡

// cria componente utilizando angular CLI
ng generate component nomedocompomente

Para tornar o componente reutilizável, incluir a declaração do componente no arquivo app.module.ts:

...
@NgModule({
	declarations: [
	AppComponent,
	// adiconar componente aqui, atentar para importação do componente
	// a partir disso o componente ja pode ser utilizado através do seu selector
],
...

Criando módulo

Voltar ao topo ⇡

Um modulo é um arquivo .ts que agrupa e disponibiliza um ou mais componentes. Exemplo de configuração do módulo photos.module.ts:

IMPORTANTE: o módulo sempre deve importar as dependendias de seus componentes. Um componente comum é o CommomModules, módulo que fornece diretivas base com ngIf, ngFor e binds do angular.

import { NgModule } from  '@angular/core';
import { PhotoComponent } from  './photo/photo.component';  // importação do componente

@NgModule({
	declarations: [ PhotoComponent ], // declaração do componete
	exports: [ PhotoComponent ] // se o componente não for exportado não estará disponivel para utlilização
})
export  class  PhotosModule {}

Para declarar o modulo e disponibilizar os componentes para toda a aplicação, realizar import do módulo no arquivo app.module.ts:

@NgModule({
	declarations: [
		AppComponent,
	],
	imports: [
		BrowserModule,
		BrowserAnimationsModule,
		PhotosModule
	],
	providers: [],
	bootstrap: [AppComponent]
})

export  class  AppModule { }

Parâmetros

Voltar ao topo ⇡

Para adicionar parâmetros em um componente, utilizar o @Input()

// no componente.ts
import {  Input } from  '@angular/core';
...
@Input() description = "";
@Input() url = "";
... 

// no component.html
<img  [src]="url"  [alt]="description"  class="responsive-img"  >

// utlização do componente 
<app-photo [url]="./../../assets/imgs/imagem.jpge" [description]="Imgagem aleatória" ></app-photo>

Subject e BehaviorSubject

Voltar ao topo ⇡

BehaviorSubject é um tipo de subject, um subject é um tipo especial de observable para que você possa assinar mensagens como qualquer outro observable. Os recursos exclusivos do BehaviorSubject são:

  • Ele precisa de um valor inicial, pois sempre deve retornar um valor na assinatura, mesmo que não tenha recebido um next()
  • Na assinatura, ele retorna o último valor do assunto. Um observável regular apenas dispara quando recebe um onnext
  • a qualquer momento, você pode recuperar o último valor do assunto em um código não observável usando o método getValue()

Rotas

Voltar ao topo ⇡

Criar arquivo app-routing.module.ts na pasta ./src/app

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';

// componente customizado
import { NotFoundComponent } from './errors/not-found/not-found.component';

const routes = [
  { path: 'url/exemplo', component:ExampleComponent }, 
  { path: '**', component: NotFoundComponent } // not found - 404
];

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    RouterModule.forRoot(routes) // importante
  ],
  exports: [
    RouterModule // importante para que o appModule obtenha as rotas
  ]
})
export class AppRoutingModule { }

no arquivo app.module.ts, realizar import do nosso componente de rotas.

  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    AppRoutingModule,  // <------

	...
	
    ErrorsModule
  ],

Page Titles

Voltar ao topo ⇡

Definindo de forma dinâmica os tiludos das pádinas da aplicação

Definir nos modulos de rota o atributo data

const routes = [ 
  ...
  { 
    path: 'p/add',
    component: PhotoFormComponent,
    canActivate: [RequiresAuthGuard],
    data: {
      title: 'Photo Upload'
    }
  },
  { 
    path: 'p/:photoId',
    component: PhotoDetailsComponent,
    data: {
      title: 'Photo Detail'
    }
  },
  { 
    path: 'not-found',
    component: NotFoundComponent,
    data: {
      title: 'Not Found'
    }
  },
  ...

No app.module.ts, primeiro móulo a ser criado quando a aplicação é iniciada, devemos definir a lógica de nomeação de páginas.

Segue algoritimo funcional:

import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { filter, map, switchMap } from 'rxjs/operators';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
  title = 'angular-studies';
  
  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private titleService: Title,
  ){}

  ngOnInit(): void {
    this.router.events
      .pipe(filter(event => event instanceof NavigationEnd))
      .pipe(map(() => this.route))
      .pipe(map(route => {
        while(route.firstChild) route = route.firstChild;
        return route;
      }))
      .pipe(switchMap(route => route.data))
      .subscribe(event => this.titleService.setTitle(event.title))
  }
}

Retrocompatibilidade com navegadores

O angular por padrão trabalha apenas com as duas ultimas versões de alguns navegadores.

Isso significa que navegadores antigos não são testados pela equipe do Angular.

Dessa forma pode ocorrer alguns problemas em navegadores antidos e um deles é relacionado a navegação.

Caso houver problemas quanto ao direcionamento de rotas da aplicação utilizar a configuração {useHash: true} no app-routing-module.ts.

Assim será adicionado um # na url, impedindo que a aplicação consulte o backend para o redireciomanto de rotas.

... 
@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    RouterModule.forRoot(routes, {useHash: true})
  ],
  exports: [
    RouterModule
  ]
})
export class AppRoutingModule { }

Parâmetros Dinâmicos

Voltar ao topo ⇡

Para adicionar paramentros as rotas, utilizar /:paranName

...
const routes = [
  { path: 'minha-url/:paran1/:paran2', component: ExampleComponent },  // representa: minha-url/qualquer/coisa
  ...
  { path: '**', component: NotFoundComponent }
];
...

Para capturar os valores passado no controler do componente utilizar o modulo ActivatedRoute.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
...
export class ExampleComponent implements OnInit {

  constructor( private route: ActivatedRoute  ) { }

  ngOnInit(): void {
    const paran1 = this.route.snapshot.params.paran1;
    const paran2 = this.route.snapshot.params.paran2;
  }
}

Proteção de Rotas (AuthGuard)

Voltar ao topo ⇡

Criar um arquivo de configuração de guarda, nesse exemplo auth.guard.ts

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import { UserService } from '../user/user.service';

// para que possa ser injetado por toda a aplicação
@Injectable({providedIn: 'root'})
export class AuthGuard implements CanActivate {
    
    constructor(
        private userSevice: UserService,
        private router: Router) {

    }

    // methodo da interface 'CanActivate'
    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        // implementação para fintro de acesso a rota
        if(this.userSevice.isLogged()){
            this.router.navigate(['user', this.userSevice.getUserName()])
            return false;
        }
        return true;
    }    
}

a utilização é definida por configuração no arquivo app-routing.modules.ts, por meio do atributo canActivate: [...], podendo ser passado mais de um guard como parametro

...
const routes = [  
  { path: '', component: SiginComponent, canActivate: [AuthGuard] },
  ...
];

@NgModule({
  ...
})
export class AppRoutingModule { }

Pages Lazy Load, Rotas filhas ( DESEMPENHO ) #IMPORTANTE

Quando o projeto é buildado, é derado apenas um arquivo main.js, esse unico arquivo contem toda a aplicação e só quando é carregado e interpretado o usuário tem a vizualização da aplicação.

Nesse cenário destaca-se a importancia do lazy load, onde é possivel devinir modulos que serão carregados apenas quando o usuário solicitar.

Para iniciar no que será lazy, criar arquivo de rotas para o modulo. Criado para os compentes home desse projeto.

...src\app\home\home.routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AuthGuard } from '../core/auth/auth.guard';
import { SiginComponent } from './sigin/sigin.component';
import { SignupComponent } from './signup/signup.component';


const routes = [ 
  {  
    path: '', 
    component: HomeComponent, 
    canActivate: [AuthGuard],
    children: [
      { path: '', component: SiginComponent },
      { path: 'signup', component: SignupComponent },
    ]
  }
];

@NgModule({
  declarations: [],
  imports: [
    // IMPORTANTE:  apenas o HomeRoutingModule deve utilizar .forRoot
    RouterModule.forChild(routes)
  ],
  exports: [
    RouterModule
  ]
})
export class HomeRoutingModule { }

IMPORTANTE: lembra sempre, que quando um modulo é lazy ele não deve ser importado pelo AppModule, para que não seja carregado ao inciar a aplicação.

  • importar o HomeRoutingModule no HomeModule
  • Remover HomeModule do AppModule

Por fim nas configurações de rota da aplicação definir o route children:

...
const routes = [ 
  {
    path: '',
    // full pathMath para garantir que será considerado especificamente o path
    pathMatch: 'full',
    redirectTo: 'home'
  },
  {
  // aqui define-se o children modulo que deve ser carregado quando a hota /home for acionada
   path: 'home',
   loadChildren: './home/home.module#HomeModule' 
  },
...

HttpClient - consumo de api

Voltar ao topo ⇡

O modulo pode ser importado no app.modules.ts, assim estará disponível em toda a aplicação.

Uma boa pratica é importar o o modulo HttpClientModule diretamente no módulo customizado e não de forma global

import { HttpClientModule } from  '@angular/common/http';
...
imports: [
	HttpClientModule,
	CommonModule
]
...

Consumindo uma API

Voltar ao topo ⇡

Exemplo de um arquivo .service.ts

import { HttpClient } from  '@angular/common/http';
import { Injectable } from  '@angular/core';  

const API = '';

@Injectable({ providedIn:  'root' })	// para que o serrviço possa ser injetado em outros componentes
export  class  ExampleService{ 

	constructor(private  http: HttpClient){}
	  
	list(){
		// para especificar o tipo de retorno com uma interface
		// subistir o Object[] por SuaIterface[]
		return this.http.get<Object[]>(API+ '/all');
	}
}

Consumindo Service API

Voltar ao topo ⇡

export  class  ExampleListComponent{
	data: any[] = [];
	
	constructor(
		private  service: ExampleService
	) { }

	all(){
		this.photoService
			.list()
			.subscribe(data =>  this.data = data);
	}
}

SimpleChanges in ngOnChange function

Voltar ao topo ⇡

SimpleChanges é um recurso Angular / Core que pode ser usado para ver as mudanças e mais alguns detalhes dos nomes das propriedades declaradas em um componente. E também precisa ser usado nome todo Angular ngOnChange para ver as mudanças de valores e fazer coisas relevantes.

Simplesmente o ngOnChange é disparado quando os valores das propriedades declaradas são alterados. Portanto, nesse método, podemos definir isso como um parâmetro para armazenar os dados. como isso:

...
ngOnChanges(changes: SimpleChanges) {
...

Um exemplo mais funcional. Quando o @Input for alterado, o ngOnChange sera acionado:

Note que foi implentanda a interface OnChanges

import { SimpleChanges } from '@angular/core';
...
export class ExampleOtherComponent implements OnChanges {

  @Input() paran: any[] = [];

  constructor() { }

  ngOnChanges(changes: SimpleChanges) {
      if(changes.paran) {
		// actions here
        ...
      }
  }

target event

Voltar ao topo ⇡

Para capturar ventos de compoentes html

Angular Events{:target="_blank"}

<input type="text"
       (input)="currentItem.name = $event.target.value"
        >

<input 
        type="text"
        (keyup)="filter = $event.target.value"
        >

Data Trasform com pipe

Voltar ao topo ⇡

Transformadores que poden ser utilizados em ng expressions

Transforming Data Using Pipes{:target="_blank"}

<p>The hero's birthday is {{ birthday | date }}</p>
...
<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }} </p>
...
{{ birthday | date | uppercase}}

Custom pipe

Voltar ao topo ⇡

Ainda é possivel definir um transformador customizado:

deve ser declarado no modulo na sessão declarations

// src/app/exponential-strength.pipe.ts

import { Pipe, PipeTransform } from '@angular/core';
/*
 * Raise the value exponentially
 * Takes an exponent argument that defaults to 1.
 * Usage:
 *   value | exponentialStrength:exponent
 * Example:
 *   {{ 2 | exponentialStrength:10 }}
 *   formats to: 1024
*/
@Pipe({name: 'exponentialStrength'})
export class ExponentialStrengthPipe implements PipeTransform {
  transform(value: number, exponent?: number): number {
    return Math.pow(value, isNaN(exponent) ? 1 : exponent);
  }
}
// src/app/power-booster.component.ts

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

@Component({
  selector: 'app-power-booster',
  template: `
    <h2>Power Booster</h2>
    <p>Super power boost: {{2 | exponentialStrength: 10}}</p>
  `
})
export class PowerBoosterComponent { }

Resolver

Voltar ao topo ⇡

Interface que as classes podem implementar para ser um provedor de dados. Uma classe de provedor de dados pode ser usada com o roteador para resolver os dados durante a navegação. A interface define um resolve()método que é chamado quando a navegação é iniciada. O roteador espera que os dados sejam resolvidos antes que a rota seja finalmente ativada. ['traduzido']

interface Resolve<T> {
  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | T
}

Implemenação

Voltar ao topo ⇡

Um exemplo de utilização, é para casos onde a página deve aguardar a consulta de um serviço

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';

...
// File example.resolve.ts

@Injectable({ providedIn: 'root' })
export class ExampleResolver implements Resolve<Observable<Object[]>>{

    constructor(private service: ExampleService) {}

    resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Object[]> {
        ...

        return ...;
    }
}
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
...
export class ExampleComponent implements OnInit {

...
  constructor(private route: ActivatedRoute) { }

  ngOnInit(): void {
	// this.route.snapshot.data...  <<--
    this.photos = this.route.snapshot.data.photos;
  }
}
...
const routes = [
  {
    path: 'my-url/:paran1', component: ExampleComponent,
    resolve: {
      photos: ExampleResolver
    }
  },
 ...
];

Comunicação entre compoentes

Voltar ao topo ⇡

A comunicação pode ser realizada através de um custom event.

Exemplo de um componente de pesquisa

import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { Subject } from 'rxjs';
import { debounceTime, filter } from 'rxjs/operators';

@Component({
  selector: 'app-search',
  templateUrl: './search.component.html',
  styleUrls: ['./search.component.css']
})
export class SearchComponent implements OnInit, OnDestroy {

  // evento customizado
  @Output() onTyping = new EventEmitter<string>();
  
  @Input() value:string ='';
  debounce: Subject<string> = new Subject<string>();

  constructor() { }

  ngOnInit() {
    this.debounce
      .pipe(debounceTime(300)) // quando parar de digitar po 300ms aciona o subscribe
      .subscribe(filter => this.onTyping.emit(filter));
  }
  
  ngOnDestroy(): void {
    this.debounce.unsubscribe();
  }
}

$event.target.value capitura o value do imput em tempo de execução

<div class="container mt-m">
    <div class="row">
      <div class="input-field input-box col s5 center-box">
        <input 
          id="input_text" 
          type="text" 
          data-length="10"
          autofocus
          (keyup)="debounce.next($event.target.value)"
          [value]="value"
          >
        <label for="input_text">Pesquisar</label>
      </div>
    </div>
  </div>

O evento customizado onTyping estará assistindo o compente e informando as transições de estado.

<!-- utlizacao do componete -->
<app-search (onTyping)="filter = $event" [value]="filter"></app-search>
...
// declaração do atributo filter no compenente que utiliza o `app-search`
filter: string = '';
...

Diretivas

Voltar ao topo ⇡

Diretivas podem ser utilizadas para definir um comportamento de um elemento da página.

Exemplo de diretiva de hover:

import { Directive, ElementRef, HostListener, Input, Renderer2 } from '@angular/core';

@Directive({
    selector: '[appDarkenOnHover]'
})
export class DarkenOnHoverDirective{

	// propriedade da diretiva
    @Input() brightness = "70%";

    // ElementRef para utilizar diretamente elementos do DOM
    constructor(
        private el: ElementRef,
        private render: Renderer2
    ) {}
    
    // HostListener para capturar acoes do usuario
    @HostListener('mouseover')
    darkenOn() {
        console.table('darkenOn');

        // Renderer2 para manimular elementos do DOM 
        this.render.setStyle(this.el.nativeElement, 'filter',  `brightness(${this.brightness})`);
    }

    @HostListener('mouseleave')
    darkenOff() {
        console.table('darkenOff');
        this.render.setStyle(this.el.nativeElement, 'filter',  'brightness(100%)');
    }
}

Utilizando a diretiva

<div class="card" appDarkenOnHover brightness="80%">

Detectando a plataforma de execução

Voltar ao topo ⇡

Ao chamar o método isPlatformBrowser sera retornando true se estivere sendo executado em um navegador.

import { Inject, Injectable, PLATFORM_ID } from '@angular/core';

// metodo para detecção de plataforma
import { isPlatformBrowser } from '@angular/common';

@Injectable({
  providedIn: 'root'
})
export class PlataformDetectorService {

  constructor(@Inject(PLATFORM_ID) private plataforID: string) { }

  isPlatformBrowser(){
    return isPlatformBrowser(this.plataforID);
  }
}

JWT decode

Voltar ao topo ⇡

Para realizar decode de um token JWT

# instalar pacote

npm install jwt-decode

Utlização

Voltar ao topo ⇡

import { Injectable } from '@angular/core';
import { BehaviorSubject, Subject } from 'rxjs';
import { TokenService } from '../toke/token.service';
import jwt_decode from 'jwt-decode';
import { User } from './user';

@Injectable({
  providedIn: 'root'
})
export class UserService {


  // sempre utilizar o BehaviorSubject para quando um serviço é carrega antes do componente
  private userSubject = new BehaviorSubject<User>(null);

  constructor(private tokeService: TokenService) {

    this.tokeService.hasToken() &&
      this.decodeAndNotfy();
  }

  setToken(token: string) {
    this.tokeService.setToken(token);
    this.decodeAndNotfy();
  }

  getUser() {
    // retorn um observable para dar subscribe
    return this.userSubject.asObservable();
  }

  private decodeAndNotfy() {
    const token = this.tokeService.getToken();

    // decode do token
    const user = jwt_decode(token) as User;
    this.userSubject.next(user);
  }
}

Angular Forms

Voltar ao topo ⇡

Para utilizar os recursos do angular forms é necessário atender alguns requisitos:

  • Importar o módulo FormsModule
  • Criar a varaivel de representação do formulário do tipo FormGroup
  • Injetar um FormBuilder para criação do formulário

arquivo .module.ts

// importação do modulo
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
...
@NgModule({
   ....
    imports: [
        ReactiveFormsModule,
        CommonModule,
        FormsModule
    ]
})

arquivo .component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
...
export class SignupComponent implements OnInit {

  // cria a variavel de representação do formulário
  signupForm: FormGroup;

  // injeta o form builder
  constructor(private formBuilder: FormBuilder) { }

  // cria o formulario com sual propriedades
  ngOnInit() {
    this.signupForm = this.formBuilder.group({
      email: [''],
      fullName: [''],
      userName: [''],
      password: [''],
    })
  }
}

arquivo .component.html

<div class="container">
    <h4 class="center">Register to embrace a new world!</h4>
    <!-- vincula a varaivel ao formulario e seus atributos -->
    <form class="form mt-s" [formGroup]="signupForm"  (submit)="submitFormMetod()">
       <input formControlName="email" id="email" type="email">
       <input formControlName="fullName" id="full-name" type="text">
       <input formControlName="userName" id="user-name" type="text">
       <input formControlName="password" id="password" type="password" >
      <button class="btn blue right">Register</button>
    </form>
</div> 

Validators

Voltar ao topo ⇡

Para facilitar, será apenas realizado o incremento do código acima.

arquivo .component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
...
export class SignupComponent implements OnInit {

  // cria a variavel de representação do formulário
  signupForm: FormGroup;

  // injeta o form builder
  constructor(private formBuilder: FormBuilder) { }

  // cria o formulario com sual propriedades
  ngOnInit() {
    this.signupForm = this.formBuilder.group({
      email: ['',
        [
            Validators.required,
            Validators.email
        ]
      ],
      fullName: ['', []],
      userName: [''
        [
          Validators.required,
          Validators.pattern(/^[a-z0-9_\-]+$/),
          Validators.minLength(2),
          Validators.maxLength(30)
        ]
      ],
      password: [''],
    })
  }
}

arquivo .component.html

Verificando se alguma validação não esta sendo atendida e apresentando mensagem equivalente.

<div class="container">
    <h4 class="center">Register to embrace a new world!</h4>
    <!-- vincula a varaivel ao formulario e seus atributos -->
    <form class="form mt-s" [formGroup]="signupForm">
      <div>
        <input formControlName="email" id="email" type="email">
        <small *ngIf="signupForm.get('email').errors?.required">Informe seu email!</small>
        <small *ngIf="signupForm.get('email').errors?.email">Email invalido</small>
      </div>
       ...
       <div>
        <input formControlName="userName" id="user-name" type="text">
         <small *ngIf="signupForm.get('userName').errors?.required">Campo obrigatorio</small>
         <small *ngIf="signupForm.get('userName').errors?.minlength">No minimo 2 caracteres</small>
        <small *ngIf="signupForm.get('userName').errors?.maxlength">No maximo 30 caracteres</small>
        <small *ngIf="signupForm.get('userName').errors?.pattern">deve contar apenas numeros e letras (a-z e 0-9)!</small>
      </div>
       ...
       <!-- desabilita o botao se o formular não estiver 100% validado -->
      <button [disabled]="signupForm.invalid" class="btn blue right">Register</button>
    </form>
</div> 

Custom validators

Voltar ao topo ⇡

Criando um validator customizado. Um validator por ser uma função apenas, ou uma função de uma classe.

arquivo src/app/shared/validators/lower-case.validator.ts

import { AbstractControl } from '@angular/forms';

export function lowerCaseValidator(control: AbstractControl) {
    if(control.value.trim() && !/^[a-z0-9_\-]+$/.test(control.value)) {
        // ATENCAO: lowerCase será o retorno do validador
        return {lowerCase: true}
    }
    return null;
} 

utilização

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

// importacao do nosso Validator
import { lowerCaseValidator } from 'src/app/shared/validators/lower-case.validator';

  export class SignupComponent implements OnInit {
  
  signupForm: FormGroup;

  ...

  ngOnInit() {
    const fn = this.userNotFoundValidatorService.checkUserNameTaken();

    this.signupForm = this.formBuilder.group({
      email: ['', 
        [
     export class SignupComponent implements OnInit {
      ngOnInit() {
        this.signupForm = this.formBuilder.group({
          ...
          userName: ['',
            [
              Validators.required,          
              Validators.minLength(2),
              Validators.maxLength(30),
              // aqui realizamos a referencia ao nosso validador customizado
              lowerCaseValidator
            ]
          ],
        password: ['']
        ...

Assim tempos que observar a necessidade de alteração da verificação de validações. Neste caso removemos a validação por pattern e adicionamos a validação customizada.

Mudando da seguinte forma:

  ...
    <div>
        <input formControlName="userName" id="user-name" type="text">
         <small *ngIf="signupForm.get('userName').errors?.required">Campo obrigatorio</small>
         <small *ngIf="signupForm.get('userName').errors?.minlength">No minimo 2 caracteres</small>
        <small *ngIf="signupForm.get('userName').errors?.maxlength">No maximo 30 caracteres</small>
        <!-- ATENCAO: note que é utilziado o retorno do validador para verificar se o mesmo foi atendido-->
        <small *ngIf="signupForm.get('userName').errors?.lowerCase">deve contar apenas numeros e letras (a-z e 0-9)!</small>
    </div>
    ...

Custom validator service

Voltar ao topo ⇡

Há momentos em que é necessário realizar uma validação que necessita o consumo de um serviço. Para esse fim pe possivel criar um Validator service.

arquivo src/app/home/signup/user-not-taken.validator.service.ts

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

const API_URL = "http://localhost:3000";

@Injectable({providedIn: 'root'})
export class SignUpService {


    constructor (private http: HttpClient) {}

    checkUserNameTaken(userName: string){
        return this.http.get(API_URL + '/user/exists/' + userName);
    }
} 

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

import { Injectable } from '@angular/core';
import { AbstractControl } from '@angular/forms';
import { debounceTime, switchMap, map, first } from 'rxjs/operators';
import { SignUpService } from './signup.service';

@Injectable({providedIn: 'root'})
export class UserNotFoundValidatorService {

    // inheção de um serviço que retorna um observable
    constructor(private signUpService: SignUpService){}

    checkUserNameTaken() {
        return (control: AbstractControl) => {
            return control
                .valueChanges
                .pipe(debounceTime(300))
                .pipe(switchMap(userName => 
                    this.signUpService.checkUserNameTaken(userName)
                ))
                .pipe(map(isTaken => isTaken ? {userNameTaken: true } : null ))
                .pipe(first());
        }
    }

} 

Utilização

Por ser um serviço e depender de uma requisição, nosso validador é asincrono e por isso não temos controle sobre o tempo de processamento das informações.

Porém, a decração de um fieldForm permite a inclusão dos seguintes paramentros na sua declarção fieldName: ['default value', [...array de validators sincronos], [... array de validator ASSINCRONOS]].

Dessa forma é possivel declarar:

    ...
    import { lowerCaseValidator } from 'src/app/shared/validators/lower-case.validator';
    import { UserNotFoundValidatorService } from './user-not-taken.validator.service';
    ...
    constructor(
        private formBuilder: FormBuilder,
        private userNotFoundValidatorService: UserNotFoundValidatorService
      ) { }
    ...
      userName: ['',
      [
        Validators.required,
        lowerCaseValidator, // o validar customizado
        Validators.minLength(2),
        Validators.maxLength(30)
      ]
      ],
      this.userNotFoundValidatorService.checkUserNameTaken() // o validador assincrono
    ],
    ... 

No html para verificar o atendimento da validação, segue-se o padrão do validador customizado

Mudando da seguinte forma:

Quando utilizamos validadores asincronos e é necessário verificar se o formulario esta valido alé de signupForm.invalid devemos também verivicar se os async validators também foram atendidos com a expressão signupForm.pending.

  ...
    <div>
        <input formControlName="userName" id="user-name" type="text">
         <small *ngIf="signupForm.get('userName').errors?.required">Campo obrigatorio</small>
         <small *ngIf="signupForm.get('userName').errors?.minlength">No minimo 2 caracteres</small>
        <small *ngIf="signupForm.get('userName').errors?.maxlength">No maximo 30 caracteres</small>
        <small *ngIf="signupForm.get('userName').errors?.lowerCase">deve contar apenas numeros e letras (a-z e 0-9)!</small>
        <!-- ATENCAO: note que é utilziado o retorno do validador para verificar se o mesmo foi atendido-->
        <small *ngIf="signupForm.get('userName').errors?.userNameTaken">Nome de usuário ja existe</small>
         <small *ngIf="signupForm.get('userName').valid" class="green-text">Nome de usuário disponivel!</small>
    </div>
    ...
    <button [disabled]="signupForm.invalid || signupForm.pending" class="btn blue right">Register</button>

Development server

Run ng serve for a dev server. Navigate to http://localhost:4200/. The app will automatically reload if you change any of the source files.

Code scaffolding

Run ng generate component component-name to generate a new component. You can also use ng generate directive|pipe|service|class|guard|interface|enum|module.

Build

Run ng build to build the project. The build artifacts will be stored in the dist/ directory. Use the --prod flag for a production build.

Running unit tests

Run ng test to execute the unit tests via Karma.

Running end-to-end tests

Run ng e2e to execute the end-to-end tests via Protractor.

Further help

To get more help on the Angular CLI use ng help or go check out the Angular CLI README.

About

Projeto Angular para exposição pratica de conhecimentos

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published