1

I am trying to transform Django response to Angular's 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":"[email protected]","first_name":"Name 1","client":{"id":1}}},
  {"id":3,"email":"[email protected]","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);
1
  • is there any reason that you want to define this through a class instead of an interface? Commented Jul 21, 2017 at 9:44

4 Answers 4

0

It should be something like

return this.http.get(this.getClientsUrl)
.map(r => this.transformResponse(r));
Sign up to request clarification or add additional context in comments.

2 Comments

Something like this, but I would like to have transformResponse implemented only once, which means it should accept class as a parameter as well.
It should not accept a class but it should accept response object.
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.

2 Comments

I've changed title a bit
I think it comes to the use case: a class can have more functionality (methods), where an interface define only a set of properties
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);
}

4 Comments

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.
When trying your first solution I get Error: Error trying to diff '[object Object]'. Only arrays and iterables are allowed.
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 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?
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.
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));
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.