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
| Aspeto | Tabela tradicional em Angular | Angular com Looker Embedded |
|---|---|---|
| Código frontend para um novo dataset | Dezenas ou centenas de linhas: service, mapeamento, componente, template | Uma linha de template, com um componente partilhado |
| Interfaces TypeScript por dataset | Pelo menos duas: DTO e modelo de vista | Nenhuma |
| Acrescentar uma coluna | Desenvolvimento, revisão e deploy de backend e frontend | Alteração ao modelo LookML, sem deploy de frontend |
| Paginação, ordenação, filtros, exportação | A implementar e testar | Incluídos |
| Segurança dos dados por utilizador | Lógica dedicada no backend | Atributos de utilizador e access filters em LookML |
| Manutenção | Cresce com o número de tabelas | Concentrada 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.