web-dev-qa-db-ja.com

Typescript汎用サービス

私はTypeScriptとangular2/4を初めて使い、CarとDriverという2つの基本エンティティを持つ単一のアプリを構築しています。APIコールでそれらをリストするだけです。

私が直面している問題は、各CarServiceとDriverServiceにコードの冗長性があり、他のエンティティサービスにも同じコードがある可能性があることです。

実装はこれまでのところ次のように行われています。

@Injectable()
export class CarService  {

private actionUrl: string;
private headers: Headers;

constructor(private _http: Http, private _configuration: Configuration) {

    // Getting API URL and specify the root
    this.actionUrl = _configuration.serverWithApiUrl + 'Car/';

    this.headers = new Headers();
    this.headers.append('Content-Type', 'application/json');
    this.headers.append('Accept', 'application/json');
}

// Function to get all Cars - API CALL: /
public GetAll = (): Observable<Car[]> => {
    return this._http.get(this.actionUrl)
        .map((response: Response) => <Car[]>response.json())
        .catch(this.handleError);
}

// Function to get a Car by specific id - API CALL: /:id
public GetSingle = (id: number): Observable<Car> => {
    return this._http.get(this.actionUrl + id)
        .map((response: Response) => <Car>response.json())
        .catch(this.handleError);
}

// Function to add a Car - API CALL: /create
public Add = (newCar: Car): Observable<Car> => {
    return this._http.post(this.actionUrl + '/create', JSON.stringify(newCar), { headers: this.headers })
        .catch(this.handleError);
}

// Function to update a Car - API CALL: /
public Update = (id: number, CarToUpdate: Car): Observable<Car> => {
    return this._http.put(this.actionUrl + id, JSON.stringify(CarToUpdate), { headers: this.headers })
        .catch(this.handleError);
}

// Function to delete a Car - API CALL: /:id
public Delete = (id: number): Observable<Response> => {
    return this._http.delete(this.actionUrl + id)
        .catch(this.handleError);
}

// Function to throw errors
private handleError(error: Response) {
    console.error(error);
    return Observable.throw(error.json().error || 'Server error');
}

DriverServiceで変更されるのはCar/はURLの最後にあり、Observable<Car[]>および応答。

汎用サービスでこれを回避する最善の方法と、TypeScriptでこれを行う方法を知りたいです。

23
Imad El Hitti

抽象ジェネリッククラスと、それを継承する2つの子クラスを作成できます。

抽象クラ​​ス:

_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);
  }
} 
_

ドライバーサービスクラス

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

車のサービスクラス

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

具象クラスのみが@Injectable()としてマークされ、モジュール内で宣言する必要がありますが、抽象クラスはそうではないことに注意してください。

Angular 4 +の更新

Httpクラスの代わりにHttpClientが推奨されるため、抽象クラスを次のようなものに変更できます。

_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>;
  }
} 
_
45
n00dl3

アプリの基本サービスを用意します。

getpostおよびdeleteメソッドでbase URL添付。

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

        );
    }

これは、WS呼び出しを簡単にデバッグできるため、便利な実装です。すべての呼び出しは最終的にベースから行われます。

Host_AND_ENDPOINT_STARTは、ベースサービスを拡張するモジュールによってオーバーライドできます。

エンドポイントを次のようなふりにしましょう:/myapp/rest/

HttpSearchBaseを実装したい場合は、単にHttpServiceBaseを拡張し、Host_AND_ENDPOINT_STARTのようなもの:

/myapp/rest/search

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)
    }


}
2
Daniel Cooke

以下は、Angular 7およびRxJS 6で構築された基本的な例です。

_ApiResponse<T>_は、サーバー応答を表します。サーバーは同じ構造を持ち、何が起こってもそれを返さなければなりません:

_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,
  ...
}
_

汎用サービス:

_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>();
        Object.assign(result, err.error)
        // if err.error is not ApiResponse<TData> e.g. connection issue
        if (result.errors.length == 0) {
          result.errors.Push({ code: ErrorCode.UnknownError, text: 'Unknown 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("&");
  }
}
_

その後、_RestService<T>_から派生できます。

_export class OrderService extends RestService<Order> {
  constructor(http: HttpClient) { super('order', http); }
}
_

そしてそれを使用します:

_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());
});
_

OrderServiceを宣言および挿入する代わりに、_RestService<T>.ctor_を再設計し、_RestService<Order>_を直接注入できます。

_RxJS 6_は型付きエラーの再スロー/リターンを許可していないようです。このため、_RestService<T>_はすべてのエラーをキャッチし、厳密に型指定された_ApiResponse<T>_内でエラーを返します。呼び出しコードは、_Observable<T>_でエラーをキャッチする代わりに、ApiResponse<T>.hasErrors()をチェックする必要があります

2
AlbertK