<link rel="stylesheet" href="/assets/fonts/inter/inter.css" />
All posts

Angular com Looker Embedded: como eliminar o boilerplate e as interfaces TypeScript das tuas tabelas de dados

Em qualquer aplicação de gestão chega o momento da tabela número vinte: mais uma interface TypeScript, mais um DTO, mais um componente com paginação, ordenação, filtros e exportação. É código útil, mas sempre igual, e cresce a cada pedido. Neste artigo mostro como, para dados de consulta e análise, o Angular com Looker Embedded permite eliminar quase todo esse boilerplate.

O problema do boilerplate tradicional

A abordagem clássica para mostrar um dataset em Angular precisa sempre das mesmas peças:

  • Interfaces e DTOs que duplicam no frontend a forma dos dados do backend, a atualizar a cada alteração.
  • Um componente de tabela dedicado, com Angular Material ou PrimeNG, colunas definidas à mão e templates para cada campo.
  • Paginação, ordenação e filtros a ligar e testar, muitas vezes também do lado do servidor.
  • Formatação de moedas, datas e estados, repetida em cada tabela.
  • Exportação para CSV ou Excel, escrita à mão ou entregue a mais uma biblioteca.

Eis como fica, em versão reduzida, uma única tabela de encomendas:

// 1. The API contract, duplicated on the frontend
export interface OrderDto {
  id: number; customer_name: string; region: string;
  total_cents: number; status: 'OPEN' | 'PAID' | 'CANCELLED'; created_at: string;
}
// 2. The view model, because the API shape is not the table shape
export interface OrderRow {
  id: number; customer: string; region: string;
  total: number; status: string; createdAt: Date;
}

@Component({ /* ... MatTableModule, MatPaginatorModule, MatSortModule ... */ })
export class OrdersTableComponent implements AfterViewInit {
  readonly columns = ['id', 'customer', 'region', 'total', 'status', 'createdAt'];
  readonly dataSource = new MatTableDataSource<OrderRow>([]);
  @ViewChild(MatPaginator) paginator!: MatPaginator;
  @ViewChild(MatSort) sort!: MatSort;

  constructor(private api: OrdersApi) {
    this.api.list().subscribe(dtos => (this.dataSource.data = dtos.map(toRow)));
  }
  ngAfterViewInit(): void {
    this.dataSource.paginator = this.paginator;
    this.dataSource.sort = this.sort;
  }
  applyFilter(value: string): void { this.dataSource.filter = value.trim().toLowerCase(); }
  exportCsv(): void { /* build the CSV by hand, escape quotes, trigger the download... */ }
}

// 3. Mapping and formatting, also by hand
const toRow = (d: OrderDto): OrderRow => ({
  id: d.id, customer: d.customer_name, region: d.region,
  total: d.total_cents / 100, status: d.status, createdAt: new Date(d.created_at),
});
// ...plus the HTML template with one <ng-container matColumnDef> per column

E isto é só o frontend: cada nova coluna obriga a mudar o DTO, o mapeamento, o template, os testes, e a publicar backend e frontend em conjunto.

A viragem: Angular com Looker Embedded

Com o Looker, o contrato de dados e a apresentação vivem no modelo LookML: dimensões, medidas, formatos, etiquetas e relações são definidos uma única vez, do lado da análise. O frontend deixa de precisar de conhecer a forma dos dados: só tem de mostrar um conteúdo já pronto, que o Looker incorpora num iframe seguro.

Inicializa-se o SDK uma vez para a aplicação, indicando o endpoint do nosso backend que assina os URLs de embed:

// app.config.ts — once for the whole application
import { LookerEmbedSDK } from '@looker/embed-sdk';

export const appConfig: ApplicationConfig = {
  providers: [
    // The SDK asks our backend for a signed embed URL for each content item
    provideAppInitializer(() => LookerEmbedSDK.init('analytics.example.com', '/api/looker/embed-auth')),
  ],
};

Depois basta um componente genérico, <app-looker-table>, que recebe apenas o ID do conteúdo e não sabe nada de colunas nem de tipos:

@Component({
  selector: 'app-looker-table',
  template: `<div #host class="looker-host"></div>`,
  styles: `.looker-host, .looker-host iframe { width: 100%; height: 100%; border: 0; }`,
})
export class LookerTableComponent {
  /** Look ID defined in Looker: the component knows nothing about the data. */
  readonly lookId = input.required<string>();
  private readonly host = viewChild.required<ElementRef<HTMLDivElement>>('host');

