Angular expression selecting directive

I need to display an angular directive by selecting it by referring to a line previously defined in a variable (usually declared in a controller). Even though a variable like this is available as an angular expression, when I try to use to select a directive it doesn't work:

<!DOCTYPE html>
<html ng-app="app">
<body ng-controller="TextController">

<!-- item.dir is accessible: -->
<div>Value of item: {{item.dir}}</div>

<!-- It works. the directive "hello" is rendered -->
<div class="hello"></div>
<hello></hello>

Here you should see additional text:
<!-- Doesn't work item.dir is not recognized-->
<!-- as a class -->
<div class="{{item.dir}}"></div>

<!-- as an attribute-->
<div {{item.dir}}></div>

<!-- trying ng-class (it fails)-->
<div ng-class="item.dir"></div>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.5/angular.min.js"></script>

<script>

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

  // The directive to render
  appModule.directive('hello', function() {
        return {
          restrict: 'ACE',
          template: '<div>works: Transcoded text</div>',
          replace: true
        };
      });

  appModule.controller('TextController', function ($scope) {
    $scope.item = {dir: 'hello'}; // the name of the directive, the idea is to use it for referring to many future directives.
  });
</script>
</body>
</html>

      

Here's the code plunker: http://plnkr.co/edit/tM73sY3vTfPFHmn6iCYE?p=preview

So what am I missing? How to achieve angular result using string interpolation when using directive? Thank!

+3


source to share


2 answers


For directives for Angular to work, your html needs to be compiled (something is automatically executed on page load).

Having a way to freely control which directive to instantiate is a bit like pulling the carpet under your feet and is atypical. One problem is that the compilation "destroys" the internal bindings / observers and some of the original DOM and therefore not enough information to "recompile" the DOM node.

NOTE. You cannot change attribute or element names (attribute values ​​only) with Angular using this type of binding: {{}} But ng- class = "..." and class = "{{...}}".

I don't understand what you are trying to achieve exactly. If the intent is indeed to change the value of item.dir and Angular to "reconfigure" your application, it is "possible", but I highly suspect it will cause "government" flaws.

However, there is a "hack" at work that "remembers" the original DOM html and recompiles it when needed. This is done in two stages of compilation: the first phase is to restore the original bindings and the second phase, which is run after the $ digest loop, so that the original bindings will finish filling in the class name from the scope (i.e. have an item.dir effect). The downside, of course, is that if you make changes to the attached DOM, it will destroy them! Alternatively, it might be possible to only remember certain attributes and return "this" only while keeping the other part of the DOM intact (but could create other problems).

  appModule.directive('forceRecompilation', ['$timeout', '$compile', function($timeout, $compile) {
    return {
      restrict: 'A',
      link: function(scope, element, attr) {

        var originalHtml = element.html();

        scope.$watch(attr.forceRecompilation, function(){
            // restore original HTML and compile that
            element.html(originalHtml);
            $compile(element.contents())(scope);

            // wait for all digest cycles to be finished to allow for "binding" to occur
            $timeout(function(){
              // recompile with bounded values
              $compile(element.contents())(scope);
            });
          });
      }
    };
  }]);

      

... to be used as the closing tag of the DOM section to act on. It will "return and recompile" everything under it when the expression changes. (here "item.dir"):



<div force-recompilation="item.dir">
    <div class="{{item.dir}}">
</div>

      

Plunker: http://plnkr.co/edit/TcMhzFpErncbHSG6GgZp?p=preview

The plunker contains 2 directives "hello" and "hello2". Change the text to "hello" and go back to "hello2" to see the effect.

EDIT: Below is a directive that allows you to embed markup for compilation as described in the comment below. This is just a slightly modified version of Angularjs - inline directives with ng-bind-html-unsafe

  angular.module('bindHtmlExample', ['ngSanitize'])
    .controller('ExampleController', ['$scope',
      function($scope) {

        $scope.test = false;
        $scope.dir = "ng-click";
        $scope.clicked = function() {
          $scope.test = !$scope.test
        }

        $scope.myHTML =
          'I am an <b ng-show="test">invisible</b> HTML string with ' +
          '<a href="#" ' + $scope.dir + '="clicked()">links!</a> and other <em>stuff</em>';
      }
    ])

  // modified plunker taken from /questions/2169518/angularjs-inline-directives-with-ng-bind-html-unsafe
  //
  // Allows an attribute value to be evaluated and compiled against the scope, resulting
  // in an angularized template being injected in its place.
  //
  // Note: This directive is prefixed with "unsafe" because it does not sanitize the HTML. It is up
  // to the developer to ensure that the HTML is safe to insert into the DOM.
  //
  // Usage:
  //     HTML: <div unsafe-bind-html="templateHtml"></div>
  //     JS: $scope.templateHtml = '<a ng-onclick="doSomething()">Click me!</a>';
  //     Result: DIV will contain an anchor that will call $scope.doSomething() when clicked.
  .directive('unsafeBindHtml', ['$compile',
    function($compile) {
      return function(scope, element, attrs) {
        scope.$watch(
          function(scope) {
            // watch the 'compile' expression for changes
            return scope.$eval(attrs.unsafeBindHtml);
          },
          function(value) {
            // when the 'compile' expression changes
            // assign it into the current DOM element
            element.html(value);

            // compile the new DOM and link it to the current
            // scope.
            // NOTE: we only compile .childNodes so that
            // we don't get into infinite loop compiling ourselves
            $compile(element.contents())(scope);
          }
        );
      };
    }
  ]);
      

<!doctype html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Example</title>


  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.5/angular.min.js"></script>
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.5/angular-sanitize.js"></script>
  <script src="script.js"></script>



</head>

<body ng-app="bindHtmlExample">
  <div ng-controller="ExampleController">
    <p unsafe-bind-html="myHTML"></p>

    (click on the link to see <code>ng-click</code> in action)
  </div>
</body>

</html>
      

Run codeHide result


+2


source


Apparently tg-dynamic-directive does the trick.



0


source







All Articles