angular - Popular dados em um Observable com base nos dados no próprio Observable
Primeiro: sou novo em Angular, então os termos aqui podem estar errados. Espero conseguir passar a compreensão.
Estou consumindo uma API. O Endpoint A expõe Tickets; esses Tickets contêm um array (0-*) de Attachments. Esses Attachments não estão completos; no modelo de dados, eles contêm a string base64 do conteúdo do arquivo subjacente, mas, para reduzir a carga geral, os Attachments só estão completos quando buscados do Endpoint B. Mas, para buscar um Attachment do Endpoint B, eu preciso do id do Attachment, que só posso obter do Endpoint A.
O que eu preciso que aconteça é que eu busque attachment.id do Endpoint A, use esse id para buscar o Attachment apropriado do Endpoint B, e substitua os Attachments no resultado do Endpoint A pelo resultado do Endpoint B. No entanto, como os Attachments são recuperados como Observable, eu não posso simplesmente atribuir o valor.
O trecho a seguir é uma das várias tentativas de resolver isso e é capaz de obter os Attachments da API, mas eles não são atribuídos da maneira que eu queria.
ngOnInit(): void {
console.log(`getting ticket for id ${this.id}`);
this.ticket$ = this.ticketService.getTicket(this.id).pipe(
map((response:Ticket) => {
response.attachments.map((attachmentPartly:Attachment) => {
console.log(`getting attachment for id ${attachmentPartly.id}`);
let attachmentFilled = this.ticketService.getAttachment(attachmentPartly.id);
attachmentFilled.subscribe(att => console.log(`base64 content is: ${att.base64Content}`)); //for posterity: this line has been omitted in tests with no change in result
return attachmentFilled;
});
return response;
})
);
}
getTicket retorna um Observable<Ticket>. getAttachment retorna um Observable<Attachment>.
Como pode ser visto, comecei a depurar registrando no console (já que o aplicativo Angular está hospedado dentro de um sistema maior que também fornece autorização contra os endpoints, não consigo realmente depurá-lo da maneira correta...) e o valor em base64 é escrito no console, então pelo menos algumas partes do código funcionam como eu quero. Por exemplo, para um arquivo do tipo text/plain, consigo obter o valor "SGV5IHRoZXJlLCByYW5kb20gcGVyc29uIGZyb20gU3RhY2tPdmVyZmxvdw" escrito no console, o que corresponde ao valor encontrado quando executo a consulta manualmente via Postman.
Suspeito que estou tentando atribuir um Observable (attachmentFilled) ao Attachment[], mas o TS não está mostrando aviso sobre conversões de tipo ilegais. Olhando a aba Network no Chrome DevTools, não consigo ver a chamada para getAttachments, apenas as chamadas para getTickets. Os modelos de dados para Ticket e Attachment são:
export interface Ticket {
id?: string;
description?: string;
assignee?: string;
createdAt?: string;
resolvedAt?: string;
attachments?: Attachment[];
}
export interface Attachment {
name?: string; //contained in result from Endpoint A
id?: string; //contained in result from Endpoint A
relatesTo?: string;
type?: string; //contained in result from Endpoint A
sizeBytes?: string; //contained in result from Endpoint A
hash?: string;
url?: string;
base64Content?: string; //need to fetch this
}
Estou usando o seguinte modelo para criar os links de download de arquivos (grandemente inspirado pelo download de arquivo base64 do angular-file-saver usando FileSaver):
<div *ngFor="let attachment of ticket.attachments" class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<a [href]="'data:' + attachment.type + ';base64,' + attachment.base64Content | safeUrl" target="_blank" download="{{ attachment.name }}">{{ attachment.name }}</a>
<small>{{ attachment.sizeBytes | filesize }}
</div>
<small class="mb-1">{{ attachment.type }}
</div>
o que resulta no seguinte HTML (observe como apenas os dados do Endpoint A estão preenchidos):
<div _ngcontent-dmp-c28="" class="list-group-item">
<div _ngcontent-dmp-c28="" class="d-flex w-100 justify-content-between">
<a _ngcontent-dmp-c28="" target="_blank" download="test.txt" href="data:text/plain;base64,undefined">test.txt</a>
<small _ngcontent-dmp-c28="">43 B
</div>
<small _ngcontent-dmp-c28="" class="mb-1">text/plain
</div>
(Sanitização de URL, inspirada pela sanitização Base64 do Angular2 de valores de URL inseguros, parece forçar o conteúdo Base64 nulo para undefined. Antes da sanitização, não haveria nada onde agora existe undefined).
O endpoint getAttachment aceita apenas o attachment.id como entrada.
Eu tentei implementar a solução em Transformar dados em um Observable desta maneira:
ngOnInit(): void {
console.log(`getting ticket for id ${this.id}`);
this.ticket$ = this.ticketService.getTicket(this.id).pipe(
mergeMap((response: Ticket) => forkJoin([...response.attachments.map((attachment : Attachment) => this.ticketService.getAttachment(attachment.id))])));
}
Mas sem sucesso: this.ticket$ gera o erro "Tipo 'Observable<Attachment[]>' não é atribuível ao tipo 'Observable'. Tipo 'Attachment[]' não possui propriedades em comum com o tipo 'Ticket'"
Resposta #1
Eu acho que esse é um problema clássico frequentemente perguntado aqui.
ngOnInit(): void {
this.ticket$ = this.ticketService.getTicket(this.id).pipe(
switchMap((response: Ticket) => {
const filledAttechments$ = response.attachments.map(
(attachmentPartly: Attachment) => this.ticketService.getAttachment(attachmentPartly.id).pipe(
map(att => ({ ...attachmentPartly, base64Content: att.base64Content })),
),
);
return forkJoin(filledAttechments$).pipe(
map(filledAttachments => ({...response, attachments: filledAttachments})),
);
})
);
}
