82

I have a component that gets the data from a service via http, the problem is that I don't want to hit the API backend every time I show this component. I want my service to check if the data is in memory, if it is, return an observable with the array in memory, and if not, make the http request.

My component

import { Component, OnInit } from 'angular2/core';
import { Router } from 'angular2/router';

import { Contact } from './contact';
import { ContactService } from './contact.service';

@Component({
    selector: 'contacts',
    templateUrl: 'app/contacts/contacts.component.html'
})
export class ContactsComponent implements OnInit {

    contacts: Contact[];
    errorMessage: string;

    constructor(
        private router: Router,
        private contactService: ContactService) { }

    ngOnInit() {
        this.getContacts();
    }

    getContacts() {
        this.contactService.getContacts()
            .subscribe(
                contacts => this.contacts = contacts,
                error => this.errorMessage = <any>error
            );
    }
}

My service

import { Injectable } from 'angular2/core';
import { Http, Response, Headers, RequestOptions } from 'angular2/http';
import { Contact } from './contact';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class ContactService {

    private contacts: Array<Contact> = null;

    constructor(private http: Http) {

    }

    getContacts() {
        // Check first if contacts == null
        // if not, return Observable(this.contacts)? <-- How to?

        return this.http.get(url)
            .map(res => <Contact[]>res.json())
            .do(contacts => {
                this.contacts = contacts;
                console.log(contacts);
            }) // eyeball results in the console
            .catch(this.handleError);
    }


    private handleError(error: Response) {
        // in a real world app, we may send the server to some remote logging infrastructure
        // instead of just logging it to the console
        console.error(error);
        return Observable.throw(error.json().error || 'Server error');
    }
}
smitop
  • 4,770
  • 2
  • 20
  • 53
Mathius17
  • 2,412
  • 2
  • 21
  • 33

7 Answers7

97

You're right there. If you already have the data in memory, you can use of observable (equivalent of return/just in RxJS 4).

getContacts() {

    if(this.contacts != null) 
    {
        return Observable.of(this.contacts);
    } 
    else 
    {
        return this.http.get(url)
            .map(res => <Contact[]> res.json())
            .do(contacts => this.contacts = contacts)
            .catch(this.handleError);
    }
}
Eric Martinez
  • 31,277
  • 9
  • 92
  • 91
  • This is probably a dumb question but couldn't you just cache the observable itself and call .next() rather than caching the state and recreating the observable on each call? – Evan Plaice Feb 21 '16 at 07:38
  • Good point, I'll try it. Though it my not be the best for when I want to iterate the array to return a single contact by a specific key – Mathius17 Feb 21 '16 at 20:14
  • 18
    import 'rxjs/add/observable/of'; – Sal Jul 24 '17 at 19:45
  • 1
    @Eric Martinez, thanks for your answer. Where do you get the of from. I imported that from 'rxjs' however it is not in the Observable. – David Sagang Sep 10 '18 at 20:09
48
import { of } from 'rxjs';
    
return of(this.contacts);
upe
  • 1,862
  • 1
  • 19
  • 33
Mo D Genesis
  • 5,187
  • 1
  • 21
  • 32
15

Some people like me want it differently, which is from string[] into Observable<string>.

This is an example which involves the conversion:

import { from } from 'rxjs/observable/from';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toArray';

const ids = ['x12', 'y81'];
let userUrls: string[];

from(ids) // Converting string[] into Observable<string>
  .map(id => 'http://localhost:8080/users/' + id)
  .toArray()
  .subscribe(urls => userUrls = urls);

Hopefully it will help some others.

sancho21
  • 3,511
  • 1
  • 39
  • 45
13

In angular7 its enough to just put the of(). Whatever you put inside of() will be changed to observable. Here, this.contacts is converted to observable.

import { of } from 'rxjs';

getContacts() {
    if (this.contacts != null) {
        return of(this.contacts);
    }
}
upe
  • 1,862
  • 1
  • 19
  • 33
kavinbalaji
  • 131
  • 1
  • 4
  • This leads to: Error: Module not found: Error: Package path ./add/observable/of is not exported from package ...\node_modules\rxjs (see exports field in ...\node_modules\rxjs\package.json) – Daniel Methner May 21 '23 at 20:13
1

Quick solution here:

getSomething(): Observable < Object[] > {
    return new Observable(observable => {
        this.http.get('example.com').subscribe(results => {
            observable.next(results.json());
            observable.complete();
        });
    });
}
upe
  • 1,862
  • 1
  • 19
  • 33
Mr.Zon
  • 195
  • 7
0

This might be extremely late in coming, but I've been using sessionStorage pretty heavily to handle some of my work. If you lose state because people were bouncing around, you still have your data.

getSubjects(): Observable < Subject[] > {
    let SubjectsString = sessionStorage.getItem("Subjects");

    if (SubjectsString) {
        let subjects: Subject[] = JSON.parse(SubjectsString);
        console.log("GETTING DATA FROM SESSION STORAGE");
        return Observable.of(subjects);
    } else {
        let url = `${this.apiHost}/api/subject`;
        return this.http.get(url)
            .map(res => {
                sessionStorage.setItem("Subjects", JSON.stringify(res.json()));
                return res.json();
            });
    }
}

In this example, I've got a class for Subject and a definition for apiHost, but the rest is pretty simple. You call the service for an observable. Your component has no idea where the data is and it doesn't care. The service checks local storage and, if it has it, converts it into an observable and returns it, if it doesn't, it goes and gets it, then saves it to local storage and then returns it.

In my case, I have some huge applications with hundreds of pages. Users might bounce from a nice new Angular4 page, over to a classic ASP age and back again multiple times. Having all menu items and even search results in sessionstorage has been a lifesaver.

upe
  • 1,862
  • 1
  • 19
  • 33
Daniel Morris
  • 329
  • 3
  • 4
0

You can also use Observable.of(resultArray);

from the import { Observable } from 'rxjs;' package

Harry Sarshogh
  • 2,137
  • 3
  • 25
  • 48