Sasha
Sasha

Reputation: 6466

Angular VARIED, database-dependent callback after render

On my blog, I want to be able to have post-specific interactive demos. So each post has both its content and the example demo, which is HTML to be rendered to the page.

So far, no problem. I created a render_html directive:

angular.module("RenderHtml", []).directive "renderHtml", ->
  restrict: "A"
  scope:
    renderHtml: "@"
  link: (scope, element, attrs) ->
    scope.$watch "renderHtml", (newVal) ->
      element.html(newVal)

And I call it like this:

<div class='example' renderHtml='{{post.example}}'></div>

The issue is, I'd like that HTML to have embedded, executed Angular.

So the rendered example HTML would look something like this:

<div ng-controller='SpecificExampleCtrl' ng-init='initFunc()'>
  <a ng-click='someFunc()'>Etc</a>
</div>

And when the page was rendered, the SpecificExampleCtrl would be loaded, its init function run, and that ng-click run when that link was clicked.

(I've resigned myself to, if I even manage to get this to work, having to save the ng_controller in the app, but if anyone can think of a way to have that saved in the DB as well, I'd be ecstatic.)

So, at any rate, my problem seems to differ from [AngularJS: callback after render (work with DOM after render) one) and others.

And to clarify what I've been able to get done -- the HTML is rendered as HTML, but none of its Angular is run, even though the Controller being called does exist in my app.

EDIT IN RESPONSE TO SUGGESTION

angular.module("RenderHtml", []).directive ($compile) "renderHtml", ->
  restrict: "A"
  scope:
    renderHtml: "@"
  link: (scope, element, attrs) ->
    scope.$watch "renderHtml", (newVal) ->
      element.html(newVal)
      $compile(eval(element))

(The above doesn't work as I've written it. It renders the HTML, but doesn't evaluate the angular at all.)

EDIT It looks like I should be using $eval instead of the vanilla eval, but when I try to inject that into the directive or call it without injecting, the site errors, and when I inject and use $parse, which looks like it does similar things, nothing in the entire angular template renders, and I get no errors.

ANSWER

This ended up working:

angular.module("RenderHtml", []).directive "renderHtml", ($compile) ->
  restrict: "A"
  scope:
    renderHtml: "@"
  link: (scope, element, attrs) ->
    scope.$watch "renderHtml", (newVal) ->
      linkFunc = $compile(newVal)
      element.html(linkFunc(scope))

Compiling html returns a function that an argument of the scope.

Upvotes: 0

Views: 129

Answers (1)

Jason Goemaat
Jason Goemaat

Reputation: 29214

You can use 'eval' to execute javascript you get from the database to add the controller. Not angular's $eval which evaluates according to a scope, but the vanilla javascript eval which will compile your code. This is not very secure, if there's any chance of user input into the js you probably don't want to do it since it'll be executed in the context of the user on your site. The f at the end of the string returns the function as an object as the result of eval().

eval(response.controllerJavascript);

Then you need to $compile your html. I based my fiddle on this example. Finally you use $injector to call your controller function on your scope.

Directive:

module.directive('compile', function($compile, $injector) {
  var obj = {
    scope: true, // child scope
    link: function(scope, element, attrs) {
      // 1st function returns value, if changed call 2nd
      scope.$watch(
        function(scope) {
          return scope.$eval(attrs.compile);
        },
        function(value) {
          element.html(value);
          $compile(element.contents())(scope);
        }
      );

      scope.$watch(function(scope) {
        return scope.$eval(attrs.compileCode);
      }, function(value) {
        // get 'function' object
        var controller = eval(value);
        if (typeof(controller) == "function") {
          // invoke controller on our child scope
          $injector.invoke(controller, this, { $scope: scope });
        }
      });
    }
  };
  return obj;
});

HTML:

<div ng-app="TestApp" ng-controller="Ctrl" id="divCtrl">
  <label>Name:</label>
  <input ng-model="name"> <br/>
  <label>Html:</label>
  <textarea ng-model="html"></textarea> <br/>
  <label>Js:</label> <textarea ng-model="js"></textarea> <br/>   

  <div compile="html" compile-code="js">Hi {{name}}</div>

  <input type="button" value="Simulate AJAX" ng-click="simulateAjax()">
</div>

Controller:

module.controller("Ctrl", function($scope) {
  $scope.name = 'Angular';
  var code = 'var f = function($scope) { $scope.name = "Ctrl2"; }\r\nf';
  $scope.simulateAjax = function() {
    $scope.html = '<div>Hello {{name}}</div>';
    $scope.js = code;
    code = 'var f = function($scope) { $scope.name = "Ctrl2-next"; }\r\nf';
  };
});

Upvotes: 1

Related Questions