Remplir les données dans un observable en fonction des données de l'observable lui-même

Nov 28 2020

Tout d'abord: je suis nouveau sur Angular, donc les termes ici peuvent être faux. J'espère pouvoir faire passer la compréhension.

Je consomme une API. Le point final A expose Tickets; ces Tickets contiennent un tableau (0- *) de Attachments. Ces questions Attachmentne sont pas complètes; dans le modèle de données, ils contiennent la chaîne base64 du contenu du fichier sous-jacent, mais pour réduire la charge générale, les Attachments ne sont complets que lorsqu'ils sont extraits d'Endpoint B.Mais pour récupérer un Attachmentdepuis Endpoint BI, j'ai besoin Attachmentde ceux idque je ne peux obtenir à partir du point final A.

Ce que je dois arriver, c'est que je récupère à attachment.idpartir du point final A, que j'utilise cet identifiant pour récupérer l'approprié Attachmentdu point final B et que je remplace Attachments dans le résultat du point final A par le résultat du point final B. Cependant, puisque les Attachments sont récupérés comme Observableje peux ' t attribuez simplement la valeur.

L'extrait de code suivant est l'une des nombreuses tentatives pour résoudre ce problème et est capable d'obtenir les Attachments de l'API mais ils ne sont pas attribués comme je le souhaitais.

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

getTicketrenvoie un Observable<Ticket>. getAttachmentrenvoie un Observable<Attachment>.

Comme on peut le voir, j'ai commencé à déboguer en journalisant (puisque l'application angulaire est hébergée dans un système plus grand qui fournit également une autorisation contre les points de terminaison, je ne peux pas vraiment la déboguer de la bonne manière ...) et la valeur base64 est écrite à la console, donc au moins certaines parties du code fonctionnent comme je le souhaite. Par exemple, pour un fichier de type texte / brut, je peux obtenir la valeur "SGV5IHRoZXJlLCByYW5kb20gcGVyc29uIGZyb20gU3RhY2tPdmVyZmxvdw" écrite sur la console qui correspond à la valeur trouvée lorsque j'exécute la requête manuellement via Postman.

Je soupçonne que j'essaie d'attribuer un Observable( attachmentFilled) au Attachment[]mais TS ne met pas en garde contre les conversions de type illégales. En regardant l'onglet Réseau dans Chrome DevTools, je ne vois pas l'appel getAttachmentsuniquement pour les appels à getTickets. Les modèles de données pour Ticketet Attachmentsont:

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
}

J'utilise le modèle suivant pour créer les liens de fichier de téléchargement (largement inspiré du fichier base64 de téléchargement angular-file-saver à l'aide de 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>

ce qui donne le code HTML suivant (notez comment seules les données du point de terminaison A sont remplies):

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

(La désinfection d'URL, inspirée de la valeur d'URL non sécurisée de nettoyage Angular2 Base64 , semble forcer le contenu base64 nul à undefined. Avant la santitisation, il n'y aurait rien là où il y en a maintenant undefined).

Le getAttachmentpoint de terminaison accepte uniquement le attachment.idcomme entrée.

J'ai essayé d'implémenter la solution dans Transformer les données de cette manière:

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

Mais pas de problème: this.ticket$renvoie une erreur "Le type 'Observable <Attachment []>' n'est pas assignable au type 'Observable'. Le type 'Attachment []' n'a pas de propriétés en commun avec le type 'Ticket'"

Réponses

1 MoxxiManagarm Nov 28 2020 at 12:25

Je pense que c'est un problème classique posé très souvent ici.

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