0

I'm newbie with Angular 8. I'm creating a method within a service that allows me to return a dynamically constructed data structure.

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { AppConfig } from 'src/app/app.config';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class BibliographyParserService {

  private editionUrls = AppConfig.evtSettings.files.editionUrls || [];
  private bibliographicCitations: Array<BibliographicCitation> = [];

  constructor(
    private http: HttpClient,
  ) {
  }

  public getBibliographicCitations() {
    const parser = new DOMParser();
    this.editionUrls.forEach((path) => {
      this.http.get(path, { responseType: 'text' }).pipe(map((response: string) => {
        Array.from(parser.parseFromString(response, 'text/xml').getElementsByTagName('bibl')).forEach(citation => {
          if (citation.getElementsByTagName('author').length === 0 &&
              citation.getElementsByTagName('title').length === 0 &&
              citation.getElementsByTagName('date').length === 0) {
            const interfacedCitation: BibliographicCitation = {
              title: citation.textContent.replace(/\s+/g, ' '),
            };
            if (!this.bibliographicCitations.includes(interfacedCitation)) { this.bibliographicCitations.push(interfacedCitation); }
          } else {
            const interfacedCitation: BibliographicCitation = {
              author: citation.getElementsByTagName('author'),
              title: String(citation.getElementsByTagName('title')[0]).replace(/\s+/g, ' '),
              date: citation.getElementsByTagName('date')[0],
            };
            if (!this.bibliographicCitations.includes(interfacedCitation)) { this.bibliographicCitations.push(interfacedCitation); }
          }
        });
        this.bibliographicCitations.forEach(biblCit => {
          console.log(((biblCit.author === undefined) ? '' : biblCit.author),
                      ((biblCit.title === undefined) ? '' : biblCit.title),
                      ((biblCit.date === undefined) ? '' : biblCit.date));
        });
      }),
      );
    });
    // THIS IS RETURNED EMPTY IN THE COMPONENT WHEN ACTUALLY IT IS FULL!
    return this.bibliographicCitations;
  }
}

export interface BibliographicCitation {
  author?: HTMLCollectionOf<Element>;
  title: string;
  date?: Element;
}

In the documentation I consulted I noticed that there is no such "complex" example, in the sense that the data I want to take is inside an http call, which in turn is inside a loop! And I obviously want to return them when the cycle is completed.

If I call the method outside with console.log(this.bps.getBibliographicCitations()), it now returns an empty data structure:

[]
length: 0
__proto__: Array(0)

I would like to know if there was a way to return the data by avoiding to immediately subscribe into the service.

Memmo
  • 298
  • 3
  • 8
  • 31
  • You `http.get` is async. You should be returning and subscribing to the observable returned form this. – Liam Dec 05 '19 at 10:00
  • Does this answer your question? [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Christian Vincenzo Traina Dec 05 '19 at 10:04
  • @Liam-ReinstateMonica I agree. But, in my example, I am in a cycle. If I had been out I would have done `return this.http.get(path, { responseType: 'text' }).pipe(map((response: string) => { [...]` and then `subscribe` in the component. What I hope for, it was that Angular would still allow data to be returned after a cycle of `http` calls. – Memmo Dec 05 '19 at 10:19

1 Answers1

2

What we have to do here is return an observable stream of http calls by using normal javascript map function.

public getBibliographicCitations() {
  return this.editionUrls.map((path) =>  this.http.get(path, { responseType: 'text' 
  }));
}

Then to get the values we have to subscribe it as observables are always lazy. To subscribe we can do the following:

import { forkJoin } from 'rxjs';

forkJoin(this.getBibliographicCitations()).subscribe(console.log);

Here I am using forkJoin which will wait for all your api calls. And once everything succeeds you would be able to see the data in console.

Whatever you need to map or act on values which you get from response you should do that inside subscribe function as below

forkJoin(this.getBibliographicCitations()).subscribe((responses) => {
  // Update instance variables accordingly
  this.bibliographicCitations = //;
});

Thanks

Liam
  • 27,717
  • 28
  • 128
  • 190
Siddharth Pal
  • 1,408
  • 11
  • 23
  • Sorry but what I meant there is to map urls to http observables. forEach doesn't return anything. I have not asked to change pipe(map()). Can you please read the answer one more time – Siddharth Pal Dec 05 '19 at 10:33
  • 1
    No, my bad, I think your right, I misunderstood the OPs code. I've add the setting of `this.bibliographicCitations` if only so I can alter my vote. But this seems about right – Liam Dec 05 '19 at 10:37
  • Doing `forkJoin(this.getHttpCallsOBSStream()).subscribe((responses) => {` and in the next line `responses.forEach(response => {` and returning the updated data structure out of these blocks, it seems to work perfectly! `forkJoin` was just what I was looking for, great! PS: `this.getHttpCallsOBSStream()` return `this.editionUrls.map((path) => this.http.get(path, { responseType: 'text' }))` – Memmo Dec 06 '19 at 09:11
  • 1
    Glad it helped you. – Siddharth Pal Dec 06 '19 at 13:29