0

I have the code shown below. When I try to set data returned from a service (the assetGroup variable) the value being set is null. My best bet is it is because not all nested arrays are typed, and needs to be mapped. So my question is two fold, is this the reason why the object continue being null after I set it? And if yes can you somehow do an assign that makes a deep mapping or whats the best approach here?

export class Asset {
    constructor(obj: any) {
        Object.assign({}, obj);
    }
    assetId:number;
    assetSubGroupId:number;
    code: number;
    name: string;
}

export class AssetGroup {

    constructor(obj: any) {
        Object.assign(this, obj);
     }
    assetGroupId:number;
    code: number;
    name: string;
    assetSubGroupList:Asset[];   
}

export class AssetSubGroup {

    constructor(obj: any) {
        Object.assign(this, obj);
     }
    assetSubGroupId:number;
    assetGroupId:number;
    code: number;
    name: string;
    assetList: Asset[];   
}

This is my service:

getAssetGroup(id:number):Observable<AssetGroup>{
    var url = `http://xxx.azurewebsites.net/api/assetgroups/${id}`;
    return  this.httpClient.get<AssetGroup>(url).pipe(map(x => new AssetGroup(x)));    
  }

My component:

private  assetGroup:  AssetGroup;

loadAssets(id)
{   
  this.apiService.getAssetGroup(id).subscribe((data) => {
    this.assetGroup  =  data;      
    console.log(this.assetGroup);  // <---- assetGroup is NULL?
  });
}

Debugger: enter image description here

Thomas Segato
  • 4,567
  • 11
  • 55
  • 104
  • to do a deep merge use one of the lodash functions discussed here - https://stackoverflow.com/questions/19965844/lodash-difference-between-extend-assign-and-merge - deep merge is not straightforward in vanilla JS – danday74 Sep 26 '18 at 08:30

1 Answers1

1

Yes you need to explicitly map the response to your classes. The generics in Typescript just work to ensure that you do not try to access any properties that do not exist, but HttpClient does not create instances of the classes for you. For more also check this discussion on github: https://github.com/angular/angular/issues/20770

In order for you to work with this, you have two options:

  • for now your classes can just be transferred into interfaces as they do not have any functionality but only describe a contract which properties have to exist. So your example is a perfect scenario for actually just using interfaces. For more on this check this link: https://medium.com/front-end-hacking/typescript-class-vs-interface-99c0ae1c2136
  • if you actually do need the types to be classes you will have to explicitly create instances of them.

You will need to write something like this:

export class AssetGroup {

    constructor(obj: any) {
        assetGroupId = obj.assetGroupId
        code = obj.code;
        name = obj.name;
        assetSubGroupList = any.assetSubGroupList.map(
            asset => new Asset(asset)
        );
    }

    assetGroupId:number;
    code: number;
    name: string;
    assetSubGroupList:Asset[];   

}

Your code does not work, because Object.assign does not return an Object type Asset as it merges an Object of Asset with an Object of type any and thus cannot know what the output type can be described as.

tommueller
  • 2,358
  • 2
  • 32
  • 44
  • If going with interfaces, do I then skip the mapping part in my service? Right now I use pipe and map, and add the object to the constructor for mapping, how do you do that with an interface with no constructor? – Thomas Segato Sep 26 '18 at 08:48
  • I just tried changing the classes to interfaces and removed constructor and thereby mapping. Then an Object is returned and assetGroup still remains null. – Thomas Segato Sep 26 '18 at 08:52
  • I cannot quite follow what exactly in your code returns null. AssetGroup is the top level object, no? – tommueller Sep 26 '18 at 09:19
  • Yes its the top level. – Thomas Segato Sep 26 '18 at 09:21
  • So what is the output of the endpoint and what is null after you try mapping it? – tommueller Sep 26 '18 at 09:22
  • In my loasAsset function in my asset component. Setting this.assetGroup=data. assetGroup is null even if data has data. – Thomas Segato Sep 26 '18 at 10:09
  • You can see it all in my main post. There is a screenshot of the debugger. – Thomas Segato Sep 26 '18 at 11:44
  • Also notice that the top object is actually as expected, its just the child and child child arrays. – Thomas Segato Sep 26 '18 at 11:59
  • for me everything looks fine in your screenshot. it is an array with 13 objects?! the only thing you will probably need to change is `this.assetGroup = data` to new `this.assetGroup = new AssetGroup(data)` or just use interfaces ... – tommueller Sep 26 '18 at 12:59
  • Just missing it all to be typed and not Object. But I key take away if I want nested strong typing it more compplex. So I stick with not everything being typed. Thanks. – Thomas Segato Sep 26 '18 at 15:26
  • if you want to have the subtype also typed, you will need to also map it explicitly. see my code above, I changed it a little bit. – tommueller Sep 27 '18 at 16:07