Consuming APIs in Angular – The Model-Adapter Pattern

Florimond Manca on September 05, 2018

Originally published at blog.florimondmanca.com on Sep 4, 2018. In a previous post, I wrote about best practices in REST API design. These were mo... [Read Full]
markdown guide
 

The http methods take an optional generic type so get<Course>(url) , this renders your adapter useless :

list(): Observable<Course[]> {
const url = "${this.baseUrl}/";
return this.http.get<Course>(url);
}

 

Interesting point. I never thought that the generic type you could pass to .get(), .post(), etc, could be used by Angular to perform the conversion. Do you have examples of that being used?

The case for adapters is also handling complex conversions, such as building nested objects and the like. For example, what if GET: /courses/ returned course objects with a students field, representing a list of Student objects? Can generic type conversion handle that?

Also, how can generic type conversion decide to build a Date object out of the created field (a string)?

Happy to discuss further. :)

 

That code snippet works like a charm i’m using it currently it been like that since angular 5 or 4 not sure, your right about the date thingy but if you send it from backend as a date type. You’ll get it as a date type too in angular.

Cool! Glad to hear simple type conversions work.

To my knowledge there is no Date type in JSON, though, so that still means the model-adapter pattern is needed in more complex use cases.

In typescript/JavaScript there is obviously, you’ll access it like a normal property Obj.dateProperty

 

The company I work for, we use a static method to construct that instead of a service:

export class Course {
  constructor(
    public id: number,
    public code: string,
    public name: string,
    public created: Date,
  ) { }

  static adapt(item: any): Course {
    return new Course(
      item.id,
      item.code,
      item.name,
      new Date(item.created),
    );
  }
}

And then we can use it as follows:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Course } from './course.model';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class CourseService {
  private apiUrl = 'http://api.myapp.com/courses';

  constructor(private readonly http: HttpClient) {}

  list(): Observable<Course[]> {
    return this.http.get<any[]>(this.apiUrl).pipe(
      // Adapt each item in the raw data array
      map(data => data.map(Course.adapt))
    );
  }
}

Do you see any of these approaches being better than another for some reason? They produce exactly the same result, but yours utilizes Angular's DI, mine does not.

 

It's a very clever alternative implementation of this pattern. I like how this approach binds the adaptation code to the model class. Also saves an import statement, and the DI boilerplate. :-) I'd definitely try this out in my next Angular projects!

 

Indeed. Map statement reads really nicely. 👌

 

I have a question, when we change the name of the field from "name" to "label", wouldn't be better if we refactor our front-end code? I mean, it's better if there's a corrispondence between front-end model and back-end model, and if for example I want to update a course sending a POST request, should I provide another adapter to reflect the changes?

 

Refactoring is an on-going process, and this kind of slight difference between the backend and the frontend is certainly worth fixing at some point. The key point here is that you don't have to refactor right now — contrary to not using an adapter, where all your code base might break. You can just make that one-line change, the code will work and the frontend team can refactor later. 👍 That's decoupling in every sense of the word.

As for the POST request, very good point! What to do when you need to adapt data "the other way around"? Well, I think it's the same idea. You can implement the reverse operation: Model instance -> API data. I generally do it on another method on the adapter, like .encode() or .serialize(). This way, we keep the adapter as a single interface between the external and internal representation (now in both directions).

 
 

Interesting approach to map the values to a typescript object. I currently use something like

class Person {
 firstname: string;
 lastname: string;
 age: number = 21;

 constructor(initialValues: Partial<Person>) {
  Object.assign(this, initialValues);
 }
}
...
 list(): Observable<Person[]> {
    const url = `${this.baseUrl}/`;
    return this.http.get(url).pipe(
      map((data: any[]) => data.map(item => new Person(item)),
    );
  }
...

to return the correct type and also be able to set default values in the class, but this doesn't decouple the code as nice as your Adapter. Might be worth a try, thank you :)

 

Thanks for sharing your approach :) I also used it in some cases when I was sure the fields wouldn't change much; but as soon as they did, I had to resort to an adapter.

(Also, I've just learnt about TypeScript Partial, thank you ✌️)

 

Hi Florimond,
this is great but can you add short description (maybe here in comment section) how to use your services in other components for the n00bs? Thanks! :)

 

Hey! You're right, I suppose it would be very useful to see how the CourseService can be used in practice to fetch and display data in a component. I'm actually writing a follow-up post for that! Will link to it here once it's published. :-)

 
 

For advanced front-end developers there is a way of generate the adapter part from openApi (swagger) specification. Even a beginner can generate angular project on page editor.swagger.io/. It can take part in developer flow so you are warned by typescript compiler if the api has changed.

 
 

The possibilities of Angular built-ins performing similar work aside, this was a great example of the practical application of sound design patterns and SOLID principles. Nice job!

 

Thanks! Really appreciated. Do you know of any Angular built-in doing this kind of thing — aside from the generic type doing automatic conversions, which was mentioned in another thread?

 

No ... I was just referring to he conversation earlier in this thread. Framework features are great and should be leveraged when available/known, but sound design practices are always applicable.

 

Good work, I'll be doing some refactoring with these ideas in mind. Thanks!

 

As far as I understood adapter doesn't really validate types, it only checks the presence of fields in the structure. You can validate actual input with something like io-ts or sarcastic. I wrote a small article about IO validation if you interested.

 

Yep, this pattern won’t validate the JSON data you receive. In fact, unless you implement error handling it will probably result in errors if the data doesn’t fit the adapter (eg providing a value that cannot be converted to a Date).
It’s simply that this pattern is only applicable if you know exactly the schema of the data you’re going to receive (that is, you built the API or have thorough documentation about its data format).
So good point for mentioning this limitation and providing resources to implement actual validation. :)

 

This is really great, I have been worrying something similar, but hadn't thought to make it a reusable interface for models.

 

Thank you for this! Very well written and easy to understand.

 

Congrats! You are getting very close the the Ember Data way of doing this:

guides.emberjs.com/release/models/

 

Imo, CourseService should receive an Adapter<Course> and not the concrete CourseAdapter.

code of conduct - report abuse