Preencher dados em um Observable com base nos dados do próprio Observable

Nov 28 2020

Primeiro: sou novo no Angular, então os termos aqui podem estar errados. Espero conseguir entender.

Estou consumindo uma API. O ponto final A expõe Tickets; esses Tickets contêm uma matriz (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ó são concluídos quando buscados no Endpoint B. Mas, para buscar um Attachmentno Endpoint, o BI precisa do Attachment's, idque só posso obter do Endpoint A.

O que preciso acontecer é que eu busco attachment.iddo Endpoint A, uso esse id para buscar o apropriado Attachmentdo Endpoint B e substituo Attachments no resultado do Endpoint A pelo resultado do Endpoint B. No entanto, uma vez que os Attachments são recuperados como Observableposso ' t simplesmente atribuir o valor.

O snippet 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;
    })
  );
}

getTicketretorna um Observable<Ticket>. getAttachmentretorna um Observable<Attachment>.

Como pode ser visto, comecei a depurar por registro (uma vez que o aplicativo angular está hospedado em um sistema maior que também fornece autorização contra os endpoints, não posso depurar da maneira certa ...) e o valor base64 está escrito para o console, então pelo menos algumas partes do código funcionam como eu desejo. Por exemplo, para um arquivo de texto / simples, posso obter o valor "SGV5IHRoZXJlLCByYW5kb20gcGVyc29uIGZyb20gU3RhY2tPdmVyZmxvdw" escrito no console 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á avisando sobre conversões de tipo ilegais. Olhando para a guia Rede no Chrome DevTools, não consigo ver a chamada para getAttachmentsapenas as chamadas para getTickets. Os modelos de dados para Tickete Attachmentsã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 arquivo de download (inspirado no arquivo base64 de download 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 }}</small>
    </div>
    <small class="mb-1">{{ attachment.type }}</small>
</div>

que resulta no seguinte html (observe como apenas os dados do Endpoint A sã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</small>
    </div>
    <small _ngcontent-dmp-c28="" class="mb-1">text/plain</small>
</div>

(A sanitização de url, inspirada no valor de URL inseguro de sanitização de Angular2 Base64 , parece forçar o conteúdo nulo de base64 undefined. Antes da santificação, não haveria nada onde agora está undefined).

O getAttachmentterminal aceita apenas o attachment.idcomo uma entrada.

Eu tentei implementar a solução em Transform data em um Observable desta forma:

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 dúvida: this.ticket$lança um erro "O tipo 'Observável <Anexo []>' não pode ser atribuído ao tipo 'Observável'. O tipo 'Anexo []' não tem propriedades em comum com o tipo 'Tíquete'"

Respostas

1 MoxxiManagarm Nov 28 2020 at 12:25

Acho que é um problema clássico perguntado com frequência 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})),
      );
    })
  );
}