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.