1

Based on How can I use/create dynamic template to compile dynamic Component with Angular 2.0 I'm currently facing a timing problem when it comes to dynamic property creation of a component model from a json string that is retrieved from a remote server.

Properties are dynamically created properly within in a new entity - that is later assigned to the empty entity, but the hosting component has been already bound to the empty entity and does not know anything about the dynamically created properties at initialization time. It seems that the creation of the hosting component is already finished when the json result from server arrives asynchronously and a bit later.

As a result a list of empty components without any property data is applied to the page. The number of displayed components is equal to the dynamically added properties. But the dynamic data binding obviously does not know anything about the data attached to the properties.

The forementioned original approach loops through a set of hard wired properties of an entity object to assemble the complete template for each property using the specific propertyname.

Defining a hard wired entity object:

entity = { 
    code: "ABC123",
    description: "A description of this Entity" 
};

Assembling a dynamic template string looping through properties of entity:

import {Injectable} from "@angular/core";

@Injectable()
export class DynamicTemplateBuilder {

    public prepareTemplate(entity: any, useTextarea: boolean){

      let properties = Object.keys(entity);
      let template = "<form >";
      let editorName = useTextarea 
        ? "text-editor"
        : "string-editor";

      properties.forEach((propertyName) =>{
        template += `
          <${editorName}
              [propertyName]="'${propertyName}'"
              [entity]="entity"
          ></${editorName}>`;
      });

      return template + "</form>";
    }
}

My approach tries to deal with a full configurable approach:

Json string

{
  "code": {
      "id": "ADRESSE-UPDATE-001",
      "componentType": "text-editor",
      "rows": 5,
      "cols": 25,
      "value": "Hello !"
  },
  "description": {
      "id": "ADRESSE-UPDATE-002",
      "componentType": "string-editor",
      "rows": 1,
      "cols": 50,
      "value": "Please enter some data"
  },
  "Street": {
      "id": "ADRESSE-UPDATE-003",
      "componentType": "text-editor",
      "rows": 10,
      "cols": 100,
      "value": "Mainstreet"
  },
  "City": {
      "id": "ADRESSE-UPDATE-004",
      "componentType": "text-editor",
      "rows": 3,
      "cols": 5,
      "value": "Hamburg"
  }
}

Basically the Json retrieval and property creation is organized as:

import { Injectable } from '@angular/core'; 
import {Http, Response} from "@angular/http";
import { Headers, RequestOptions } from '@angular/http';
import {IValue, IEntityClassAdvanced, Member} from './dynamic-config-interfaces';

var jsonX; 

     @Injectable() 
     export class DynamicConfigBuilder{ 

        constructor(private http: Http)
        {
        }

         getEntity(callback):any{

                this.callConfigApi(callback);

         }


        callConfigApi(callback:any) {

            jsonX = null;

            var content = { "Action": "REQUEST-FOR-CONFIG", "Type": 'CONFIG', "Raw":""};

            var jsonText = JSON.stringify(content);
            let headers = new Headers({ 'Content-Type': 'application/json' });
            let options = new RequestOptions({ headers: headers });

            this.http.post('http://localhost:9123/tasks/config',jsonText, options)
                .map((res: Response) => res.json())
                .subscribe((message: string) => { 
                    jsonX = JSON.parse(message);
                    console.log(JSON.stringify(jsonX));
                    //alert("loaded from server: " + JSON.stringify(jsonX));

                    var entity: IEntityClassAdvanced = {};
                    Object.keys(jsonX).forEach(function (key) {
                        entity[key] = { propertyValue: null };
                        var val = entity[key];
                        val.propertyValue = new Member().deserialize(jsonX[key]);
                    });
                    callback(entity);
                 });


        }

     } 

Necessary Interfaces and types are located in a separate file:

import { Injectable} from '@angular/core';

//--- typed nested object from json:
export interface Serializable<T> {
    deserialize(input: Object): T;
}

@Injectable()
export class Member implements Serializable<Member> {
    id: string;
    componentType: string;
    rows: number;
    cols:number;
    value:string;

    deserialize(input) {
        this.id = input.id;
        this.componentType = input.componentType;
        this.rows = input.rows;
        this.cols = input.cols;
        this.value = input.value;

        return this;
    }
}


//--- dynamic object builder elements
export interface IValue {
    propertyValue: Member
}

export interface IEntityClassAdvanced {
    [name: string]: IValue;
}
//--- end dynamic object builder

Within the callback method the late assignment occurs:

private entityCallback(entityItem:any)
{
    //alert("--->" + JSON.stringify(entityItem));
    this.entity = entityItem; 
    this.refreshContent(); 
}

The template creation comes with a slightly different flavour:

public prepareTemplate(entity:any, useTextarea: boolean){


      let properties = Object.keys(entity);
      let template = "<form >";
      let editorName = useTextarea 
        ? "otago-text-editor"
        : "string-editor";

      properties.forEach((propertyName) =>{
// alert(propertyName);
        let targetComponent = entity[propertyName]["propertyValue"]["componentType"];

        template += `
          <${targetComponent}
              [propertyName]="'${propertyName}'"
              [entity]="entity"
          ></${targetComponent}>`;
      });

      return template + "</form>";
    }

When trying a hard wired approach using an nested inline object that already exists at compile time the binding works fine:

var json = {
    code:  {
        id: "ADRESSE-UPDATE-001",
        componentType: "text-editor",
        rows:5,
        cols:25,
        value:"Hello !"
    },
    description:  {
        id: "ADRESSE-UPDATE-002",
        componentType: "string-editor",
        rows:1,
        cols:50,
        value:"Please enter some data"
    },
    firstMember: {
        id: "ADRESSE-UPDATE-003",
        componentType: "text-editor",
        rows:10,
        cols:100,
        value:"Mainstreet"
    },
    secondMember: {
        id: "ADRESSE-UPDATE-004",
        componentType: "text-editor",
        rows:3,
        cols:5,
        value:"Hamburg"
    }
};

Any suggestions concerning this generic component creation approach/timing problem appreciated.

Community
  • 1
  • 1
Prodev Muc
  • 19
  • 2
  • I haven't looked into your code but why don't you wait until the async data is retrieved to create the dynamic component? – AngularChef Feb 21 '17 at 18:00
  • To me there is a magic automatic Angular2 life cycle going on: Neither hooking into ng Event handler onNgInit, ngAfterViewInit, ngOnChanges nor injecting a preprocessing service does the job. In contradiction to wellknown pure data binding Angular2 obviously comes into trouble when changing resp. re-assigning component properties that better should be untouched during runtime. Concerning the service injecting approach I hoped that there is a proper instantiation order: At first calling the service constructor and then the component constructor to ensure the properties are in proper position. – Prodev Muc Feb 21 '17 at 18:45

0 Answers0