I am using Ionic 4 and Angular 7 to create a hybrid mobile app. This app will communicate with an API that I have developed in PHP (Yii2) to display data to the users.
I want to create a generic REST API class to communicate with the server, and I have used this article as a baseline for my development (https://medium.com/@krishna.acondy/a-generic-http-service-approach-for-angular-applications-a7bd8ff6a068).
From my understanding reading multiple other articles and documentation, the serializer class example in that article should declare the methods fromJson()
and toJson()
as static. I am used to doing this kind of stuff in PHP. Since there is no state to this object and these are essentially helper functions, I don't want multiple instances of these classes around when I'm really only using these helper functions (help me understand if I am wrong here).
However, in the rest.service.ts
file those functions are being called on an instance (this.serializer.fromJson(data)
). To call a static method from what I've read I need to call it like ClassName.staticMethod()
but in the context of rest.service.ts
I cannot do that since I do not know how to call a static method dynamically on a variable (this.serializer.staticMethod()
). In PHP I am used to being able to call $className::staticMethod()
which is what I am trying to find an equivalent for here but it doesn't seem possible.
I found this question (Dynamically calling a static method) which seems to be touching on what I want to do but it seems like there must be a better way. Doing it that way in each one of my XYZ.service.ts
classes I will have to write out the same map using the serializer class' static methods, which seems to be repeating myself and makes me think there must be a better practice for what I am trying to achieve.
Another reason for all of this is that in my serializers I sometimes need to reference another serializer for subresources. Right now I am creating an instance to do this, but this seems inefficient and further makes me think that I should be finding a way to do this statically since I may have multiple instances of XYZSerializer
created just to be able to run the methods that I feel should be static.
Here is an example of the working code I have right now, modeled after the above article, abbreviated for ease of reading:
export interface Serializer {
fromJson(json: any): ApiResource;
toJson(resource: ApiResource): any;
}
export class SharkBiteSerializer implements Serializer{
playerSerializer: PlayerSerializer = new PlayerSerializer;
fromJson(json: any): SharkBite {
const sharkBite = new SharkBite();
sharkBite.id = json.id;
...
sharkBite.featuredPlayer = this.playerSerializer.fromJson(json.featuredPlayer);
return sharkBite;
}
toJson(sharkBite: SharkBite): any {
return {
id: sharkBite.id,
...
};
}
}
export class SharkBiteService extends RestService<SharkBite> {
endpoint = 'shark-bites'
serializer = new SharkBiteSerializer;
constructor(
httpClient: HttpClient,
)
{
super(httpClient);
}
}
export abstract class RestService<T extends ApiResource> {
abstract endpoint: string;
abstract serializer: Serializer;
constructor(
private httpClient: HttpClient,
) {}
/**
* Add a new record
*/
public create(item: T): Observable<T> {
return this.httpClient
.post<T>(`${environment.apiUrl}/${this.endpoint}`, this.serializer.toJson(item))
.pipe(
map(data => this.serializer.fromJson(data) as T)
);
}
/**
* Update an existing record
*/
public update(item: T): Observable<T> {
return this.httpClient
.put<T>(`${environment.apiUrl}/${this.endpoint}/${item.id}`, this.serializer.toJson(item))
.pipe(
map(data => this.serializer.fromJson(data) as T)
);
}
/**
* Retrieve a single record
*/
public read(id: number): Observable<T> {
return this.httpClient
.get(`${environment.apiUrl}/${this.endpoint}/${id}`)
.pipe(
map(data => this.serializer.fromJson(data) as T)
);
}
/**
* Retrieves muleiple records based on a query
*/
public list(httpParams: HttpParams): Observable<T[]> {
return this.httpClient
.get(`${environment.apiUrl}/${this.endpoint}`, {params: httpParams})
.pipe(
map((data: any) => this.convertData(data))
);
}
/**
* Delete a single record
*/
public delete(id: number) {
return this.httpClient
.delete(`${environment.apiUrl}/${this.endpoint}/${id}`);
}
/**
* Converts the array of items into an array of the typed items
*/
private convertData(data: any): T[] {
return data.map(item => this.serializer.fromJson(item));
}
}
To try to reiterate the point of what I am trying to do, I would like to instead have fromJson
and toJson
be static methods. In rest.service.ts
I should be calling SerializerClassName.toJson()
and SerializerClassName.fromJson()
; then in the SharkBiteSerializer
I could call PlayerSerializer.toJson()
and PlayerSerializer.fromJson()
statically. I toyed around with using an abstract class Serializer
to have the methods be static but I can't seem to figure out how to call a static method on a variable.
If I am not following a best practice here, please enlighten me. I am new to Typescript and I realize my PHP background may be seeping in. I am just trying to find the best way to implement this and am willing to take any suggestions. Thank you!
#
Update:
I understand the concept that I cannot call a static method on a instance of the class. I am wondering whether I could any way store the class name in a variable and then call the static method on the class name stored in that variable. For instance, in PHP I can:
$className = '\namespace\for\ClassName';
echo $className::staticMethod();
I would like some way to store the name of the serializer class on each rest service and have it call the static function from that serializer class. Or, if there is a better method of implementing what I am looking for I am open to ideas.