Angular - getting ng-bind to work with nested directives

I built a small template that recursively creates different sub-directives based on the model. I use $compile

to recursively create child directives and then add them to the parent. Apparently the directive building works pretty well, but for some reason inline expressions or ng-bind

or interpolation doesn't work on a nested directive.

Here's a snippet:

app.directive("child", function ($compile) {
  function getTemplate(depth) {
    if (depth % 2 == 0) {
      return "<even depth='deeper'/>"
    } else {
      return "<odd depth='deeper'/>"
    }
  }
  return {
    scope: {
      depth: "="
    },
    link: function linker($scope, $element) {
      if ($scope.depth == 0) {
        var child = angular.element("<span ng-bind='depth'/>");
        child = $compile(child)($scope);
        $element.append(child);
      } else {
        $scope.deeper = $scope.depth - 1;
        var child = angular.element(getTemplate($scope.depth));
        child = $compile(child)($scope);
        $element.append(child);
      }
    }
  }
})

      

Basically in this test, the directive will recursively dive down until depth

it reaches 0

and then spit out the element <span>

.
The expected result should be a span element with a value 0

. But that doesn't seem to be evaluating. Usage <span>{{depth}}</span>

also results in literal html instead of content evaluation.
I am trying to get the result of nested district directive <even><odd><even>

delete directives <child>

.

Here's the full jsFiddle: https://jsfiddle.net/eg1e1aLz/

The final DOM should look like this:

<test depth="4" class="ng-isolate-scope">
  <even depth="depth-1" class="ng-scope ng-isolate-scope">
    <odd depth="depth-1" class="ng-scope ng-isolate-scope">
      <even depth="depth-1" class="ng-scope ng-isolate-scope">
        <odd depth="depth-1" class="ng-scope ng-isolate-scope"><span ng-bind="depth" class="ng-binding ng-scope">0</span></odd>
      </even>
    </odd>
  </even>
</test>
      

Run codeHide result


+3


source to share


6 answers


Based on your comments below and updates to the question, how about the next ...

It uses a template function getTemplate

to create structure and identical directives: odd

and even

as placeholders to create functionality.

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

app.directive("test", function($compile) {
  return {
    scope: {
      depth: "="
    },
    link: function linker($scope, $element, $attrs) {

      // template accessible to the child directives
      $scope.getTemplate = function(depth) {
        if (depth <= 0) {
          return "<span ng-bind='depth'/>"; // also bindings like {{depth}} work
        } else if (depth % 2 === 0) {
          return "<even depth='depth-1'></even>"; // bindings like {{depth}} work
        } else {
          return "<odd depth='depth-1'></odd>";
        }
      }

      var child = angular.element($scope.getTemplate($scope.depth));
      $compile(child)($scope);
      $element.append(child);
    }
  }
});
app.directive("odd", function($compile) {
  return {
    scope: {
      depth: "="
    },
    link: function linker($scope, $element) {
      $scope.getTemplate = $scope.$parent.getTemplate; // bring template into current scope
      var child = angular.element($scope.getTemplate($scope.depth));
      $compile(child)($scope);
      $element.append(child);
    }
  }
})
app.directive("even", function($compile) {
  return {
    scope: {
      depth: "=",
    },
    link: function linker($scope, $element) {
      $scope.getTemplate = $scope.$parent.getTemplate; // bring template into current scope
      var child = angular.element($scope.getTemplate($scope.depth));
      $compile(child)($scope);
      $element.append(child);
    }
  }
})

var controller = app.controller("controller", function($scope) {});

      



Updated script: https://jsfiddle.net/bda411fj/15/

Result:

<test depth="4" class="ng-isolate-scope">
  <even depth="depth-1" class="ng-binding ng-scope ng-isolate-scope">
    <odd depth="depth-1" class="ng-binding ng-scope ng-isolate-scope">
      <even depth="depth-1" class="ng-binding ng-scope ng-isolate-scope">
        <odd depth="depth-1" class="ng-binding ng-scope ng-isolate-scope">
          <span ng-bind="depth" class="ng-binding ng-scope">0</span>
        </odd>
      </even>
    </odd>
  </even>
</test>

      

+3


source


The compiled part of your template should only be listed as a child of only child.html()

