6: Get Data from a Server
[examples/angular-tour-of-heroes] / src / app / hero.service.ts
1 import { Injectable } from '@angular/core';
2 import { Observable, of, EMPTY } from 'rxjs';
3 import { Hero } from './hero';
4 import { HEROES } from './mock-heroes';
5 import { HttpClient, HttpHeaders } from '@angular/common/http';
6 import { MessageService } from './message.service';
7 import { catchError, tap } from 'rxjs/operators';
8
9
10 @Injectable({
11   providedIn: 'root'
12 })
13 export class HeroService {
14
15   heroesUrl = 'api/heroes';  // URL to web api
16   httpOptions = {
17     headers: new HttpHeaders({ 'Content-Type': 'application/json' })
18   };
19
20   constructor(
21     private http: HttpClient,
22     private messageService: MessageService) { }
23
24   getHeroes(): Observable<Hero[]> {
25     this.log('fetching heroes...');
26     return this.http
27       .get<Hero[]>(this.heroesUrl)
28       .pipe(
29         tap((heroes: Hero[]) => this.log(`fetched ${heroes.length} heroes`)),
30         catchError(this.handleError<Hero[]>('getHeroes', []))
31       );
32   }
33
34   /** POST: add a new hero to the server */
35   addHero(hero: Hero): Observable<Hero> {
36     return this.http.post<Hero>(this.heroesUrl, hero, this.httpOptions).pipe(
37       tap((newHero: Hero) => this.log(`added hero w/ id=${newHero.id}`)),
38       catchError(this.handleError<Hero>('addHero'))
39     );
40   }
41
42   /** GET hero by id. Will 404 if id not found */
43   getHero(id: number): Observable<Hero> {
44     this.log(`requested hero id=${id}`);
45     const url = `${this.heroesUrl}/${id}`;
46     return this.http.get<Hero>(url).pipe(
47       tap(_ => this.log(`fetched hero id=${id}`)),
48       catchError(this.handleError<Hero>(`getHero id=${id}`))
49     );
50   }
51
52   /** PUT: update the hero on the server */
53   updateHero(hero: Hero): Observable<any> {
54     return this.http.put(this.heroesUrl, hero, this.httpOptions).pipe(
55       tap(_ => this.log(`updated hero id=${hero.id}`)),
56       catchError(this.handleError<any>('updateHero'))
57     );
58   }
59
60   /* GET heroes whose name contains search term */
61   searchHeroes(term: string): Observable<Hero[]> {
62     if (!term.trim()) {
63       // if not search term, return empty hero array.
64       return of([]);
65     }
66     return this.http.get<Hero[]>(`${this.heroesUrl}/?name=${term}`).pipe(
67       tap(x => x.length ?
68         this.log(`found heroes matching "${term}"`) :
69         this.log(`no heroes matching "${term}"`)),
70       catchError(this.handleError<Hero[]>('searchHeroes', []))
71     );
72   }
73
74   /**
75    * Handle Http operation that failed.
76    * Let the app continue.
77    * @param operation - name of the operation that failed
78    * @param result - optional value to return as the observable result
79    */
80   private handleError<T>(operation = 'operation', result?: T) {
81     return (error: any): Observable<T> => {
82
83       // TODO: send the error to remote logging infrastructure
84       console.error(error); // log to console instead
85
86       // TODO: better job of transforming error for user consumption
87       this.log(`${operation} failed: ${error.message}`);
88
89       // Let the app keep running by returning an empty result.
90       if (result === undefined) {
91         return EMPTY as Observable<T>;
92       } else {
93         return of(result as T);
94       }
95     };
96   }
97
98   /** Log a HeroService message with the MessageService */
99   private log(message: string) {
100     this.messageService.add(`HeroService: ${message}`);
101   }
102 }