Angular 类型脚本通用服务

Angular 类型脚本通用服务,angular,generics,typescript,service,Angular,Generics,Typescript,Service,我不熟悉typescript和angular2/4,我正在构建一个应用程序,它有两个基本实体,即汽车和司机,我所做的就是用API调用列出它们 我面临的问题是,每个CarService和DriversService都有代码冗余,而其他服务实体可能也有相同的代码 到目前为止,实现如下所示,跳过了用于说明的其他方法: @Injectable() export class CarService { private actionUrl: string; private headers: Headers

我不熟悉typescript和angular2/4,我正在构建一个应用程序,它有两个基本实体,即汽车和司机,我所做的就是用API调用列出它们

我面临的问题是,每个CarService和DriversService都有代码冗余,而其他服务实体可能也有相同的代码

到目前为止,实现如下所示,跳过了用于说明的其他方法:

@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');
}
@Injectable()
出口级汽车服务{
私有actionUrl:string;
私有头:头;
构造函数(私有http:http,私有配置:配置){
//获取API URL并指定根目录
this.actionUrl=_configuration.serverWithApiUrl+'Car/';
this.headers=新的headers();
this.headers.append('Content-Type','application/json');
append('Accept','application/json');
}
//获取所有汽车的函数-API调用:/
公共GetAll=():可观察=>{
返回此。\u http.get(this.actionUrl)
.map((response:response)=>response.json())
.接住(这个.把手错误);
}
//通过特定id获取汽车的函数-API调用:/:id
public GetSingle=(id:number):可观察=>{
返回此。\u http.get(this.actionUrl+id)
.map((response:response)=>response.json())
.接住(这个.把手错误);
}
//用于添加Car-API调用的函数:/create
公共添加=(新车:汽车):可观察=>{
返回此文件。_http.post(this.actionUrl+'/create',JSON.stringify(newCar),{headers:this.headers})
.接住(这个.把手错误);
}
//用于更新Car-API调用的函数:/
公共更新=(id:number,CarToUpdate:Car):可观察=>{
返回这个。_http.put(this.actionUrl+id,JSON.stringify(CarToUpdate),{headers:this.headers})
.接住(这个.把手错误);
}
//用于删除Car-API调用的函数:/:id
publicdelete=(id:number):可观察=>{
返回此。\u http.delete(this.actionUrl+id)
.接住(这个.把手错误);
}
//函数抛出错误
私有句柄错误(错误:响应){
控制台错误(error);
返回Observable.throw(error.json().error | |'Server error');
}
DriversService唯一改变的是url末尾的
Car/
,以及
Observable
中的数据类型和响应


我想知道使用通用服务避免这种情况的最佳方法是什么,以及如何在Typescript中做到这一点。

为您的应用程序提供基本服务

使用
get
post
delete
方法,附加
基本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

        );
    }

您可以创建一个抽象泛型类和从中继承的两个子类:

抽象类:

导出抽象类AbstractRestService{
构造函数(受保护的http:http,受保护的actionUrl:string){
}
getAll():可观察{
返回这个;
}
getOne(id:number):可观察{
返回这个.http.get(`${this.actionUrl}${id}`).map(resp=>resp.json()作为T);
}
} 
驾驶员服务等级

@Injectable()
导出类DriverService扩展了AbstractRestService{
构造函数(http:http,配置:configuration){
super(http,configuration.serverwithapirl+“Driver/”);
}
}
汽车服务等级

@Injectable()
导出类CarService扩展了AbstractRestService{
构造函数(http:http,配置:configuration){
super(http,configuration.serverwithapirl+“Car/”);
}
}
请注意,只有具体类被标记为
@Injectable()
,应该在模块内声明,而抽象类则不应该

角度4+的更新

Http
类被弃用,取而代之的是
HttpClient
,您可以将抽象类更改为如下内容:

导出抽象类AbstractRestService{
构造函数(受保护的http:HttpClient,受保护的actionUrl:string){
}
getAll():可观察{
返回此。_http.get(this.actionUrl)作为可观察的;
}
getOne(id:number):可观察{
返回这个.u http.get(`${this.actionUrl}${id}`)作为可观察的;
}
} 

下面是一个基于Angular 7RxJS 6的基本示例

ApiResponse
表示任何服务器响应。服务器必须具有相同的结构,并返回它,无论发生什么情况:

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,
  ...
}
导出类响应{
构造函数(){
this.errors=[];
}
资料:T;
错误:APIRROR[];
getErrorsText():字符串{
返回this.errors.map(e=>e.text.join(“”);
}
hasErrors():布尔值{
返回this.errors.length>0;
}
}
导出类APIRROR{code:ErrorCode;text:string;}
导出枚举错误代码{
未知错误=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>();
        // 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("&");
  }
}
导出类RestService{
httpOptions={
headers:newhttpheaders({'Content-Type':'application/json',
'Accept':'application/json'})
};
private _apident:string=environment.apident;
构造函数(private _url:string,private _http:HttpClient){}
getAll():可观察{
返回this.mapAndCatchError(
this.\u http.get(this.\u apident+this.\u url
,此为.httpOptions)
);
}
get(id:number):可观察{
返回this.mapAndCatchError(
this.\u http.get(`${this.\u apident+this.\u url}/${id}`
,此为.httpOptions)
);
}
添加(资源:T):可观察{
返回this.mapAndCatchError(
这是http.post(
这个。_apident+这个。_url,
资源,,
这是(http://httpOptions)
);
}
//在此处更新并删除。。。
//常用方法
makeRequest(方法:string,url:string,数据:any)
:可见{
让finalUrl:string=this.\u apident+url;
let body:any=null;
if(method.toUpperCase()=='GET'){
finalUrl+='?'+this.objectToQueryStrin
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("&");
  }
}
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());
});