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.

3
  • You http.get is async. You should be returning and subscribing to the observable returned form this. Commented Dec 5, 2019 at 10:00
  • Does this answer your question? How do I return the response from an asynchronous call? Commented Dec 5, 2019 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. Commented Dec 5, 2019 at 10:19

1 Answer 1

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

Sign up to request clarification or add additional context in comments.

4 Comments

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
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
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' }))
Glad it helped you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.