Executando verificação de segurança...
-1

[Angular] Não perca tempo! Crie modais com @angular/cdk @ViewChild

E aí, pessoal! Já pensou em criar sua própria modal para suas aplicações web sem se preocupar com bibliotecas de terceiros? Com o @angular/cdk, podemos tornar essa tarefa muito mais fácil.

O @angular/cdk contém vários componentes prontos para uso, como o Overlay e o Portal, que ajudam muito na criação de modais personalizadas.

Neste artigo, vou te mostrar como criar uma modal personalizada usando o @ViewChild e @ViewChild. Então, pegue seu café e vamos ao código 👨‍💻

Pré-requisitos:

Para acompanhar este tutorial, você precisará de um projeto Angular e ter um breve conhecimento sobres os conceitos do Angular.

Atenção! Este tutorial foi criado usando Angular 15, mas com um pouco de esforço, é possível implementá-lo em versões anteriores do Angular.

É Hora de implementar

Para iniciar, é necessário instalar o pacote @angular/cdk através do comando npm install @angular/cdk ou utilizar a opção ng add @angular/cdk fornecida pelo @angular/cli. É importante também incluir os estilos disponíveis do @angular/cdk no seu arquivo de CSS global.

@import "@angular/cdk/overlay-prebuilt.css";

Para criar o nosso componente modal utilizando o @angular/cli, execute o seguinte comando no terminal dentro do diretório do seu projeto:

ng g c modal

após gerar nosso component via @angular/cli devemos ter algo assim:

import { Component, OnInit } from "@angular/core";

@Component({
  selector: "app-modal",
  templateUrl: "./modal.component.html",
  styleUrls: ["./modal.component.css"],
  standalone: true,
})
export class ModalComponent implements OnInit {
  constructor() {}

  ngOnInit() {}
}

Ótimo! Vamos começar criando uma variável chamada MODAL_DEFAULT_OPTIONS com alguns valores padrão. Nela, definimos o positionStrategy como GlobalPositionStrategy, o que fará com que o Overlay trabalhe com o posicionamento relativo à janela do navegador. Em seguida, chamamos os métodos centerHorizontally() e centerVertically() para alinhar nossa modal ao centro da tela.

Também definimos que a modal terá um fundo hasBackdrop e adicionamos uma class personalizada chamada modal-panel para nosso painel. Por fim, definimos uma largura mínima para nossa modal de 500px.

export const MODAL_DEFAULT_OPTIONS: OverlayConfig = {
  positionStrategy: new GlobalPositionStrategy()
    .centerHorizontally()
    .centerVertically(),
  hasBackdrop: true,
  panelClass: "modal-panel",
  minWidth: "500px",
};

O próximo passo é criar um ng-template em nosso arquivo HTML e incluir algumas tags HTML para ajudar a estruturar nossa modal:

Criamos um ng-template e demos a ele um nome de referência #modalTemplate.
Criamos uma div para agrupar nossos elementos.
Adicionamos um ng-content com o select [modal--content].

<ng-template #modalTemplate>
  <div class="modal">
    <ng-content select="[modal--content]"></ng-content>
  </div>
</ng-template>

Vamos criar um ng-template para que o conteúdo seja chamado apenas quando criarmos nossa modal utilizando o TemplatePortal.

O TemplatePortal é uma classe do @angular/cdk/portal que nos permite inserir conteúdo em um componente ou elemento de destino.

Agora que já temos o nosso template definido no HTML, vamos criar uma variável e utilizar o decorator @ViewChild, passando o nome que demos ao nosso template no HTML. Além disso, vamos criar uma variável chamada overlayRef para controlar a exibição da nossa modal.

 @ViewChild("modalTemplate") modalTemplate: TemplateRef<any>;

 private overlayRef: OverlayRef;

no constructor do nosso component vamos injetar o viewContainerRef e o overlay

constructor(
    private _viewContainerRef: ViewContainerRef,
    private overlay: Overlay
  ) {}

criamores agora o metodo que ira abrir a modal, vamos chamar ele de open()

Primeiramente, o método open() cria uma overlayRef utilizando a função create() do overlay, que recebe como parâmetro um objeto contendo as opções para a modal. Essas opções foram definidas através do MODAL_DEFAULT_OPTIONS.

