2

I am trying to transform Django response to Angulars User array. There are several reasons, such as different variable names (first_name vs firstName) and having some logic inside Angular User constructor.

Simply Django User => Angular User

Example of server response:

[
  {"id":2,"email":"test1@test.com","first_name":"Name 1","client":{"id":1}}},
  {"id":3,"email":"test2@test.com","first_name":"Name 2","client":{"id":2}}}
]

What I want is to transform to this format:

export class User {
    // contructor that transforms
    id: number;
    email: string;
    firstName: string;
    isClient: boolean = false;
}

I currently have this "solved", but I am wondering if there is a better solution for this.

Inside something.service.ts

public getClients(): Observable<User[]> {
    return this.http.get(this.getClientsUrl)
    .map(r => r.json() || []).map(x => x.map(y => new User(y)));
}

When I say better solution, while this works, it doesn't look very readable. Not a problem if you do this once, but when you have a lot of requests you start thinking of a better solution (one method to deal with this?). What would be a better way? Something like having

return this.http.get(this.getClientsUrl)
.map(transformResponse);
Roman C
  • 49,761
  • 33
  • 66
  • 176
Bojan Kogoj
  • 5,321
  • 3
  • 35
  • 57

4 Answers4

0

It should be something like

return this.http.get(this.getClientsUrl)
.map(r => this.transformResponse(r));
Roman C
  • 49,761
  • 33
  • 66
  • 176
  • Something like this, but I would like to have transformResponse implemented only once, which means it should accept class as a parameter as well. – Bojan Kogoj Jul 21 '17 at 09:02
  • It should not accept a class but it should accept response object. – Roman C Jul 21 '17 at 10:19
0

A TS interface looks like this :

interface SquareConfig {
    color: string;
    width?: number;
    enabled: boolean;
}

Do you want to convert a given object to a TS interface (really) or do you wanna make the response object more readable? That's completely different.
Because the "What I want" above example is not a TS interface.

Maxime Lafarie
  • 2,172
  • 1
  • 22
  • 41
0

If you formatted your code differently and used more clearly defined names it would be more readble e.g.

public getClients(): Observable<User[]> {
    return this.http.get(this.getClientsUrl)
        .map(response => response.json() || [])
        .map(users => users.map(user => new User(user)));
}

I've created a codepen example which logs out the server response formatted correctly.

Reading one of your comments from another answer:

Something like this, but I would like to have transformResponse implemented only once, which means it should accept class as a parameter as well.

I think that you want TypeScript generics which may look like this:

public getClients<T>(type: T): Observable<T[]> {
    return this.http.get(this.getClientsUrl)
        .map(response => response.json() || [])
        .map(clients => clients.map(client => new type(user)));
}

And if you wanted to combine Roman C's approach:

public getClients(): Observable<User[]> {
    return this.http.get(this.getClientsUrl)
        .map(response => response.json() || [])
        .map(users => users.map(user => this.mapServerResponse<User>(user, User)));
}

mapServerResponse<T>(response, type: T): T {
    return new type(response);
}
0mpurdy
  • 3,198
  • 1
  • 19
  • 28
  • I simplified most of the code, to make it easier to read on StackOverflow. But I do like your first solution, unlike the solution I came up with it has no downsides and easy to understand. – Bojan Kogoj Jul 21 '17 at 11:21
  • When trying your first solution I get `Error: Error trying to diff '[object Object]'. Only arrays and iterables are allowed`. – Bojan Kogoj Jul 21 '17 at 11:30
  • Can you replicate it on the codepen example? Or give any more information like is the server response definitely the format that you show in your answer? From [this question](https://stackoverflow.com/questions/38216857/error-trying-to-diff-object-object) it looks like it may not be returning an array from getClients(), could you log out the result and see what it is giving you? Another possibility might be are you remembering to subscribe to the observable and not just using the observable directly? – 0mpurdy Jul 21 '17 at 11:34
  • Difficult to get a working example of this. However the problem is `response.json()` returns an array of users, and not a single user, so you would need to use `.map(response => response.json() || []).map(users => users.map(user => new User(user)));` which is pretty much the solution I had in the question. – Bojan Kogoj Jul 21 '17 at 11:59
0

One solution I found (with some help of Roman C's idea) is extending Service class. This solution works for both Array and Object responses.

export class ServiceUtils {
    protected transformResponse(response: Response, myClass: any) {
        let body = response.json();

        if (body.constructor === Array) {
            return body.map(element => new myClass(element)) || []
        } 

        return new myClass(body) || new myClass();
    }
}

And using is as this:

export class SomeService extends ServiceUtils {

    constructor(private http: Http) {
        super(); // Unfortunate side effect
     }

    public getClients(): Observable<User[]> {
        return this.http.get(this.getClientsUrl)
        .map(response => this.transformResponse(response, User));
    }
}

EDIT:

An ever better solution (without extending Service):

import { Response } from '@angular/http';


export class ServiceUtils {
    public static transformResponse(response: Response, myClass: any) {
        let body = response.json();

        if (body.constructor === Array) {
            return body.map(element => new myClass(element)) || []
        } 

        return new myClass(body) || new myClass();
    }
}

And using it:

public getClients(): Observable<User[]> {
    return this.http.get(this.getClientsUrl)
    .map(r => ServiceUtils.transformResponse(r, User));
}
Roman C
  • 49,761
  • 33
  • 66
  • 176
Bojan Kogoj
  • 5,321
  • 3
  • 35
  • 57