Search code examples
angularangular2-servicesrxjs5angular2-observables

Assigning Observable reference to null inside map operator does not make any effect


I am trying to create a generic DataService with hateoas implementation. There's a REST api /root which provides all the hateoas link. For example,

{
    _links : {
        login : {
            href : '/login',
            method : 'POST'
        },
        orders : {
            href : '/orders',
            method : 'GET'
        },
        orderById : {
            href : '/order/{id}',
            method : 'GET'

        }
        .......
    }
}

On application load, The DataService should make a call to /root api and store the response in an instance variable, say rootLinks. It should be available for the entire session.

Then DataService should provide a followLinkByName method which get's the href from available rootLinks and triggers a new http request.

const rootUrl: string = '/root';
const baseUrl: string = 'http://localhost:8080';

@Injectable()
export class DataService {

  private observable$: Observable<any>;
  private rootLinks: any;

  constructor(private http: Http) {
    this.ngOnInit();
  }

  ngOnInit() {
    this.getRootLinks().subscribe();
  }

  private getRootLinks() {
    if (this.rootLinks) {
      return Observable.of(this.rootLinks);
    } else if (this.observable$) {
      return this.observable$;
    } else {
      this.observable$ = this.http.get(rootUrl).map(this.extractRootLinkData);
      return this.observable$;
    }
  }

  private extractRootLinkData(response: Response) {
    this.observable$ = null;                             // LINE 1
    let data = response.json();
    this.rootLinks = data._links;
  }


  private extractData(response: Response) {
    let body = response.json();
    return body;
  }



  followLinkByName(linkName: String): Observable<any> {
    let link;
    if (this.observable$) {                              // LINE 2
      return this.observable$.map((res) => {
        link = res._links[linkName];
        // make a http request and return the response
      });
    } else {
      link = this.rootLinks[options.linkName];
      options.link = link;
      // make a http request and return the response
    }
  }

}

I have added this DataService in core module's providers array, and core module is imported to the app module.

Now there's a LoginComponent from pages module which uses this DataService to login. Though in line 1, the observable$ is assigned to null, it is available at line 2 when a call is made from LoginComponent.

Snapshots, 1. on application load it invoke /root api and once the data is available, assigns the observable to null. enter image description here

2.when trying to login, enter image description here


Solution

  • Since the this.http.get(rootUrl) call is asynchronous are you sure you're not losing this context when you're using .map(this.extractRootLinkData)?

    I think when the extractRootLinkData() method is called as a callback to map() the this context is equal to window. So you're executing statement this.observable$ = null on window which doesn't exist anyway.

    You can use an anonymous function instead:

    this.observable$ = this.http.get(rootUrl).map(response => this.extractRootLinkData(response));
    

    ... or bind the this context:

    this.observable$ = this.http.get(rootUrl).map(this.extractRootLinkData.bind(this));
    

    Also see: How to access the correct `this` context inside a callback?