em seguida, é criado um containerModal utilizando o TemplatePortal, que é uma classe que permite criar um portal para o conteúdo de um ng-template. O containerModal é criado a partir do modalTemplate, que é definido em um ng-template no arquivo HTML. O TemplatePortal também recebe como parâmetro o viewContainerRef, que é uma referência para o container da view do Angular.

por fim, a overlayRef é anexada ao containerModal utilizando a função attach(), que recebe como parâmetro o containerModal. A função open() retorna a overlayRef, que pode ser utilizada para fechar a modal posteriormente.

public open() {
    this.overlayRef = this.overlay.create({
      ...MODAL_DEFAULT_OPTIONS,
    });

    const containerModal = new TemplatePortal(
      this.modalTemplate,
      this._viewContainerRef
    );

    this.overlayRef.attach(containerModal);

    return this.overlayRef;
}

Agora método close() ele é responsável por fechar a modal. Ele simplesmente chama o método dispose() no overlayRef, o que faz com que a modal seja removida da tela

public close() {
    this.overlayRef.dispose();
}

agora em nosso arquivo de scss global vamos incluir um estilo para nossa modal

.modal-panel {
  background-color: white;
  border-radius: 8px;
  padding: 24px;
}

Otimo! finalizamos a implementação da nossa modal, agora vamos utilizar.

Para utilizar o componente ModalComponent, podemos criar um botão que chama o método open() e um outro botão dentro da modal que chama o método close(). Podemos utilizar a referência do componente utilizando o decorator @ViewChild.

import "zone.js/dist/zone";
import { Component, ViewChild } from "@angular/core";
import { CommonModule } from "@angular/common";
import { bootstrapApplication } from "@angular/platform-browser";
import { ModalComponent } from "./modal/modal.component";

@Component({
  selector: "my-app",
  standalone: true,
  imports: [CommonModule, ModalComponent],
  template: `
    <button (click)="modalOpen()">Abrir modal</button>

    <app-modal #modal>
      <div modal--content>
        <p>modal</p>
        <button (click)="modalClose()">Fechar</button>
      </div>
    </app-modal>
  `,
})
export class App {
  @ViewChild("modal") modal: ModalComponent;

  name = "Angular";

  modalOpen() {
    this.modal.open();
  }

  modalClose() {
    this.modal.close();
  }
}

bootstrapApplication(App);

ou

import "zone.js/dist/zone";
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
import { bootstrapApplication } from "@angular/platform-browser";
import { ModalComponent } from "./modal/modal.component";

@Component({
  selector: "my-app",
  standalone: true,
  imports: [CommonModule, ModalComponent],
  template: `
    <button (click)="modal.open()">Abrir modal</button>

    <app-modal #modal>
      <div modal--content>
        <p>modal</p>
        <button (click)="modal.close()">Fechar modal</button>
      </div>
    </app-modal>
  `,
})
export class App {
  name = "Angular";
}

bootstrapApplication(App);

🥳 Parabéns! Agora você tem em mãos uma modal reutilizável que pode ser implementada em seus projetos ou até mesmo em uma biblioteca de componentes (design system).
Lembre-se de considerar as necessidades do seu projeto e manter uma documentação clara e atualizada para facilitar o uso por outros membros da equipe.

Além disso, você pode acessar o repositório no Github e o exemplo no Stackblitz para consultar o código e ter mais informações sobre a implementação da modal.

🌐 repository: https://github.com/DrewOliv/angular-cdk-component-modal-example
💻 stackblitz: https://stackblitz.com/edit/angular-qhsjku?file=README.md

Carregando publicação patrocinada...
1

Interessante, porém é melhor utilizar o @angular/materialque também é mantido pelo google. Utilize o Dialog:

constructor(
    private dialog: MatDialog;
) {}

public openDeleteDialog(id: number, name: string){
    this.loading = true;
    const dialogRef = this.dialog.open(ONomeDoDialogoComponente, {
        width: '300px',
        data: `deseja mesmo apagar ${name}?`
    });

    dialogRef.afterClosed().subscribe({
        next: (res) => {
            if(res) {
                this.delete(id)
            }else{
                this.loading = false;
            }
        }
    });
}

private delete(id: number) {
    // Método delete aqui
    this.loading = false;
}

Mais fácil e prático.

Documentação em:
https://material.angular.io/components/dialog/overview