Criando um modal com conteúdo dinâmico - Angular
Tags: angular
Published: 2022-06-09 12:40:59

Olá ~~eu sou o Goku~~, meu primeiro post aqui 🎉, talvez você já tenha passado por uma situação de criar um componente com conteúdo de outro, existem algumas maneiras de criar um componente de forma dinâmica, para exemplificar esse comportamento vou utilizar como exemplo a implementação de um modal que tem seu conteúdo (corpo) modificado através de outro componente, vamos para a prática:

Vamos começar implementando nosso componente de modal, onde teremos os botões de concluir, cancelar, título e corpo (dinâmico).

```ts
import {
  Component,
  ComponentFactoryResolver,
  EventEmitter,
  Input,
  OnDestroy,
  OnInit,
  Output,
  ViewChild,
  ViewContainerRef,
} from '@angular/core';
import { ComponentType } from '@angular/cdk/overlay';

@Component({
  selector: 'dynamic-modal',
  templateUrl: 'dynamic-modal.component.html',
  styleUrls: ['dynamic-modal.component.less'],
})
export class DynamicModalComponent implements OnInit, OnDestroy {
  constructor(private resolverFactory: ComponentFactoryResolver) {
  }

  @Input() title: string = '';
  @Input() body!: ComponentType<{}>;
  @Output() closeMeEvent = new EventEmitter();
  @Output() confirmEvent = new EventEmitter();

  @ViewChild('viewContainer', {read: ViewContainerRef, static: false}) viewContainer!: ViewContainerRef;

  ngOnInit(): void {
    console.log('Modal init');
  }

  closeMe() {
    this.closeMeEvent.emit();
  }
  confirm() {
    this.confirmEvent.emit();
  }

  ngOnDestroy(): void {
    console.log('Modal destroyed');
  }

  ngAfterViewInit() {
    const factory = this.resolverFactory.resolveComponentFactory(this.body as any);
    this.viewContainer.createComponent(factory);
  }
}
```

Nosso _body_ será o componente informado via _service_ que será renderizado por nossa _factory_ como está implementado no _ngAfterViewInit_.

Adicione também o HTML do componente de modal. Você sabia que os componentes do angular são desse tipo? ComponentType<T>.

```html
<div
  style="
    width: 500px;
    height: auto;
    border: 1px solid black;
    background-color: white;
    border-radius: 15px;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
  "
>
  <h1 class="title">{{ title }}</h1>
  <div #viewContainer></div>
  <div>
    <button (click)="closeMe()">Fechar</button>
    <button (click)="confirm()">Salvar</button>
  </div>
</div>
```

a div que contém **#viewContainer** será responsável por renderizar nosso conteúdo dinâmico. Para chamar o modal que acabamos de criar vamos precisar adicionar um serviço que será responsável por receber os parâmetros necessários para a construção do modal como título e conteúdo (body dinâmico). Segue abaixo a implementação do service.

```ts
import {
  ComponentFactoryResolver,
  ComponentRef,
  Injectable,
  ViewContainerRef,
} from '@angular/core';
import { Subject } from 'rxjs';
import { DynamicModalComponent } from './dynamic-modal.component';
import { ComponentType } from '@angular/cdk/overlay'; 

@Injectable({ providedIn: 'root' })
export class ModalService {
  private componentRef!: ComponentRef<DynamicModalComponent>;
  private componentSubscriber!: Subject<string>;
  constructor(private resolver: ComponentFactoryResolver) {}

  openModal(entry: ViewContainerRef, modalTitle: string, modalBody: ComponentType<{}>) {
    let factory = this.resolver.resolveComponentFactory(DynamicModalComponent);
    this.componentRef = entry.createComponent(factory);
    this.componentRef.instance.title = modalTitle;
    this.componentRef.instance.body = modalBody;
    this.componentRef.instance.closeMeEvent.subscribe(() => this.closeModal());
    this.componentRef.instance.confirmEvent.subscribe(() => this.confirm());
    this.componentSubscriber = new Subject<string>();
    return this.componentSubscriber.asObservable();
  }

  closeModal() {
    this.componentSubscriber.complete();
    this.componentRef.destroy();
  }

  confirm() {
    this.componentSubscriber.next('confirm');
    this.closeModal();
  }
}

```

esse serviço é responsável por informar os **@Inputs** do nosso **DynamicModalComponent**, note que informamos o conteúdo do modal na seguinte linha `this.componentRef.instance.body = modalBody;`, agora nós temos um serviço que cria nosso modal (DynamicModalComponent) com título e conteúdo dinâmicos, com isso só precisamos agora chamar nosso serviço e informar os conteúdos para ele, essa é a parte que nós vamos chamar no dia a dia para criar um modal. Na tela que você precisa chamar o modal adicione os seguintes códigos:

```ts
constructor(private modalService: ModalService) {}

@ViewChild('modal', { read: ViewContainerRef, static: true })
  entry!: ViewContainerRef;
  sub!: Subscription;

openModal() {
// MyComponent é o componente que será renderizado dentro do seu body
    this.sub = this.modalService
      .openModal(this.entry, 'Título do modal', MyComponent)
      .subscribe((v) => {
        // dispara quando é aberto o modal
      });
  }
```

no HTML precisamos adicionar o botão ~~obviamente~~ para chamar a função **openModal** e uma tag para nosso **ViewChild** localizar.

```html
<button
  (click)="openModal()"
  data-testid="button-login"
>
 Abrir Modal
</button>

<div #modal></div>
```

e prontinho! Aconselho **fortemente** criar um module separado para adicionar seus modais _contents_ e esse componente de modal com o serviço dentro do mesmo módulo. Crie também um modelo bacana de modal ~~não use esse layout maravilhoso do post para o projeto kkk~~ e defina como padrão para todo o seu sistema, caso um dia o modal venha a ser alterado você precisará alterar somente em um local (modal.component.html).

É isso pessoal, espero que eu tenha conseguido contribuir com o desenvolvimento de vocês, também tenho que aprender e me empenhar para escrever mais por aqui então qualquer feedback
vai ser muito construtivo, obrigado! 🍻