Angular HTTP Tips for Success

I love working with the Angular HttpClient. It is easy to use and was designed to work with RxJS. It is vastly different from the AngularJS implementation, if you’re curious I wrote about these differences here. However, there is one common issue that developers fall victim to. The issue really relates to TypeScript generics. I have also written about generics in TypeScript here. But in this post, we will reveal how the issue can easily be avoided.

THE PROBLEM

The problem is that the HttpClient class exposes generic methods that allow consumers to make assumptions, these assumptions are dangerous.

ProTipNever… make… assumptions!

The assumption is that you can pass an interface with non-primitive types or a class as a generic type parameter, and that it will work as expected. This is simply not the case. Consider the following:

public getDetails(id: number): Promise<Details> {
    return this.http
               .get<Details>(`${this.baseUrl}/api/details/${id}`)
               .toPromise();
}

Most of the time we’d pass in an interface as the type parameter. Often this seems to work as the interfaceis a simple property bag of primitive types. The issue is that if the interface defines non-primitive types like a Date or a Function – these will not be available at runtime. Likewise, if you pass in a class with getor set properties – these too will not work! The specific problem is that the underlying implementation from Angular doesn’t instantiate your object. Instead, it simply casts it as the given type. Ultimately, Angular is performing a JSON.parse on the body of the response.

Part of the issue is that TypeScript is blissfully unaware that Angular will not instantiate the object and treats the return as a Promise<Details>. As such flow analysis, statement completion and all the other amazing features that the TypeScript language services provide to your development environment work. But this is actually misleading, because you’ll encounter runtime errors – this is the issue that TypeScript aims to solve!

WORKING EXAMPLE

An interface with primitive types will work just fine. The JSON.parse will give you an object and because of JavaScript coercion, it works. TypeScript will treat it as this object and everything is perfect.

export interface Details {
    score: number;
    description: string;
    approved: boolean;
}

NON-WORKING EXAMPLE

Imagine that we want to return another property from the server, so we add a Date property. Notice how our interface added this new property. Now we want to do some date logic and use some of the methods on the Date instance – this will not work!

export interface Details {
    date: Date;
    score: number;
    description: string;
    approved: boolean;
}

The details.date property will exist, sure… but it will not be a Date instance – instead it is simply a string. If you attempt to use any of the string methods, it will fail at runtime.

See the Pen TypeScript – JSON.parse interface by David Pine (@ievangelist) on CodePen.

Ah, we can fix this – right?! We might think to ourselves, we’ll use a class instead and then add a getDate()“get function” property that will pass the .date member to the Date constructor. Let’s look at this.

export class Details {
    date: Date;
    score: number;
    description: string;
    approved: boolean;

    get getDate(): Date {
        return new Date(this.date);
    }
}

Perhaps to your surprise, this doesn’t work either! The Details type parameter is not instantiated.

See the Pen TypeScript – JSON.parse class with get property by David Pine (@ievangelist) on CodePen.

See the Pen TypeScript – JSON.parse class with get property by David Pine (@ievangelist) on CodePen.

If we add a constructor to our class and then pass in a data: any argument, we could easily perform an Object.assign(this, data). This solves several issues

The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.MDN Web Docs

See the Pen TypeScript – JSON.parse class .ctor Object.assign by David Pine (@ievangelist) on CodePen.

JAVASCRIPT TO THE RESCUE!

What’s to stop a consumer from trying to interact with the details.date property – if you recall it is still typed as a Date. This is error prone and will cause issues – if not immediately, certainly later on. Ideally, all objects that are intended to map over from JSON should contain primitive types only.

If you’re set on using a class, you should use the RxJS map operator.

public getDetails(id: number): Promise<Details> {
    return this.http
               .get<Details>(`${this.baseUrl}/api/details/${id}`)
               .map(response => new Details(response.json()))
               .toPromise();
}

But what if we wanted an array of details to come back – that’s easy too?! 

public getDetails(): Promise<Details[]> {
    return this.http
               .get<Details>(`${this.baseUrl}/api/details`)
               .map(response => {
                   const data = JSON.parse(response.json()) as any[];
                   const details = array.map(data => new Details(data));
                   return details;
               })
               .toPromise();
}

TYPES THAT WORK WITHOUT INTERVENTION

This table details all the primitive types that will map over without a constructor or any other intervention.

Primitive TypesDescription
stringAlready a string anyways
numberCoercion from string to number
booleanCoercion from string to boolean
arrayAs long as all types are primitives also
tupleFollows same rules as array

 

CONCLUSION

While TypeScript and Angular play nicely together, at the end of the day we’re all battling JavaScript. As long as you’re aware of how your tool, framework, or technology works and why it works a certain way – you’re doing great! Take this bit of knowledge and share it with the world. If it helps you, hopefully it will help someone else too!

About the Author: 

David Pine is a Technical Evangelist and Microsoft MVP working at Centare in Wisconsin. David loves knowledge sharing with the technical community and speaks internationally at meetups, user groups, and technical conferences. David is passionate about sharing his thoughts through writing as well and actively maintains a blog at davidpine.net. David’s posts have been featured on ASP.NETMSDN Web-DevMSDN .NET and Dot Net Curry. David loves contributing to open-source projects and stackoverflow.com as another means of giving back to the community. David sat on the technical board and served as one of the primary organizers of Cream City Code for going on four years. When David isn’t interacting with a keyboard, you can find him spending time with his wife and their three sons, Lyric, Londyn and Lennyx. Follow David on Twitter at @davidpine7.

Reference: 

Pine, D (2018). Angular HTTP Tips for Success. Available at: https://davidpine.net/blog/angular-http-gotchas/. [Accessed 25 July 2018]

Share this on...

Rate this Post:

Share: