0

I'm struggling with Angular Services and can't find out what's the issue.

I'm working on a book app (with Promises). For efficiency reasons, I'm refactoring my code with service injection. I use the service for all the http requests (there's a rest service behind), the data are correctly retrieved in the service but there's no way to get it in the component. To check if it was done correctly i've added a console.log in the service promise ( populateBookList() ). The console shows the correct list.

I also added a console.log in my component to see the list, and it's empty. Moreover, the component book list is loaded before the service one. I know there's something fishy but can't figure out where >_<

Any clue, advice, [other] would be really appreciated! Thank you guys!

This is my component :

import { Component, OnInit, Input } from '@angular/core';
import { Http } from '@angular/http';
import { Book } from '../domain/book';
import { Message } from 'primeng/primeng';
import { Author } from '../domain/author';
import { HttpService } from './service/http.service';
import { Observable } from 'rxjs/Observable';

@Component({
    selector:'lib-book',
    templateUrl:'./book.component.html',
    providers: [HttpService]
})

export class BookComponent implements OnInit{
        _httpService: HttpService;
        urlRef:String;
        books:Array<Book>;
        authorList:Array<Author>;

        public msgs:Message[]=[];

    constructor(private httpService:HttpService, private http:Http){
        this._httpService = httpService;
        this._httpService.populateBookList(this.msgs);
        this.books = this._httpService.getBooks();

    }

    ngOnInit(){        
    }

}

This is my service

import { Injectable } from "@angular/core";
import { Book } from '../../domain/book';
import { Http } from '@angular/http';
import { Author } from '../../domain/author';

import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/map';
import { Observable } from "rxjs/Observable";

@Injectable()
export class HttpService{
private urlRef:String = "http://localhost:8080/Librarian-1.0/ws/";
private bookList:Array<Book>;

    constructor(private http:Http){
        this.bookList = new Array<Book>();
    }

    populateBookList(msgs){
        this.http.get(this.urlRef+"book")
                .toPromise()
                .then(response => this.bookList = response.json() as Array<Book>)
                .then(() => console.log(this.bookList))
                .then(() => msgs.push({severity:'info', summary:'Book list',detail:'Loaded succesfully !'}))
                .catch(() => msgs.push({severity:'error', summary:'Book list',detail:'Can not load list.'}));
    }

    getBooks():Book[]{
        return this.bookList;
    }
}
raik
  • 33
  • 2
  • 13
  • 3
    You are doing an async operation but want to do the assignment synchronously. Meaning: The data from `populateBookList` haven't arrived by the time you call `getBooks`. The `http.get` inside the `populateBookList` will get the list say in like 2 seconds but you are requesting the books in `getBooks` immediately after you request `populateBookList`. – eko Apr 09 '17 at 16:04

2 Answers2

0

Change you two function to this :

 populateBookList(msgs){
       return this.http.get(this.urlRef+"book").map(response => {
            this.bookList = response.json() as Array<Book>;
            console.log(this.bookList);
            msgs.push({severity:'info', summary:'Book list',detail:'Loaded succesfully !'}
            return this.bookList;
       }).catch(() => msgs.push({severity:'error', summary:'Book list',detail:'Can not load list.'}));;
    }


    constructor(private httpService:HttpService, private http:Http){
        this._httpService = httpService;
        this._httpService.populateBookList(this.msgs).subscribe(res => {
            this.books = res;
        });
    }

And you will get your expected result.

Vivek Doshi
  • 56,649
  • 12
  • 110
  • 122
  • Just a few precisions needed, in the populateBookList(), this.bookList refers to a service variable right? With those two elements (populateBookList in the service and the constructore in the component) I still get an "undefined" when I do a console.log() once the subscribe is done :s – raik Apr 09 '17 at 16:39
  • yes, and will you please , console.log "res" from "subscribe(res => " , so I help you more. – Vivek Doshi Apr 09 '17 at 16:42
  • Will you please check mu updated answer and try again. – Vivek Doshi Apr 09 '17 at 16:43
  • I get the good response on the console.log() Response {_body: "[{"isbn":"987654321","title":"Sac d'os","author":{…se},"year":1987,"category":"fr","language":"fr"}]", status: 200, ok: true, statusText: "OK", headers: Headers…} – raik Apr 09 '17 at 16:46
  • great , just need to make it json by .json(). And you there. – Vivek Doshi Apr 09 '17 at 16:47
0

Another solution is to switch from Promises to map.

In my case, I had to get a Json for a known object. First the service! To do so, I call my RS to retrieve a JSON typed as Observable>:

getBooks():Observable<Book[]>{
        return this.http.get(this.urlRef+"book").map(this.extractData);
    }

The extractData method handles the response (I used it as a method by itself because I'm gonna reuse it later):

extractData(res: Response){
        return res.json()
    }

Secondly my component. I add the service to my providers in my decoractor:

@Component({
    selector:'lib-book',
    templateUrl:'./book.component.html',
    providers: [ HttpService ]
})

Instantiate it in the constructor :

constructor(private httpService:HttpService){}

And then subscribe (as showed by Vivek Doshi, thanks again!) to my service method, with error and success handling :

ngOnInit(){ 
     this.httpService.getBooks()
      .subscribe(
          // opération à réaliser
          data => {this.books = data}, 
          // gestion de l'erreur
          ()=> this.msgs.push({severity:'error', summary:'Book list',detail:'Can not load list.'}), 
          // gestion de la réussite
          () => this.msgs.push({severity:'info', summary:'Book list',detail:'Loaded succesfully !'})) 
    }

For later reference the subscribe works as follows : componentORservice.method().subscribe(data handling, error handling, success handling).

Thanks again!

And here's the full code:

Service :

import { Injectable } from "@angular/core";
import { Http, Response } from '@angular/http';

import { Observable } from "rxjs/Observable";

import { Author } from '../../domain/author';
import { Book } from '../../domain/book';

@Injectable()
export class HttpService{
private urlRef:String = "http://your_url";

    constructor(private http:Http){
    }

    getBooks():Observable<Book[]>{
        return this.http.get(this.urlRef+"book").map(this.extractData);
    }

    getAuthors():Observable<Author[]>{
        return this.http.get(this.urlRef+"author").map(this.extractData);
    }

    extractData(res: Response){
        return res.json()
    }
}

And the component:

import { Component, OnInit, Input } from '@angular/core';
import { Http } from '@angular/http';
import { Message } from 'primeng/primeng';

import { HttpService } from './service/http.service';
import { Observable } from 'rxjs/Observable';

import { Book } from '../domain/book';
import { Author } from '../domain/author';

@Component({
    selector:'lib-book',
    templateUrl:'./book.component.html',
    providers: [ HttpService ]
})

export class BookComponent implements OnInit{
        books:Array<Book>;
        msgs:Message[]=[];

  constructor(private httpService:HttpService){
    }

    ngOnInit(){ 
     this.httpService.getBooks()
      .subscribe(
          data => {this.books = data}, 
          ()=> this.msgs.push({severity:'error', summary:'Book list',detail:'Can not load list.'}), 
          () => this.msgs.push({severity:'info', summary:'Book list',detail:'Loaded succesfully !'})) 
    }
}
raik
  • 33
  • 2
  • 13