Pass generic type as type parameter - javascript

I am struggling to come up with a solution to a problem i'm facing, where i am trying to use a generic type as a type parameter.
I have the following two classes/types:
UserModel.ts:
export class UserModel{
private _id : string;
public get id() : string {
return this._id;
}
public set id(v : string) {
this._id = v;
}
....
}
HttpResponse.ts:
export class HttpResponse<T>{
private _success : boolean;
public get success() : boolean {
return this._success;
}
public set success(v : boolean) {
this._success = v;
}
private _model : T;
public get model() : T {
return this._model;
}
public set model(v : T) {
this._model = v;
}
}
As you can guess, I am using this to have a generic type to handle http calls easily. The intended use is to call my http method with the HttpResponse type and whatever the expect result type is as the type parameter. For example, when making a user related http call the type parameter would be HttpResponse<UserModel>> or for a resource related call it would be HttpResponse<ResourceModel>>. However, i don't seem to be having any luck when trying this.
In my data service I have a method that POSTS to the server called 'create' and
create<T>(data: any){
//Angular's HttpClient
return this.http.post<T>(data, ...);
}
I then have a service that extends this class and overloads the create method with some extra bits before it calls super.create. I run into the issue at this point because I want to pass in HttpResponse<Type the service should return> which in the following case would be HttpResponse<UserModel>:
create<HttpResponse<UserModel>>(user: UserModel){
//stuff happens here
return super.create<HttpResponse<UserModel>>(user, ...);
}
However, this returns a syntax error at create<HttpResponse<UserModel>>.
I had a look online and found another way to achieve this as something along the lines of:
create<HttpResponse, UserModel>(user: any){
//stuff happens here
return super.create<HttpResponse<UserModel>>(user, ...);
}
But, again, this returns an error stating "HttpResponse is not generic".
The design idea is that by passing in the types in this manner, the json response from the server can be automatically mapped into the appropriate type, making it simple to use the response throughout the application
Any pointers as to where I am going wrong in my design?

