3

I need to render dynamic template from database and also need to bind the variables to expressions.

My response JSON will look like this,

[{
    "htmlTemplate": "<div>{{name}}</div><div>{{age}}</div>",
    "bindData": {
        "name": "safeer",
        "age" : "25"
    }
}, {
    "htmlTemplate": "<span>{{name}}</span><div>{{address}}</div>",
    "bindData": {
        "name": "john",
        "address":"qwerty"
    }
}, {
    "htmlTemplate": "<h4>{{name}}</h4><h2>{{country}}</h2>",
    "bindData": {
        "name": "james",
        "country":"India",
        "state" : "Kerala"
    }
}]

I have created a directive as per the answer to the question Compiling dynamic HTML strings from database

In html, demo.html

<div dynamic="html"></div>

In directive, directive.js

var app = angular.module('app', []);

app.directive('dynamic', function ($compile) {
  return {
    restrict: 'A',
    replace: true,
    link: function (scope, ele, attrs) {
      scope.$watch(attrs.dynamic, function(html) {
        ele.html(html);
        $compile(ele.contents())(scope);
      });
    }
  };
});

It will render html template and replace variable with $scope variable name finely.

But I need to render each htmlTemplate with its corresponding bindData. That is render each template with isolated scope data. Need to generalize directive.

Any help appreciated! Thanks in advance.

Community
  • 1
  • 1
Mohammed Safeer
  • 20,751
  • 8
  • 75
  • 78

2 Answers2

2

When you receive the JSON data you can use angular.fromJSON to decode the json string into the array (unless you are using the $http.get() which already does that for you)...

//request the JSON from server
jsonString = SomeFactory.fetchDataFromServer();
$scope.dataArray = angular.fromJson(jsonString);

...and then use the ngRepeat to create multiple elements:

<div ng-repeat="element in dataArray" dynamic="element"></div>

Modify your directive like this:

  app.directive('dynamic', function ($compile) {
  return {
    restrict: 'A',
    replace: true,
    link: function (scope, ele, attrs) {
      scope.bindData = {};
      scope.$watch(attrs.dynamic, function(dynamic) {
         console.log('Watch called');
        ele.html(dynamic.htmlTemplate); //here's your htmlTemplate being compiled
        $compile(ele.contents())(scope);
        scope.bindData = dynamic.bindData; //here's your bound data ifyou need the reference
        scope.name = dynamic.bindData.name; //bound data property name
      }, true); //deep watch
    }
  };
  });

Edit: Or you could simply pass element.htmlTemplate and element.bindData separately to the directive through two separate attributes as mentioned in an answer by user Vineet, which makes more sense. Edit2: Fixed some bugs.

Here's the fiddle: Fiddle

  • Made JSFiddle work for template mentioned in question. I have to modify the template html a little Here is the updated fiddle http://jsfiddle.net/Lvc0u55v/325/ – Mohammed Safeer Feb 25 '16 at 17:27
1

With generalising I assume you want to make it like a component and resuse it whenever you want.

I would suggest to separate the scope of this directive. Bind your "html" and "name" both of them from parent.

AFTER EDITING:

app.directive('dynamic', function ($compile) {
 return {
   restrict: 'A',
   replace: true,
   scope : {
     html : "=dynamic"
   },
   link: function (scope, ele, attrs) {
     ele.html(scope.html);
     $compile(ele.contents())(scope);
     var unbindWatcher = $scope.$watch(
         attrs.bindData,
         function(binddata) {
             if ( binddata ) {
                angular.extend(scope,binddata);
                // Once the data has been binded to scope,
                // there's no more need to watch the change
                // in the model value.
                unbindWatcher();
              }
         }
     );
   }
 };
});

And your html as:

<div dynamic="html" bindData="bindData"></div>

In this edit, what I have done is following 3 things:

1) Watching your attribute bindData - which will contain your db stored bind data. I have not included this in scope of directive because I want to include its properties in the scope so that you can bind your 'name', etc. from bindData in db to the templates.

2)Extending the bindData object into scope of your directive. Now your scope will have 'name', etc.

3)Destroying the watcher as soon as bindData is read for first time. This ensures that any change in bindData variable inside parent will not be conveyed to directive after first bind.

But still keep the scope separated to make it work properly.

Hope this will solve your problem

Vineet 'DEVIN' Dev
  • 1,183
  • 1
  • 10
  • 35
  • Thanks for your answer. bindData is an object it will contain more objects, in JSON response. And html template is also changing – Mohammed Safeer Feb 24 '16 at 17:39