Solucionado (ver solução)
Solucionado
(ver solução)
2
respostas

[Dúvida] [Angular] V16: Dúvida sobre carregar component dinâmicamente

Pessoal, estou iniciando um projeto no Angular (v16) e precisava de uma luz para inserir de forma dinâmica um component dentro do tabs. Estou usando Material Tabs; Um serviço para gerenciar as Tabs abertas, onde estas são inseridas dinâmicamente também. A ideia é renderizar o component no 'corpo' do tab de acordo com a escolha do usuário no menu, ou seja, com um evento click. Meu component tab esta definido da seguinte forma:

<ng-container *ngIf="openTabs.length > 0; else noTabs">
  <mat-tab-group
    class="mat-tab-group"
    (selectedTabChange)="tabChanged($event)"
    [selectedIndex]="openTabs.length - 1"
  >
    <mat-tab *ngFor="let tab of openTabs; let i = index">
      <ng-template mat-tab-label>
        {{ tab.label }}
        <button mat-icon-button (click)="closeTab(i)" title="Fechar aba">
          <mat-icon>close</mat-icon>
        </button>
      </ng-template>
    </mat-tab>
  </mat-tab-group>
  <mat-grid-list
    class="mat-grid-list"
    *ngFor="let tab of openTabs.reverse(); let i = index"
    [style.display]="tab.active ? 'block' : 'none'"
    [cols]="0"
    [rowHeight]="rowHeight"
    style="overflow-y: auto"
  >
    <mat-grid-tile>

      <app-reports-iframe-new [url]="tab.url"></app-reports-iframe-new>

    </mat-grid-tile>
  </mat-grid-list>
</ng-container>
<ng-template #noTabs>
  <app-logo-centro></app-logo-centro>
</ng-template>

Estou passando de forma estática o component ReportsIframeNew. Porém gostaria de poder passar como opção meus components de tela de cadastros, tabelas e etc...

O serviço que gerencia as tabs:

import { Injectable, Type } from '@angular/core';
import { Router } from '@angular/router';
import { SafeUrl, DomSanitizer } from '@angular/platform-browser';
import { ApiService } from './api.service';
import { Tab } from '../models/tab';

@Injectable({
  providedIn: 'root',
})
export class TabService {
  //openTabs: any[] = []; // Matriz que guarda abas abertas
  openTabs: Tab[] = [];

  constructor(
    private router: Router,
    private sanitizer: DomSanitizer,
    private apiService: ApiService
  ) {}

  addTab(tabData: { label: string; url: string }) {
    if (this.openTabs.length < 5) {
      // Const que recebera true somente quando o tamanho da matriz de tabs for 0
      // Então o valor booleano irá para o construtor da primeira tab no método push mais abaixo
      const isFirstTab = this.openTabs.length === 0;
      // Usando o método bypassSecurityTrustResourceUrl para sanitizar a URL
      // Ajuda nos ataques de injeção de código
      const sanitizedUrl: SafeUrl =
        this.sanitizer.bypassSecurityTrustResourceUrl(tabData.url);

      // Aqui usa o méto push para incluir uma nova tab na matriz de tabs
      this.openTabs.push({
        label: tabData.label,
        url: sanitizedUrl,
        active: isFirstTab,
      }); // Adicionar uma nova aba à matriz

      // Essa linha usa-se se necessário criar uma regra de roteamento
      // No app-routing esta comentado o trecho que a torna funcional
      this.router.navigate(['/relatorios-tabs']);
      console.log('TAB ATIVA: ', this.openTabs.length - 1);
    } else {
      this.apiService.showMessage('Limitado a 5 abas abertas simultaneamente!');
      this.router.navigate(['/relatorios-tabs']);
    }

  }

  getTabs() {
    return this.openTabs;
  }

  closeTab(index: number) {
    if (index >= 0 && index < this.openTabs.length) {
      this.openTabs.splice(index, 1); // Fechar a aba com base no índice
    }
  }
}

Existe também uma interface que define a Tab:

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

export interface Tab {
  label: string;
  url?: any;
  active: boolean;
  componentType?: Type<any>; // Campo para a classe de componente
}
2 respostas
solução!

Olá Thiago! Tudo ok contigo?

Uma maneira de realizar essa mudança é utilizando o conceito de Component Factory Resolver. Com o Component Factory Resolver, você pode criar um componente dinamicamente em tempo de execução e inseri-lo no tab.

Primeiro, você precisará importar o ComponentFactoryResolver no seu serviço TabService:

import { ComponentFactoryResolver, Injectable, ViewContainerRef } from '@angular/core';

Em seguida, adicione o ComponentFactoryResolver como uma dependência no construtor do TabService:

constructor(
  private router: Router,
  private sanitizer: DomSanitizer,
  private apiService: ApiService,
  private componentFactoryResolver: ComponentFactoryResolver
) {}

Agora, você pode criar um método no TabService para carregar dinamicamente um componente dentro do tab. Vou chamar esse método de loadComponent:

loadComponent(componentType: Type<any>, tabData: { label: string; url: string }, viewContainerRef: ViewContainerRef) {
  const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentType);
  const componentRef = viewContainerRef.createComponent(componentFactory);
  componentRef.instance.url = tabData.url;
}

No seu template do tab, adicione um ng-template com uma referência de variável:

<ng-template #tabContent></ng-template>

No seu componente, você precisará obter uma referência para o ViewContainerRef do ng-template. Você pode fazer isso usando a diretiva ViewChild:

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

@ViewChild('tabContent', { read: ViewContainerRef }) tabContentRef: ViewContainerRef;

Agora, você pode chamar o método loadComponent no evento de clique do botão para carregar o componente dinamicamente:

loadDynamicComponent(componentType: Type<any>, tabData: { label: string; url: string }) {
  this.tabService.loadComponent(componentType, tabData, this.tabContentRef);
}

Por exemplo, se você quiser carregar o componente ReportsIframeNew dinamicamente, você pode chamar o método loadDynamicComponent passando o tipo do componente e os dados do tab:

<mat-tab *ngFor="let tab of openTabs; let i = index">
  <ng-template mat-tab-label>
    {{ tab.label }}
    <button mat-icon-button (click)="closeTab(i)" title="Fechar aba">
      <mat-icon>close</mat-icon>
    </button>
  </ng-template>
  <ng-container *ngIf="tab.componentType === ReportsIframeNew">
    <ng-template #tabContent></ng-template>
  </ng-container>
</mat-tab>

Espero que isso te ajude a carregar componentes dinamicamente dentro dos tabs. Se tiver alguma dúvida adicional, é só me dizer!

Espero ter ajudado e bons estudos!

Excelente! Utilizei a abordagem do Component Factory Resolver! Grato :D