Füllen Sie Daten in einem Observable basierend auf Daten im Observable selbst

Nov 28 2020

Zunächst einmal: Ich bin neu bei Angular, daher könnten die Begriffe hier falsch sein. Ich hoffe, ich kann das Verständnis durchbringen.

Ich konsumiere eine API. Endpunkt A macht Tickets sichtbar ; Diese Tickets enthalten ein Array (0- *) von Attachments. Diese Attachments sind nicht vollständig; Im Datenmodell enthalten sie die base64-Zeichenfolge des Inhalts der zugrunde liegenden Datei. Um die allgemeine Auslastung zu verringern, sind die Attachments jedoch nur dann vollständig, wenn sie von Endpoint B abgerufen werden. Um jedoch eine Attachmentvon Endpoint BI abzurufen, benötigen Sie die Attachments, iddie ich nur erhalten kann vom Endpunkt A.

Was ich attachment.idtun muss, ist, dass ich von Endpunkt A abrufe, diese ID verwende, um das entsprechende Ergebnis Attachmentvon Endpunkt B abzurufen , und Attachments im Ergebnis von Endpunkt A durch das Ergebnis von Endpunkt B Attachmentersetze . Da die s jedoch so weit wie möglich abgerufen werden. Observable' t einfach nur den Wert zuweisen.

Das folgende Snippet ist einer von mehreren Versuchen, dies zu lösen, und kann die Attachments von der API abrufen, aber sie sind nicht so zugewiesen, wie ich es wollte.

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

getTicketgibt ein zurück Observable<Ticket>. getAttachmentgibt ein zurück Observable<Attachment>.

Wie zu sehen ist, habe ich mit dem Debuggen durch Protokollierung begonnen (da die eckige App in einem größeren System gehostet wird, das auch die Autorisierung für die Endpunkte bietet, kann ich sie nicht richtig debuggen ...) und der base64-Wert wird geschrieben an die Konsole, so dass zumindest einige Teile des Codes so funktionieren, wie ich es möchte. Zum Beispiel kann ich für eine Text- / Nur-Typ-Datei den Wert "SGV5IHRoZXJlLCByYW5kb20gcGVyc29uIGZyb20gU3RhY2tPdmVyZmxvdw" erhalten, der in die Konsole geschrieben wurde und dem Wert entspricht, der gefunden wurde, wenn ich die Abfrage manuell über Postman ausführe.

Ich vermute, dass ich versuche, dem ein Observable( attachmentFilled) zuzuweisen , Attachment[]aber TS warnt nicht vor illegalen Typkonvertierungen. Wenn ich in Chrome DevTools auf die Registerkarte "Netzwerk" schaue, kann ich den Anruf getAttachmentsnur für die Anrufe an nicht sehen getTickets. Die Datenmodelle für Ticketund Attachmentsind:

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
}

Ich verwende die folgende Vorlage, um die Download- Dateilinks zu erstellen (stark inspiriert von der Angle-File-Saver-Download-Base64-Datei mit 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>

Dies führt zu folgendem HTML-Code (beachten Sie, dass nur die Daten von Endpunkt A ausgefüllt werden):

<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>

(Die URL- Bereinigung, inspiriert von der Angular2 Base64-Bereinigung eines unsicheren URL-Werts , scheint den Null-Base64-Inhalt zu erzwingen undefined. Vor der Santitisierung würde es nichts geben, wo es jetzt ist. undefined)

Der getAttachmentEndpunkt akzeptiert nur die attachment.idals Eingabe.

Ich habe versucht, die Lösung in Transform data in einem Observable folgendermaßen zu implementieren :

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))])));
}

Aber ohne zu beklagen: löst this.ticket$einen Fehler aus "Der Typ 'Observable <Attachment []>' kann nicht dem Typ 'Observable' zugewiesen werden. Der Typ 'Attachment []' hat keine gemeinsamen Eigenschaften mit dem Typ 'Ticket'."

Antworten

1 MoxxiManagarm Nov 28 2020 at 12:25

Ich denke, das ist ein klassisches Problem, das hier sehr oft gestellt wird.

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})),
      );
    })
  );
}