First off: I'm new to Angular so the terms here might be wrong. I hope I can get the understanding through.
I'm consuming an API. Endpoint A exposes Ticket
s; these Ticket
s hold an array (0-*) of Attachment
s. Those Attachment
s are not complete; in the data model they contain the base64 string of the content of the underlying file but to reduce general load the Attachment
s are only complete when fetched from Endpoint B. But in order to fetch an Attachment
from Endpoint B I need the Attachment
's id
which I can only get from Endpoint A.
What I need to happen is that I fetch attachment.id
from Endpoint A, use that id to fetch the approriate Attachment
from Endpoint B, and replace Attachment
s in the result from Endpoint A with the result from Endpoint B. However since the Attachment
s are retreived as Observable
I can't just simply assign the value.
The following snippet is one of several attempts at solving this and is able to get the Attachment
s from the API but they aren't assigned the way I wanted.
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
returns an Observable<Ticket>
. getAttachment
returns an Observable<Attachment>
.
As can be seen I've started to debug by logging (since the angular app is hosted within a larger system that also provides authorization against the endpoints I can't really debug it the right way...) and the base64 value is written to the console, so at least some parts of the code works as I want. For instance for a text/plain type file I can get the value "SGV5IHRoZXJlLCByYW5kb20gcGVyc29uIGZyb20gU3RhY2tPdmVyZmxvdw" written to the console which matches the value found when I execute the query manually via Postman.
I suspect that I'm trying to assign an Observable
(attachmentFilled
) to the Attachment[]
but TS is not warning about illegal type conversions. Looking at the Network tab in Chrome DevTools I can't see the call to getAttachments
only the calls to getTickets
.
The data models for Ticket
and Attachment
are:
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
}
I'm using the following template to create the download file links (hugely inspired by angular-file-saver download base64 file using 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>
which results in the following html (notice how only the data from Endpoint A is populated):
<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>
(Url sanitization, inspired by Angular2 Base64 sanitizing unsafe URL value, appear to force the null base64content to undefined
. Before santitization there would be nothing where there now is undefined
).
The getAttachment
endpoint only accept the attachment.id
as an input.
I have tried to implement the solution in Transform data in an Observable this way:
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))])));
}
But to no awail: this.ticket$
throws an error "Type 'Observable<Attachment[]>' is not assignable to type 'Observable'. Type 'Attachment[]' has no properties in common with type 'Ticket'"