  constructor() {
    afterNextRender(() => {
      LookerEmbedSDK.createLookWithId(this.lookId())
        .appendTo(this.host().nativeElement)
        .build()
        .connect()
        .catch(err => console.error('Looker embed failed', err));
    });
  }
}

// Usage: the same component for any dataset
// <app-looker-table lookId="42" />   orders
// <app-looker-table lookId="57" />   invoices

Repara no que falta: nenhuma interface, nenhum DTO, nenhuma definição de coluna, nenhuma função de exportação. O mesmo componente mostra encomendas, faturas ou qualquer outro dataset.

As vantagens para a equipa

Novas colunas sem deploy do frontend

Acrescentar uma coluna, mudar um formato ou uma etiqueta é uma alteração ao modelo LookML ou ao Look: assim que é guardada, fica visível na aplicação. O frontend não precisa de ser recompilado nem publicado.

Exportação, drill-down e paginação incluídos

Downloads em CSV, Excel e PDF, ordenação, paginação e drill-down sobre os valores são funcionalidades do Looker, já testadas e coerentes em todas as tabelas, e ativam-se com as permissões do utilizador.

Segurança ao nível da linha

Com o signed embed, o nosso backend gera o URL assinado com os atributos do utilizador autenticado. No modelo LookML, um access_filter usa esses atributos para filtrar todas as queries: um utilizador não consegue ver as linhas de outra região, nem manipulando o URL.

// NestJS: signs the embed URL with the logged-in user's attributes
@Get('looker/embed-auth')
async embedAuth(@Query('src') src: string, @CurrentUser() user: User) {
  const embed = await this.looker.ok(this.looker.create_sso_embed_url({
    target_url: `https://analytics.example.com${src}`,
    session_length: 3600,
    external_user_id: String(user.id),
    first_name: user.firstName,
    last_name: user.lastName,
    permissions: ['access_data', 'see_looks', 'download_without_limit'],
    models: ['sales'],
    user_attributes: { region: user.region },   // drives row-level security
  }));
  return { url: embed.url };
}
# LookML: every query on this explore is filtered by the user's region
explore: orders {
  access_filter: {
    field: customers.region
    user_attribute: region
  }
}

A comparação

AspetoTabela tradicional em AngularAngular com Looker Embedded
Código frontend para um novo datasetDezenas ou centenas de linhas: service, mapeamento, componente, templateUma linha de template, com um componente partilhado
Interfaces TypeScript por datasetPelo menos duas: DTO e modelo de vistaNenhuma
Acrescentar uma colunaDesenvolvimento, revisão e deploy de backend e frontendAlteração ao modelo LookML, sem deploy de frontend
Paginação, ordenação, filtros, exportaçãoA implementar e testarIncluídos
Segurança dos dados por utilizadorLógica dedicada no backendAtributos de utilizador e access filters em LookML
ManutençãoCresce com o número de tabelasConcentrada no modelo de dados

Quando não usar

  • Tabelas editáveis: inserção, edição em linha e ações sobre as linhas continuam a ser trabalho da aplicação.
  • Interfaces muito personalizadas: o conteúdo vive num iframe, por isso o estilo adapta-se com os temas do Looker e não com o CSS da aplicação.
  • Custo e infraestrutura: o Looker é uma plataforma licenciada com um modelo de dados a manter; compensa quando os datasets são muitos e mudam com frequência.
  • A lógica da aplicação continua a precisar dos seus tipos: as interfaces desaparecem nas tabelas de consulta, não nos formulários nem nas regras de negócio.

Conclusões

  • Time-to-market: um novo relatório passa a ser uma linha de template, não um sprint.
  • Manutenibilidade: a codebase Angular deixa de crescer a cada dataset; a complexidade fica no modelo de dados, onde deve estar.
  • Segurança: as regras de visibilidade dos dados ficam centralizadas e aplicadas a todas as queries.
  • Divisão de papéis: os programadores frontend concentram-se na experiência da aplicação, quem trabalha com dados concentra-se no modelo.

Para dados de consulta e análise, o Angular com Looker Embedded desloca o trabalho do código repetitivo para o modelo de dados. Se quiseres aprofundar a organização do resto da aplicação, escrevi sobre arquitetura Angular e sobre design systems com componentes reutilizáveis; se estás a avaliar uma integração semelhante, escreve-me.

💬 Notas dos leitores

0 notas

Escreva uma nota

Partilhe a sua opinião, uma sugestão ou um elogio

Notas recentes

Ainda não há notas. Seja o primeiro a comentar!