I am beginning to develop a common data service that can be used by other services. Specifically, this service will be injected into a generic abstract application service. I am trying to figure out the best way to return the results from these services.
I chose to setup a common wrapper for the HTTP service to consume a custom IResult from my API application. It allows for system messages (error, warnings, successes), typical post/get commands, and so it can be used throughout the application with ease. I am using blockUI to show a loading indicator
The common data service:
@Injectable()
export class DataService {
private reqData = {};
private url: string;
@BlockUI() blockUI: NgBlockUI;
constructor(private http: Http) {
}
post(url: string, data: any, blockUi: boolean, blockUiMessage?: string, options?: RequestOptionsArgs): Observable<IResult> {
// some logic
return this.http.post(url, data, options)
.map((response: Response) => {
if (blockUi)
this.blockUI.stop();
var result = <IResult>response.json();
if(result.Message && result.Message.DisplayMessage)
this.showMessage(result.Message);
return result;
})
.catch((error: Response) => {
if (blockUi)
this.blockUI.stop();
console.log(error);
return Observable.throw('Server error');
});
}
public get(url: string, blockUi: boolean, blockUiMessage?: string, options?: RequestOptionsArgs):
Observable<IResult> {
if (!blockUiMessage || blockUiMessage.length <= 0)
blockUiMessage = 'Please Wait...';
if (blockUi)
this.blockUI.start(blockUiMessage);
return this.http.get(url, options)
.map((response: Response) => {
if (blockUi)
this.blockUI.stop();
var result = <IResult>response.json();
if (result.Message && result.Message.DisplayMessage)
this.showMessage(result.Message);
return result;
})
.catch((error: Response) => {
if (blockUi)
this.blockUI.stop();
console.log(error);
var result = <IResult>error.json();
this.showMessage(result.Message);
return Observable.throw(error.json() || 'Server error');
});
}
public showMessage(message: any) {
...code to display growl type messages
}
}
My back-end API exposes commands for GetById<T>
, GetAll<IEnumberable<T>>
, etc. I then wanted to create an abstract; generic base service class that would be used by specific application types. It also requires the user be authenticated. I add an Authorization header for each request in the constructor to ensure this.
The BaseClientDto
is a simple class to expose properties required by the BaseHttpAuthorizationService
. This is best illustrated by the getById
method.
@Injectable()
export abstract class BaseHttpAuthorizationService<T extends BaseClientDto> {
public header: Headers;
public model: BehaviorSubject<T>;
public httpResult: any;
public modelCollection: T[];
constructor(public authService: AuthenticationService, public dataService: DataService) {
this.model = BehaviorSubject.create();
if (this.authService.isAuthenticated())
this.header = new Headers({ 'Authorization': 'Bearer ' + this.authService.getUserFromLocalStorage().token });
}
public getDataService(): DataService {
return this.dataService;
}
public getById(dto: T) {
var url = this.buildUri(dto.constructor.name) + dto.Id;
this.dataService.get(url, true, 'Loading...', new RequestOptions({ headers: this.header }))
.subscribe(result => {
console.log(result.Model);
this.model.next(<T>result.Model);
});
}
public getAll(dto: T) {
var url = this.buildUri(dto.constructor.name);
this.dataService.post(url, dto, true, 'Loading...', new RequestOptions({ headers: this.header }))
.subscribe(result => {
this.modelCollection = <T[]>result.model;
});
}
public deleteHard(dto: T) {
var url = this.buildUri(dto.constructor.name) + 'delete/hard/' + dto.Id;
this.dataService.post(url, dto, true, 'Loading...', new RequestOptions({ headers: this.header }))
.subscribe(result => {
this.httpResult = result;
});
}
public buildUri(typeName: string) {
if (typeName.toLowerCase().indexOf("dto") !== -1)
typeName = typeName.toLowerCase().replace('dto', '');
return globalContansts.ApiUrl typeName + "/";
}
}
I'd like my methods to return whatever T is, rather than a type of observable. s you can see in the generic base class, the model
property is setup as a BehaviorSubject<T>
. I did this so my implemented classes could subscribe to the property and get the value, but this seems clunky to me. I end up having to do something like this in the implemented class to get the result:
export class SomeObjectService extends BaseHttpAuthorizationService<SomeObject>{
someObject = SomeObject;
constructor(){
this.someObject = Observable.create((observer: any) => {
this.implmentedService.model.asObservable()
.subscribe((result: SomeObject) => {
observer.next(result);
});
});
}
}
I am sure there is a better way to set this up than how I currently have it.