Parse JSON to Typescript class in Angular app
Asked Answered
J

1

7

I'm creating a app usign angular and typescript. Everything is comming together nicely, but one issue bugs me.

I define entity/model classes that i would like to pass around in the app, the data for the classes comes from JSON from $resource calls.

Here is an example of a model class:

module app.domain {

    export interface IJob {
        id: number;
        jobTitle: string;
        jobDescription: string;
    }

    export class Job implements IJob {

       constructor(public id:number, public jobTitle: string, public jobDescription: string) {

       }
    }
}

I access my JSON resource through a service that returns the resource:

namespace app.services {
    'use strict';

    interface IDataService {
        getJobResource(): angular.resource.IResourceClass<IJobResource>
    }

    interface IJobResource extends angular.resource.IResource<app.domain.IJob>{

    }

    export class DataService implements IDataService {

        static $inject: Array<string> = ['$log','$resource','ApiDataEndpoint'];
        constructor(
                    private $log: angular.ILogService,
                    private $resource: angular.resource.IResourceService,
                    private ApiDataEndpoint          
            ){}

        getJobResource(): angular.resource.IResourceClass<IJobResource>{
            return this.$resource(this.ApiDataEndpoint.url + 'job/:id',{});
        }
    }

    angular
        .module('app.services',[])
        .service('dataService', DataService);
}

This is where it goes wrong, when I cast the result of the resource call to Ijob typescript restrict me to call properties that don't match the names in the interface (as expected) but because this is still just JSON I'm unable to make method calls and if the properties en the IJob interface do not match the names in the JSON result the I get nothing.

So, my questing is: What is the correct way to implement a service that calls a restfull endpoint that returns JSON and then returns an array of objects or a single object. The way should support that names on JSON and on the objects might not match.

Jilly answered 19/1, 2016 at 10:40 Comment(3)
You could make a decorator class that takes the interface as one of the constructor arguments. Then you will be able to do new Job(data).Middelburg
@Middelburg interesting, could you elaborate on this answer, maybe show a little example?Jilly
I added the response, I hope this fits your problem. :)Middelburg
M
2

You could make a class that has the functionality you need and gets a IJob object to work with.

module app.domain {

    export interface IJob {
        id: number;
        jobTitle: string;
        jobDescription: string;
    }

    export class Job {
       // keep the data received from the server private
       constructor(private jobData: IJob) {}

       // class methods
       public isIdGreaterThanTen(): boolean {
         return this.jobData.id > 0;
       }

       // expose the properties of the IJob interface via getters and setters
       public get id(): number { 
         return this.jobData.id;
       }

       public set id(id: number): void {
         this.jobData.id = id;
       }

       // so on for all properties if needed
       // ...

    }
}

In the service you can use the transformResponse functionality of $resource to create and return a new instance of the Job class instead of the object returned by the server.

namespace app.services {
    'use strict';

    interface IDataService {
        getJobResource(): angular.resource.IResourceClass<IJobResource>
    }

    // use the Job class instead of the interface
    interface IJobResource extends angular.resource.IResource<app.domain.Job>{

    }

    export class DataService implements IDataService {

        static $inject: Array<string> = ['$log','$resource','ApiDataEndpoint'];
        constructor(
                    private $log: angular.ILogService,
                    private $resource: angular.resource.IResourceService,
                    private ApiDataEndpoint          
            ){}

        getJobResource(): angular.resource.IResourceClass<IJobResource>{
            return this.$resource(this.ApiDataEndpoint.url + 'job/:id',{},
            {
              get: {
                method: 'GET',
                transformResponse: function(data: IJob) {
                  return new Job(data); // create a new instance of the Job class and return it
                }
              }
            });
        }
    }

    angular
        .module('app.services',[])
        .service('dataService', DataService);
}
Middelburg answered 20/1, 2016 at 15:31 Comment(1)
This looks very interesting. I will try it out tomorrow.Jilly

© 2022 - 2024 — McMap. All rights reserved.