简体   繁体   中英

Using Angular2 HTTP Service with caching mecanism

I expose an HTTP GET request through a service, and several components are using this data (list of countries). I would like the first component request to actually perform the HTTP GET request to the server and cache the results so the the consequent requests will use the cached data, instead of calling the server again.

Here's my Service:

import {Injectable} from '@angular/core'
import {Subject} from 'rxjs/Subject';
import {Message, SelectItem} from 'primeng/primeng';
import {Http, Headers, RequestOptions} from '@angular/http';
import {environment} from '../../../environments/environment';
import {Country} from '../../domain/country';
import {Response} from "@angular/http";
import {Observable} from 'rxjs/Observable';
import {ReplaySubject} from "rxjs/Rx";
import "rxjs/add/observable/of";

@Injectable()
export class ListsService {

  private _listOfCountries:SelectItem[]=[];
  private countries:SelectItem[]=[];

  private options = new RequestOptions({headers: new Headers({'Content-Type': 'application/json'})});

  constructor(private http:Http) {
    console.log("create ListsService");
  }

  /**
   * Get all countries
   */
  getListOfCountries():Observable<SelectItem[]> {
    console.log('this._listOfCountries==');
    console.log(this._listOfCountries);
    let countries$ = this._listOfCountries.length > 0? Observable.of(this._listOfCountries): this.http
      .get(`${environment.backend_url}api/countries`, this.options)
      .map(this.mapCountries)
      .catch(this.handleError);
    return countries$;
  }

  private mapCountries(response:Response):SelectItem[] {
    let countries:Country[] = response.json();
    let selectItems:SelectItem[] = countries.map((c) => {
      return {label: c.frLabel, value: c.id}
    });
    this._listOfCountries = selectItems.map(a => Object.assign({}, a));
    console.log('this._listOfCountries==');
    console.log(this._listOfCountries);
    return selectItems;
  }

  private handleError(error:any) {
    let errorMsg = error.message || `Error during retrieving data from the API`;
    return Observable.throw(errorMsg);
  }
}

I'm injecting my service on components like Below:

constructor(
              private listService:ListsService,
              private router: Router,
              public fb:FormBuilder) {
...
}

  ngOnInit() {

    this.listService.getListOfCountries().subscribe(countries => {
      console.log(countries);
      this.listOfCountries = countries;
    });
}

I'm declaring the Service only on my root module app.module.ts like below:

...
import {ListsService} from "./shared/services/lists.service";

@NgModule({
  declarations: [
    AppComponent,
    IntroComponent,
  ],
  imports: [
    // @angular
    BrowserModule,
    FormsModule,
    ReactiveFormsModule,
    HttpModule,
    RouterModule.forRoot(routes, {useHash: true}),
    ...
    //app
    AdministrationModule,
  ],
  providers: [MessageService, ListsService],
  bootstrap: [AppComponent]
})
export class AppModule { }

I'm expecting the service to maintain the data in the field _listOfCountries but it's always initialized as an empty array so I'm not having the desired behavior.

Anyone can point me how to fix this and suggest the best approach to resolve this issue?

Solution*: I modified the Service like below:

@Injectable()
export class ListsService {

  private countriesSubject: Subject<SelectItem[]>;
  private countriesRequest: Observable<SelectItem[]>;

  private options = new RequestOptions({headers: new Headers({'Content-Type': 'application/json'})});

  constructor(private http:Http) {
    console.log("create ListsService");
    this.countriesSubject = new ReplaySubject<SelectItem[]>(1);
  }

  /**
   * Get all countries
   */
  getListOfCountries(refresh: boolean = false) {
    if (refresh || !this.countriesRequest) {
      this.countriesRequest =  this.http
        .get(`${environment.backend_url}api/countries`, this.options)
        .map(this.mapCountries)
        .catch(this.handleError);

      this.countriesRequest.subscribe(
        result => this.countriesSubject.next(result),
        err => this.countriesSubject.error(err)
      );
    }
    return this.countriesSubject.asObservable();
  }

  private mapCountries(response:Response):SelectItem[] {
    console.log('mapCountries');
    let countries:Country[] = response.json();
    let selectItems:SelectItem[] = countries.map((c) => {
      return {label: c.frLabel, value: c.id}
    });
    return selectItems;
  }
}

on my component:

ngOnInit() {

    this.listService.getListOfCountries().take(1).subscribe(countries => {
      this.listOfCountries = countries;
    });

  }

Your components which use ListsService can subscribe to the Observable , and then maintain their own cache.

@Component({...})
export class SomeComponent implements OnInit {
  private countries:SelectItem[]=[];

  constructor(private listsService: ListsService) {}

  ngOnInit() {
    this.listsService.getListOfCountries()
      .subscribe((countries: SelectItem[]) => {
        this.countries = countries;
      }
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM