1

The potential 'duplicate' questions do not answer my question - I potentially think my use of Observables is different than the examples and as I point out, I attempted using share() unsuccessfully.

I have a service which looks like that:

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Card } from '../../card';
import { Observable } from 'rxjs/Rx';

@Injectable()
export class CardService {
    private privateUrl = 'http://localhost:3335/cardname/';
    constructor (private http: Http) {}

    public getDetailedInfo(card): Observable<Card> {
        return this.http.get(`${this.privateUrl}${card.name}`)
            .map(this.extractData);
    }

    private extractData(res: Response) {
        let body = res.json();
        return new Card(body.data.name, body.data.card_type, body.data.text);
    }
}

And a component that uses it that looks like :

import { Component, OnInit } from '@angular/core';
import { Card } from '../../card';
import { CardService } from '../../services/card/card.service';
import { CARDNAMES } from '../../cards';

@Component({
    selector: 'main',
    styles: [require('./main.component.scss').toString()],
    template: require('./main.component.html'),
    providers: [CardService]
})

export class MainComponent implements OnInit {
    mode = 'Observable';
    title = 'YuGiOh Deck Browser';
    cardnames = CARDNAMES;
    card: Card;
    selectedCard: Card;
    dataStore: Array<any> = [];

    constructor(private cardService: CardService) { }

    ngOnInit() {
        for (let prop in this.cardnames) {
            this.getDetailedInfo(this.cardnames[prop]);
        }
    }

    onSelect(card: Card): void {
        this.getDetailedInfo(card);
    }

    getDetailedInfo(card) {
        this.cardService.getDetailedInfo(card)
            .subscribe((card: Card) => {
                this.card = this.selectedCard = card;
                this.dataStore.push(card);
            });
    }
}

My template is:

<div *ngIf="card">
    <h2>{{card.name}}</h2>
    <div class="card-type">{{card?.card_type}}</div>
    <div class="card-description">{{card?.text}}</div>
</div>

and my Card looks like that:

export class Card {
    response:string;
    constructor(
        public name: string,
        public card_type: string,
        public text: string
    ) { }
}

I tried using share(), cache(), publishReplay(1).refCount() after reading this and that but nothing seems stop that HTTP Get request to be called when the click handler is called.

Am I misunderstanding what these methods actually do?

I also made an attempt, following this first answer in this question to save my data in an array of objects in memory (code sample following) which partially works, but fails when I click and trigger the onSelect event.

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Card } from '../../card';
import { Observable } from 'rxjs/Rx';

@Injectable()
export class CardService {
    private dataStore: Array<any> = [];
    private observable: Observable<Card>;
    private privateUrl = 'http://localhost:3335/cardname/';
    constructor (private http: Http) {}

    public getDetailedInfo(card): Observable<any> {
        if (this.dataStore.length > 0) {
            this.dataStore.forEach((value, index) => {
                return new Card(
                    this.dataStore[index].data.name,
                    this.dataStore[index].data.card_type,
                    this.dataStore[index].data.text
                )
            });
        } else if (this.observable) {
            return this.observable;
        } else {
            return this.http.get(`${this.privateUrl}${card.name}`)
                .map(
                    (res) => {
                        let body = res.json();
                        this.dataStore.push(body);
                        return new Card(body.data.name, body.data.card_type, body.data.text);
                    }
                );
        }
    }
}

    VM99537:93 EXCEPTION: Error in ./MainComponent class MainComponent - inline template:4:8

2016-09-11 21:04:03.241 zone.js?c625:269 Uncaught EXCEPTION: Error in ./MainComponent class MainComponent - inline template:4:8

ORIGINAL EXCEPTION: TypeError: Cannot read property 'subscribe' of undefined