A subclass has to maintain the entire interface of its base class (with some exceptions that aren't relevant here). If your data service base class contains a create method that is generic and works for every T, then your subclass cannot override create with a method that only works for T = HttpResponse<UserModel>. Instead, consider (1) defining a new method with a different name or (2) moving the T parameter from the create method to the base class itself, so that when your subclass extends the base class, it can specify the single T that it works with. (Compare to this recent question.) If this doesn't seem to be what you're looking for, then please provide more information.

Related

How do I retrieve the name of a generic class of type T in typescript

I am trying to implement the Service Locator pattern in TypeScript.
Here is my code:
//due to only partial knowledge of TypeScript
private static serviceMap: Map<string, any>;
public static get<T>(): T {
// firstly lazily register all of the necessary services if this is the
// first time calling get.
if(this.serviceMap == undefined){
this.init();
}
let service = this.serviceMap.get(T.name) //issue
if(service == undefined){
throw Error("You must register the service before retrieving it.")
}
return service;
}
The problem is on the line marked issue. As you can see I am trying to retrieve the name of the type of class that I am passing to the method. When I try and call T.name I get this error:
TS2693: 'T' only refers to a type, but is being used as a value here.
How can I retrieve the name of the class of type T.
I am very new to TypeScript so I apologise in advance if the answer is super simple.
Service Locator's get method has to receive something based on which can locate the instance.
If you change the signature to this: public static get<T>(fn: Function): T {
then function type has a prop called name and you can use it inside get like this:
let service = Locator.serviceMap.get(fn.name);
The locator getting class instances can be called with:
const classInstance = Locator.get<ClassC>(ClassC);
Check this stackblitz.

Best practice/only possibility. "Json to Javascript / Typescript Object by constructor"

I started developing with angular / typescript and created a service for my .NET Core API and I like to know what's the best way to get a clean and reliable object from my service.
I have an .NET CORE REST API returning a json result represented by the class definition below.
Service:
demoservice.getDemo().subscribe((val) => new Demo(val));
Demo-Class is the following code:
export class Demo {
public id : number;
public name : string;
public subDemo: SubDemo;
constructor(demo: Demo) {
this.id = demo.id;
this.name = demo.name;
this.subDemo = new SubDemo(demo.subDemo);
}
}
export class SubDemo {
public demoList : ListDemo[]
constructor(subDemo: SubDemo) {
this.demoList = new Array<ListDemo>();
subDemo.demoList.forEach(dl => {
this.demoList.push(new ListDemo(dl))
});
}
}
export class ListDemo {
constructor(listdemo : ListDemo) {
this.birthday = listdemo.birthday;
this.smoker = listdemo.smoker;
}
get birthDayFormatted() : Date {
return new Date(this.birthday);
}
public birthday : string;
public smoker : boolean;
}
I this the best way (full implement all constructors) to create a object. Please note I like to use the "getter" - functionality of my ListDemo Class.
Is there no better way? I just found some Object.clone / Object.assign / Object.create.
But none of this solution is comprehensive...
I am really interested in your experience..
Since you're using better & best I will answer with my, probably unwanted, opinion. Disclaimer: I'm no guru, this answer is based on opinion, feel free to disregard.
Don't do it. Your server has a set of objects in its domain, probably some kind of problem solving or data storage domain.
Your client has a set of objects in its domain, typically focused on presenting the data to the user and allowing the user to understand and manipulate it.
Both of these domains may have objects that have the same name or are based on the same real world concept. It can be tempting to feel like they are the same domain with the same objects. They are not. If they were the same you would not be writing a client and a server you would be writing two of the same thing. The two should communicate with pure data objects. In TS this means you should only create an interface, not a class, for the objects you receive from the server.
Instead, start over. Create a new domain based on what you want to appear in the API. If you design your API from the top (UI) down to the bottom (access services) you'll likely find that you don't have a one-to-one mapping between objects. On the occasions you do then you can probably get away with the occasional assign / merge (see below) but I've personally never found reason to do more than what you posted above.
Should you persist you may come across the option to reassign the prototype of the JSON literal from the base object to the class the data represents but that is a contentious topic and potential performance pitfall.. Your best bet is probably to just do a recursive/deep assign like Lodash's merge.
Just use interfaces not classes.
export interface Demo {
id: number;
name: string;
subDemo: SubDemo;
}
export interface SubDemo {
demoList: ListDemo[];
}
export interface ListDemo {
birthday: string;
smoker: boolean;
}
and your api should return the same shape, you should just be able to go
getDemo(): Observable<Demo> {
return this.http.get<Demo>('url');
}
in your service and in your component assign it to a property
demo$ = this.service.getDemo();
and then use the observable with the async pipe in your template.
<ng-container *ngIf="demo$ | async as demo">
{{ demo | json }}
</ng-container>
The less you have to manipulate your data the better. I have a VS pluging that allows you to paste C# classes into TS files and it converts them to TypeScript interfaces on the fly.

Call Typescript methods with named parameters from decorator

I'm fairly new into typescript, coming from java and still trying to get into typescript. I'm trying to write a small lib for a rest service with express, using typescripts decorator.
#rest() //Not yet sure if i really need this
class RestCall{
#get("/path")
doSomething(id: number, name: string){
console.log(id+": "+name);
}
}
So, I want to call this method, whenever express gets a call on "/path". I also want to call the method with it's parameters from the get request.
The only problem I have is calling this method with the parameters in the right order cause it should also work with this:
#rest() //Not yet sure if i really need this
class AnotherRestCall{
#get("/path")
doSomething(name: string, id: number){ //Not the same order as above
console.log(id+": "+name);
}
}
In the second class, parameters are in an other order.
I tried around with the descriptor and it's value but i can not find a way of getting a list (if possible) of parameters in their order.
function get(path: string){
return (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>): any =>{
}
}
Worst of all is that i don't have a clue on what i should actually search for. In a way i want named parameters from typescript, but not really since it's executed on runtime so there is only javascript left.
Maybe i missed some basic things..
Thanks,
Chris

Angular 5 - Replacement for json().token in HttpClient? [duplicate]

I am trying to change an existing app from using Http to using HttpClient, however i have an error.
So in my service now you can see the new code vs the old code that's been commented out:
constructor(
// private http: Http
private http: HttpClient
) { }
getSidebar() {
// return this.http.get('http://localhost:3000/sidebar/edit-sidebar')
// .map(res => res.json());
return this.http.get('http://localhost:3000/sidebar/edit-sidebar');
}
And in my page.component.ts I have this
this.sidebarService.getSidebar().subscribe(sidebar => {
this.sidebar = sidebar.content; // this does not work now
});
However for the line above which I commented on I get this error now:
Property 'content'
does not exist on type 'Object'.
However if I console.log(sidebar) I get the following:
{_id: "59dde326c7590a27a033fdec", content: "<h1>sidebar here</h1>"}
So what's the issue?
Once again, Http works but HttpClient does not.
You can specify the type that is being returned, using an interface, class, etc. For example, you can use something like the following:
return this.http.get<Sidebar>('http://localhost:3000/sidebar/edit-sidebar');
As an example, Sidebar might be defined as:
interface Sidebar {
_id: string;
content: string;
}
See Typechecking the response from the Angular docs for further information:
...TypeScript would correctly complain that the Object coming back from HTTP does not have a results property. That's because while HttpClient parsed the JSON response into an Object, it doesn't know what shape that object is.
HttpClient parse automatically the JSON response to an Object and the shape of that object is not known, that's why Typescript show this error
alternative solution, using bracket notation:
this.sidebarService.getSidebar().subscribe(sidebar => {
this.sidebar = sidebar["content"];
});
You can assign the variable(sidebar) an interface to explicitely tell what it will get or assign to it so it doesnt throw compile time error.
this.sidebarService.getSidebar().subscribe((sidebar: any) => {
this.sidebar = sidebar.content;
});
you can do this like that with type <any>
if you do this you will not get that error
this.http.get<any>('http://localhost:3000/sidebar/edit-sidebar');
Using bracket notation instead of dot notation to reference sidebar['content'] instead of sidebar.content will solve the issue.
The Type-checking the response section of the Angular docs on HttpClient mention's this, although it's not all that clear:
The subscribe callback above requires bracket notation to extract the
data values. You can't write data.heroesUrl because TypeScript
correctly complains that the data object from the service does not
have a heroesUrl property.
Error Description
ERROR in src/app/home.component.ts(131,20): error TS2339: Property
'someProperty' does not exist on type '{}'.
why Occured ?
I think this error is in service file , whenever you making http call and that call returns any Object(json) or array of Objects (json) , then yours http call should return Observable or Model of that result type
Home.service.ts
connectToServer(data) {
return this.http.post('http://localhost:5000/connect' , data)
}
Suppose , I have above Post http call , where i connect to server by passing data inside 'data' and this request returns me connection Object (Json).But where u seen that i didn't specify observable or model for holding result and if i access the result Object properties from component.ts it will says result.somepropery does not exist on {} .
Solutions
First :
connectToServer(data) : Observable<any> {
return this.http.post('http://localhost:5000/connect' , data)
}
Note : Don't forget to import Observable
Second :
connectToServer(data) : <Connection> {
return this.http.post('http://localhost:5000/connect' , data)
}
In second case you need to define Interface for declaring Connection Object fields and needs to import here.

What to use for data-only objects in TypeScript: Class or Interface?

I have a bunch of data-only "classes" (in .NET world we call them POCO objects) that does not have any methods or even constructors. Examples are Customer, Product, User entities, you name it...
Originally I started using typescript classes but now I'm thinking that declaring them as interface might be better. From performance standpoint, and not only... It's just that in C# we're used to use interfaces for different thing, and for "POCO" (Plain-old-clr-object, or "data-only" object) we use just a class (sometimes even struct).
What is a proper way to declare them in TypeScript?
Note that I mostly understand (I think) technical differences between class and interface (i.e. that interface is a compile-time construct), but I'm trying to find out which one fits this case semantically.
P.S.: I've seen similar questions (like this) but none of them adress this specific issue clearly and definitely, so please don't close this as 'possible duplicate' or 'opinion-based' (cause it isn't) :)
Interface and it's not even close.
People start writing TypeScript and they suddenly think they have to use classes for some reason. But they don't. Classes are an ES6 feature and they work fine, but if it's just data, it's just data.
A major problem with using classes is that they won't serialize/deserialize like you expect over the wire, so things like instanceof checks won't work.
One rule of thumb is that if there's not internal state associated with some methods, and there's no need for traditional OO polymorphism, don't use a class. This even extends to static classes -- use namespace / module instead.
Use classes with parameter properties:
// Immutable data object
class Person {
constructor(readonly firstName: String, readonly lastName: String) {}
}
// Mutable data object
class Person {
constructor(public firstName: String, public lastName: String) {}
}
I use classes for my data in Typescript, as I allways did in C# C++ Java, and only use interfaces for dependency injection. Interfaces have not be thought for manipulating data.
In my application model, if I need to write a method that uses some datas of the same class, then the class is better place to go for that method. Adding getters and setters that transform your properties is a great flexibility.
I am not a javascript programmer so when I need to create an object, I don't like using data only object where properties can by anything. I create an instance of class by the way of the constructors that have been defined for that class.
When I receive data from a service, I don't deserialize a class: I deserialize the json data and I create my instance with that data. Here is the way for building my model from the received data:
// ajax callback for diaries
onReceiveDiary( jsonDiary : any )
{
let newDiary = new Diary ( jsonDiary );
// now I can call methods on the object:
let ok : boolean = newDiary.CheckIfCompleted();
}
In the class I add a constructor with the only one dependency on the json object:
export class Diary
{
title : string;
article : Article;
constructor( json : any )
{
// the trick for setting all the object properties
$.extend( this, json);
this.article = new Article( json.article );
}
}
Or we can create a factory for building objects using the default constructor:
let newDiary = new Diary ();
$.extend( newDiary, jsonDiary );
newDiary.article = $.extend( new Article(), jsonDiary.article );

Categories

Resources