Typescript generic service

后端 未结 3 405
礼貌的吻别
礼貌的吻别 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:37

    Have a base service for your app.

    With get post and delete methods with your base URL attached.

    export class HttpServiceBase {
    
        HOST_AND_ENDPOINT_START : string = 'you/rD/efa/ult/Url' ;
        public getWebServiceDataWithPartialEndpoint(remainingEndpoint: string): Observable<Response> {
    
            if (!remainingEndpoint) {
                console.error('HttpServiceBase::getWebServiceDataWithPartialEndpoint - The supplied remainingEndpoint was invalid');
                console.dir(remainingEndpoint);
            }
    
            console.log('GET from : ' , this.HOST_AND_ENDPOINT_START + remainingEndpoint);
            return this.http.get(
                this.HOST_AND_ENDPOINT_START + remainingEndpoint
    
            );
        }
    

    This a useful implementation as it allows you to easily debug WS calls - all calls end up coming from the base.

    HOST_AND_ENDPOINT_START can be overriden by any module that you want to extend the base service.

    Lets pretend your endpoint is something like: /myapp/rest/

    And you want to implement a HttpSearchBase you can simply extend HttpServiceBase and override HOST_AND_ENDPOINT_START with something like:

    /myapp/rest/search

    Example CarDriverService

    @Injectable()
    export class CarDriverService extends HttpServiceBase{
    
        //here we are requesting a different API
        HOST_AND_ENDPOINT_START : string = '/myapp/rest/vehicle/;
        getAllCars() : Observable<Car[]>{
        return this.getWebServiceDataWithPartialEndpoint('/Car')
               .map(res => <Car[]>res.json())
        }
    
        getAllDrivers(){
        return this.getWebServiceDataWithPartialEndpoint('/Driver')
        }
    
        addNewDriver(driver: Driver){
        return this.postWebServiceDataWithPartialEndpoint('/Driver/',driver)
        }
    
    
    }
    
    0 讨论(0)
  • 2021-01-30 14:39

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

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

    export class ApiResponse<T> {
      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<T> {
      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<ApiResponse<T[]>> {
        return this.mapAndCatchError(
          this._http.get<ApiResponse<T[]>>(this._apiEndPoint + this._url
             , this.httpOptions)
        );
      }
      get(id: number): Observable<ApiResponse<T>> {
        return this.mapAndCatchError(
          this._http.get<ApiResponse<T>>(`${this._apiEndPoint + this._url}/${id}`
             , this.httpOptions)
        );
      }
      add(resource: T): Observable<ApiResponse<number>> {
        return this.mapAndCatchError(
          this._http.post<ApiResponse<number>>(
            this._apiEndPoint + this._url,
            resource,
            this.httpOptions)
        );
      }
      // update and remove here...
    
      // common method
      makeRequest<TData>(method: string, url: string, data: any)
                                        : Observable<ApiResponse<TData>> {
        let finalUrl: string = this._apiEndPoint + url;
        let body: any = null;
        if (method.toUpperCase() == 'GET') {
          finalUrl += '?' + this.objectToQueryString(data);
        }
        else {
          body = data;
        }
        return this.mapAndCatchError<TData>(
          this._http.request<ApiResponse<TData>>(
            method.toUpperCase(),
            finalUrl,
            { body: body, headers: this.httpOptions.headers })
        );
      }
    
      /////// private methods
      private mapAndCatchError<TData>(response: Observable<ApiResponse<TData>>)
                                             : Observable<ApiResponse<TData>> {
        return response.pipe(
          map((r: ApiResponse<TData>) => {
            var result = new ApiResponse<TData>();
            Object.assign(result, r);
            return result;
          }),
          catchError((err: HttpErrorResponse) => {
            var result = new ApiResponse<TData>();
            // if err.error is not ApiResponse<TData> 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<T>:

    export class OrderService extends RestService<Order> {
      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<number>('post', 'order', order).subscribe(r => {
      if (!r.hasErrors()) {
        //deal with r.data: number
      }
      else
        this._messageService.showError(r.getErrorsText());
    });
    

    You can redesign RestService<T>.ctor and inject RestService<Order> directly instead of declaring and injection OrderService.

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

    0 讨论(0)
  • 2021-01-30 14:48

    You can create an abstract generic class and two children class that inherits from it :

    abstract class:

    export abstract class AbstractRestService<T> {
      constructor(protected _http: Http, protected actionUrl:string){
      }
    
      getAll():Observable<T[]> {
        return this._http.get(this.actionUrl).map(resp=>resp.json() as T[]);
      }
      getOne(id:number):Observable<T> {
        return this._http.get(`${this.actionUrl}${id}`).map(resp=>resp.json() as T);
      }
    } 
    

    driver service class

    @Injectable()
    export class DriverService extends AbstractRestService<Driver> {
      constructor(http:Http,configuration:Configuration){
        super(http,configuration.serverWithApiUrl+"Driver/");
      }
    }
    

    car service class

    @Injectable()
    export class CarService extends AbstractRestService<Car> {
      constructor(http:Http,configuration:Configuration) {
        super(http,configuration.serverWithApiUrl+"Car/");
      }
    }
    

    Note that only the concrete classes are marked as @Injectable() and should be declared inside a module while the abstract one should not.

    update for Angular 4+

    Http class being deprecated in favor of HttpClient, you can change the abstract class to something like that:

    export abstract class AbstractRestService<T> {
      constructor(protected _http: HttpClient, protected actionUrl:string){
      }
    
      getAll():Observable<T[]> {
        return this._http.get(this.actionUrl) as Observable<T[]>;
      }
    
      getOne(id:number):Observable<T> {
        return this._http.get(`${this.actionUrl}${id}`) as Observable<T>;
      }
    } 
    
    0 讨论(0)
提交回复
热议问题