ORIGINAL STACKTRACE:
TypeError: Cannot read property 'subscribe' of undefined
    at MainComponent.getDetailedInfo (eval at <anonymous> (http://localhost:5000/bundle.js:965:2), <anonymous>:32:13)
    at MainComponent.onSelect (eval at <anonymous> (http://localhost:5000/bundle.js:965:2), <anonymous>:27:14)
    at DebugAppView._View_MainComponent1._handle_click_0_0 (MainComponent.ngfactory.js:172:35)
    at eval (eval at <anonymous> (http://localhost:5000/common.js:1731:2), <anonymous>:381:24)
    at eval (eval at <anonymous> (http://localhost:5000/common.js:2322:2), <anonymous>:255:36)
    at eval (eval at <anonymous> (http://localhost:5000/common.js:2364:2), <anonymous>:27:111)
    at ZoneDelegate.invoke (eval at <anonymous> (http://localhost:5000/vendor.js:472:2), <anonymous>:332:29)
    at Object.onInvoke (eval at <anonymous> (http://localhost:5000/common.js:1551:2), <anonymous>:53:41)
    at ZoneDelegate.invoke (eval at <anonymous> (http://localhost:5000/vendor.js:472:2), <anonymous>:331:35)
    at Zone.runGuarded (eval at <anonymous> (http://localhost:5000/vendor.js:472:2), <anonymous>:239:48)
Community
  • 1
  • 1
George Katsanos
  • 13,524
  • 16
  • 62
  • 98
  • 1
    http://stackoverflow.com/questions/36271899/what-is-the-correct-way-to-share-the-result-of-an-angular-2-http-network-call-in – cartant Sep 11 '16 at 15:24
  • Can anyone explain: George's code never calls `subscribe()`, so I don't understand why the Observable chain (the HTTP request) even gets triggered – BeetleJuice Sep 11 '16 at 15:43
  • Possible duplicate of [caching results with angular2 http service](http://stackoverflow.com/questions/34104277/caching-results-with-angular2-http-service) – Tiberiu Popescu Sep 11 '16 at 17:46
  • @cartant I've obviously read this question and have made attempts to use share() unsuccesfully - therefore this is no duplicate as my code is quite different than the questions. – George Katsanos Sep 11 '16 at 18:48
  • @BeetleJuice interesting.. I just placed a debugger in the callback of subscribe and I can safely say it's called.. – George Katsanos Sep 11 '16 at 18:55

1 Answers1

0

Your data store is a good idea, but I see a couple of problems with your implementation. The biggest is that when the component calls Service.getDetaileInfo, it expects an Observable<Card> but this fails in two instance from your code:

//this returns a Card instead of an Observable<Card>
if (this.dataStore.length > 0)... return New Card

//this never seems to be true since you never assign anything to this.observable
else if (this.observable) return this.observable

My service would look something like:

//object that holds cards already fetched from server
private cache = {};
private url = '...';

/** takes Card.name and returns Observable that emits detailed info
    from the cache if possible, from the server if not */
public getDetailedInfo(name):Observable<Card> {

   // if a Card by that name is in the cache, return it as an Observable
   if (this.cache[name]) return Observable.of(this.cache[name]);

   //call the server and convert results to JS object and access .data
   return this.http.get('...').map( res => res.json().data)

               // transform object to a new Card
              .map(data => new Card(data.name, data.card_type, data.text))

              // store result in the cache, under the card's name
              .do(card => this.cache[card.name] = card)
}

In the component, you can just call Service.getDetailedInfo() when you need the details. The component doesn't need its own datastore.

BeetleJuice
  • 39,516
  • 19
  • 105
  • 165
  • `this.cache.name` : cache doesn't have a name property up there.. (compiler is complaining) in general somethings wrong with cache = {} – George Katsanos Sep 12 '16 at 20:03
  • this code doesn't run :( too many errors during compilation from TS – George Katsanos Sep 12 '16 at 20:05
  • `(18,20): error TS2322: Type 'Observable' is not assignable to type 'Observable'.` – George Katsanos Sep 12 '16 at 20:06
  • what is lowercase card ? 'card' ? for example `.do(card => this.cache[card.name] = card)` where do you get card from? – George Katsanos Sep 12 '16 at 20:12
  • The `.do()` is the next callback in an observable stream, so its input (I named it `card`, but I could have used `foo`) is the output of the previous callback. So `card` is the `new Card` that `.map()` just above it returned – BeetleJuice Sep 12 '16 at 21:43
  • @GeorgeKatsanos In response to *"this.cache.name: cache doesn't have a name property"*: you make a good point. I have fixed the `if(this.cache.name)...` line above to use `if(this.cache[name])...` instead. the idea is to check whether there is a property whose name matches the card's name. My mistake was to look for a property named `name`. – BeetleJuice Sep 12 '16 at 21:49
  • `.do(card => this.cache[card.name] = card)` : `error TS2339: Property 'name' does not exist on type 'void'`. – George Katsanos Sep 18 '16 at 16:11
  • alright, I got it to work using your code :) can we go into chat mode for one - two quick questions ? :) – George Katsanos Sep 18 '16 at 18:19
  • @GeorgeKatsanos Assuming you used the code in my answer, the argument `card` in `.do()` is the `new Card()` that the previous function returned: This is something you have control over, so if it's of type `void`, something may be wrong with either the `Card` constructor, or the data the server returned and that is used to call `new Card()` – BeetleJuice Sep 18 '16 at 18:20
  • @GeorgeKatsanos sorry I'm in the middle of something. – BeetleJuice Sep 18 '16 at 18:20