.
Change your code 3 places from $element.append(child.html());

to$element.append(child);



Printing of the depth value you are looking for will start. What else are you looking for?

+1


source


Your html might not be sanitized, why won't it compile. Try using ngSanatize ti to fix your problem. https://docs.angularjs.org/api/ngSanitize/service/ $ sanitize , it will remove all potentially dangerous markers and give you correct html back.

0


source


Since you don't want to use html (), don't use child.html ().

Use the child himself.

Check the documentation here

Check the snippet below. Added based on the sample snippet in your question

var app = angular.module("app", []);
app.directive("test", function($compile) {
  return {
    scope: {
      depth: "="
    },
    link: function linker($scope, $element, $attrs) {
      var child = angular.element("<child depth='depth'/>");
      child = $compile(child)($scope);
      $element.append(child);
    }
  }
});
app.directive("child", function($compile) {
  function getTemplate(depth) {
      return depth % 2 == 0 ? "<even depth='depth-1'/>" : "<odd depth='depth-1'/>"
  }
  return {
    scope: {
      depth: "="
    },
    link: function linker($scope, $element) {
      if ($scope.depth == 0) {
        var child = angular.element("<span ng-bind='depth'/>");
        child = $compile(child)($scope);
        $element.append(child);
      } else {
        var child = angular.element(getTemplate($scope.depth));
        child = $compile(child)($scope);
        $element.append(child);
      }
    }
  }
})
app.directive("odd", function($compile) {
  return {
    scope: {
      depth: "="
    },
    link: function linker($scope, $element) {
      var child = angular.element("<child depth='depth'/>");
      child = $compile(child)($scope);
      $element.append(child);
    }
  }
})
app.directive("even", function($compile) {
  return {
    scope: {
      depth: "="
    },
    link: function linker($scope, $element) {
      var child = angular.element("<child depth='depth'/>");
      child = $compile(child)($scope);
      $element.append(child);
    }
  }
})
var controller = app.controller("controller", function($scope) {});
      

body {
  padding: 5px 5px 5px 5px !important;
  font-size: 30px;
}

test,
child,
even,
odd {
  padding: 5px 5px 5px 5px;
  margin: 5px 5px 5px 5px;
  border: 1px solid black;
}

test {
  background-color: aliceblue !important;
}

child {
  background-color: beige !important;
}

even {
  background-color: lightgreen !important;
}

odd {
  background-color: lightcoral !important;
}
      

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.min.js"></script>
<body ng-app="app" ng-controller="controller">
  <test depth="4"></test>
</body>
      

Run codeHide result


0


source


Your jsfiddle works great, the only problem is a little logic.

When you compile a child directive, you shouldn't add child.html()

simply because it will add HTML with no bindings and observers associated with them. Instead, you should add the entire child

https://jsfiddle.net/eg1e1aLz/2/

child = $compile(child)($scope);
$element.append(child);

      

0


source


Sorry if I'm missing the context of what you are trying to accomplish, but I don't think you need to use helper directives or anything like that. You can just use the test directive and create children all the time, and then bind the scope to the latter:

angular.module("app", [])
    .directive("test", function ($compile) {
        return {
            scope: {
                depth: "="
            },
            link: function linker($scope, $element, $attrs) {
                var element = $element;
                for (var x = 0; x < $scope.depth; x++) {
                    var elementType = x % 2 === 0 ? 'even' : 'odd';
                    var subElement = angular.element(document.createElement(elementType));
                    element.append(subElement);
                    element = subElement;
                }
                var span = angular.element('<span ng-bind="depth" />');
                element.append(span);
                $compile(span)($scope);
            }
        }
    })

      

Fiddle: https://jsfiddle.net/ffyv0zmy/

I understand that in your example you want the output to be "0" for depth, but you can hardcode it as I'm not sure about that.

HTML result:

<test depth="4" class="ng-isolate-scope">
    <even>
        <odd>
            <even>
                <odd>
                    <span ng-bind="depth" class="ng-binding ng-scope">4</span>
                </odd>
            </even>
        </odd>
    </even>
</test>

      

The best part about this is you don't have to attach the region to even / odd elements unless you want to.

You can still attach directives to each of the odd / even elements, but then you have to compile $ element instead of $ to attach all scopes.

0


source







All Articles