打字稿通用服务

问题描述 投票:21回答:3

我是typescript和angular2 / 4的新手,我正在构建一个具有两个基本实体的应用程序,即Car和Driver,我所做的就是用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中更改的是url末尾的Car/Observable<Car[]>中的数据类型以及响应。

我想知道使用通用服务避免这种情况的最佳方法是什么,以及如何在Typescript中执行此操作。

angular generics typescript service
3个回答
45
投票

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

抽象类:

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

2
投票

为您的应用提供基本服务。

get postdelete方法与您的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调用 - 所有调用最终都来自基础。

qazxsw pmi可以被任何要扩展基本服务的模块覆盖。

让我们假设你的端点是这样的:HOST_AND_ENDPOINT_START

而且你想要实现一个/myapp/rest/,你可以简单地扩展HttpSearchBase并覆盖HttpServiceBase,例如:

HOST_AND_ENDPOINT_START

例子/myapp/rest/search

CarDriverService

1
投票

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

@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) } } 代表任何服务器响应。服务器必须具有相同的结构并在发生任何事情时返回它

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()); }); 并直接注入RestService<T>.ctor而不是声明和注射RestService<Order>

看起来OrderService不允许重新抛出/返回类型错误。出于这个原因,RxJS 6捕获所有错误并将其返回到强类型RestService<T>中。调用代码应该检查ApiResponse<T>而不是在ApiResponse<T>.hasErrors()上捕获错误

© www.soinside.com 2019 - 2024. All rights reserved.