2

I am working on my first single page Angular.js application and am kind of stuck at programmatically compilling/evaluating a custom directive in order to insert it into the DOM from within a controller. The custom directive I created uses two values (which return functions and take a parameter) and a parameter. The whole thing works fine with ng-repeat in the initial html of the SPA:

.directive('myDirective', ['value1', 'value2', function('value1', 'value2'){
    return {
        restrict: 'E',
        scope: {
            param: '=param'
        },
        replace: true,
        templateUrl: '/path/to/template.html',
        link: function(scope, element, attrs){
            scope.v1 = value1(scope.param);
            scope.v2 = value2(scope.param);
        }
    };
})

The directive template looks somewhat like this:

<div>
    <img ng-src="{{ param.img.src }}" />
    <div>
        <a href="{{ param.link.src }}">{{ param.link.text }}</a>
        <time datetime="{{ v1 }}">{{ v2 | date: 'medium' }}</time>
        <span ng-bind-html="param.text | customFilter1 | customFilter2 | customFilter3"></span>
    </div>
</div>

Everything works out nicely when I use this directive in the page html like this:

<ul ng-controller="SomeController" ng-cloak>
    <li ng-repeat="param in params">
        <my-directive param="param"></my-directive>
    </li>
</ul>

I now would like to reuse the directive to dynamically generate the respective html programmatically in a function from within a controller. My approach thereto tried to use the $compile function but without success:

// in controller:
// ...
$scope.generateMyDirective = function(param){
    var compiled = $compile('<my-directive param="param"></my-directive>')({});
    someElement.innerHTML = compiled[0];
};

The result (i.e. someElement) does include the static html of the directive, but won't evaluate any expressions within the directive, e.g. {{ param.img.src }} or {{ v1 }}. Furthermore the following type error is thrown:

TypeError: scope.$new is not a function

I tried to compile with different scopes, e.g. $scope of the controller or simply true to generate a new scope, but none will evaluate the expressions within the directive template. I also tried to invoke $scope.$apply() after inserting the directive html into someElement, which didn't work either.

I'm kind of stuck at this point, out of ideas and thankful for any hints. I hope you guys can help me fix this.

3
  • try $compile('...')($scope); You provide empty object instead of scope. Commented Jun 29, 2015 at 16:31
  • Thank you for your answer! Using $compile('<my-directive param="param"></my-directive>)($scope) seems to bring me a little bit further. However it now seems like param, which is part of the method signuature $scope.generateMyDirective = function(param){...}, is not passed to the directive. This leads to errors when compiling the template, e.g. Cannot read property 'img' of undefined. How can I make sure the param passed to the method is in turn passed to the directive within $compile()? Commented Jun 29, 2015 at 18:16
  • My suspicion seems to be correct. If I bind param to $scope before calling $compile() in the controller it leads to every call to generateMyDirective showing the same param, but it won't throw errors. I, however, would prefer to avoid binding the method parameter to the controller scope and I need to be able to compile for different params. Is there any other way of passing the parameter to $compile and therefore the directive? Commented Jun 29, 2015 at 18:24

3 Answers 3

2

Very 1st thing where you made a mistake is you directive is incorrect. Camel case should be - separated with all the letter in small case

It should be

<my-directive param="param"></my-directive>

Instead of

<myDirective param="param"></myDirective>

Also $compile should compile element with a scope, you need not worry about the creating new scope from controller, your directive already has an isolated scope.

$compile('<my-directive param="param"></my-directive>')($scope);
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you for your answer! My bad concerning the camel case to dash-separated notation, the actual directive has no camel case name, but a simple lowercase name, say directive, therefore resolving this issue. Calling compile with the controller scope $scope results in the follwoing type error: TypeError: Cannot read property 'replace' of undefined which I attribute to the custom filters used in the directive (which use some regular expressions to replace text). Do I need to inject the dependencies for the filters into the controller despite of them working naturally in html?
Changing to replace: false does not fix it. I suspect the replace in the error stemming from the calls to text.replace(regEx) in the filters. It seems like the param is not passed when calling $compile, leading to a call of undefined.replace(regEx) in turn leading to Cannot read property replace of undefined. But how do I make sure the param is passed properly when compiling in the controller?
you need to put $watch on param inside your directive link.and do opearation on scope when it has value
0
  1. You should provide any scope to your directive compiler
  2. all used variables (see p below) should be resolvable in the provided scope

$scope.p = {img: '', ...};  
$compile('<my-directive param="p"></my-directive>')($scope);
delete $scope.p;

Comments

0

I was able to solve the issue with the help of @vp_rath and @pankajparkar! The problem indeed lies with the $scope. In order to get the param passed to the call to $compile() we have two possible options. One solution is to bind param to the controller's scope and add a $watch(). The second way of doing this (and more what I was looking for) is: Instead of binding to the controller's $scope just create a new, vanilla scope like so:

// controller:
//
var generateMyDirection = function(param){
    var scope = $rootScope.$new();
    scope.param = param;
    var compiled = $compile('<my-directive param="param"></my-directive>')(scope);
    someElement.innerHTML = compiled[0];
};

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.