Typescript generic service

后端 未结 3 411
礼貌的吻别
礼貌的吻别 2021-01-30 14:16

I\'m new to typescript and angular2/4 and I\'m building a single app that have two basic entities which is Car and Driver and all I do is to list them with an API call.

3条回答
  •  无人共我
    2021-01-30 14:39

    Below is a basic example built on Angular 7 and RxJS 6.

    ApiResponse represents any server response. Server must have the same structure and return it whatever happens:

    export class ApiResponse {
      constructor() {
        this.errors = [];
      }
      data: T;
      errors: ApiError[];
      getErrorsText(): string {
        return this.errors.map(e => e.text).join(' ');
      }
      hasErrors(): boolean {
        return this.errors.length > 0;
      }
    }
    
    export class ApiError { code: ErrorCode; text: string; }
    
    export enum ErrorCode {
      UnknownError = 1,
      OrderIsOutdated = 2,
      ...
    }
    

    Generic service:

    export class RestService {
      httpOptions = {
        headers: new HttpHeaders({ 'Content-Type': 'application/json', 
           'Accept': 'application/json'})
      };
      private _apiEndPoint: string = environment.apiEndpoint;
      constructor(private _url: string, private _http: HttpClient) { }
    
      getAll(): Observable> {
        return this.mapAndCatchError(
          this._http.get>(this._apiEndPoint + this._url
             , this.httpOptions)
        );
      }
      get(id: number): Observable> {
        return this.mapAndCatchError(
          this._http.get>(`${this._apiEndPoint + this._url}/${id}`
             , this.httpOptions)
        );
      }
      add(resource: T): Observable> {
        return this.mapAndCatchError(
          this._http.post>(
            this._apiEndPoint + this._url,
            resource,
            this.httpOptions)
        );
      }
      // update and remove here...
    
      // common method
      makeRequest(method: string, url: string, data: any)
                                        : Observable> {
        let finalUrl: string = this._apiEndPoint + url;
        let body: any = null;
        if (method.toUpperCase() == 'GET') {
          finalUrl += '?' + this.objectToQueryString(data);
        }
        else {
          body = data;
        }
        return this.mapAndCatchError(
          this._http.request>(
            method.toUpperCase(),
            finalUrl,
            { body: body, headers: this.httpOptions.headers })
        );
      }
    
      /////// private methods
      private mapAndCatchError(response: Observable>)
                                             : Observable> {
        return response.pipe(
          map((r: ApiResponse) => {
            var result = new ApiResponse();
            Object.assign(result, r);
            return result;
          }),
          catchError((err: HttpErrorResponse) => {
            var result = new ApiResponse();
            // if err.error is not ApiResponse e.g. connection issue
            if (err.error instanceof ErrorEvent || err.error instanceof ProgressEvent) {
              result.errors.push({ code: ErrorCode.UnknownError, text: 'Unknown error.' });
            }
            else {
              Object.assign(result, err.error)
            }
            return of(result);
          })
        );
      }
    
      private objectToQueryString(obj: any): string {
        var str = [];
        for (var p in obj)
          if (obj.hasOwnProperty(p)) {
            str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
          }
        return str.join("&");
      }
    }
    

    then you can derive from RestService:

    export class OrderService extends RestService {
      constructor(http: HttpClient) { super('order', http); }
    }
    

    and use it:

    this._orderService.getAll().subscribe(res => {
      if (!res.hasErrors()) {
        //deal with res.data : Order[]
      }
      else {
        this._messageService.showError(res.getErrorsText());
      }
    });
    // or
    this._orderService.makeRequest('post', 'order', order).subscribe(r => {
      if (!r.hasErrors()) {
        //deal with r.data: number
      }
      else
        this._messageService.showError(r.getErrorsText());
    });
    

    You can redesign RestService.ctor and inject RestService directly instead of declaring and injection OrderService.

    It looks like RxJS 6 doesn't allow to rethrow/return typed errors. For this reason RestService catches all errors and returns them within strongly typed ApiResponse. The calling code should check ApiResponse.hasErrors() instead of catching errors on Observable

提交回复
热议问题