Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I've tried to write a small directive, to wrap its contents with another template file.

This code:

<layout name="Default">My cool content</layout>

Should have this output:

<div class="layoutDefault">My cool content</div>

Because the layout "Default" has this code:

<div class="layoutDefault">{{content}}</div>

Here the code of the directive:

app.directive('layout', function($http, $compile){
return {
    restrict: 'E',
    link: function(scope, element, attributes) {
        var layoutName = (angular.isDefined(attributes.name)) ? attributes.name : 'Default';
        $http.get(scope.constants.pathLayouts + layoutName + '.html')
            .success(function(layout){
                var regexp = /^([\s\S]*?){{content}}([\s\S]*)$/g;
                var result = regexp.exec(layout);

                var templateWithLayout = result[1] + element.html() + result[2];
                element.html($compile(templateWithLayout)(scope));
            });
    }
}

});

My problem:

When I'm using scope variables in template (in layout template or inside of layout tag), eg. {{whatever}} it just work initially. If I update the whatever variable, the directive is not updated anymore. The whole link function will just get triggered once.

I think, that AngularJS does not know, that this directive uses scope variables and therefore it will not be updated. But I have no clue how to fix this behavior.

share|improve this question
1  
None of the current answers seem to address the question of why (when using $compile) the watch is not automatically set up. As you say, it is bound in initially... –  Davin Tryon Nov 19 '13 at 11:17
 
I've found another solution, to use template and ng-transclude. This works well - always. The only problem is, that I don't know how to make the layout-template itself configurable. If I use ng-include with a scope function to get the template path, I get a ngTransclude:orphan error. –  Armin Nov 19 '13 at 15:45
1  
Okay, I've found a solution to change templateUrl dynamically. See my own answer below. –  Armin Nov 19 '13 at 16:25
add comment

4 Answers

You should create a binded scope variable and watch its changes:

return {
   restrict: 'E',
   scope: {
     name: '='
   },
   link: function($scope) {
     $scope.$watch('name', function() {
        // all the code here...
     });
   }
};
share|improve this answer
add comment

You need to tell Angular that your directive uses a scope variable:

You need to bind some property of the scope to your directive:

return {
    restrict: 'E',
    scope: {
      whatever: '='
    },
   ...
}

and then $watch it:

  $scope.$watch('whatever', function(value) {
    // do something with the new value
  });

Refer to the Angular documentation on directives for more information.

share|improve this answer
add comment
up vote 1 down vote accepted

I've found a much better solution:

app.directive('layout', function(){
    var settings = {
        restrict: 'E',
        transclude: true,
        templateUrl: function(element, attributes){
            var layoutName = (angular.isDefined(attributes.name)) ? attributes.name : 'Default';
            return constants.pathLayouts + layoutName + '.html';
        }
    }
    return settings;
});

The only disadvantage I see currently, is the fact that transcluded templates got their own scope. They get the values from their parents, but instead of change the value in the parent, the value get stored in an own, new child-scope. To avoid this, I am now using $parent.whatever instead of whatever.

Example:

<layout name="Default">
    <layout name="AnotherNestedLayout">
        <label>Whatever:</label>
        <input type="text" ng-model="$parent.whatever">
    </layout>
</layout>
share|improve this answer
add comment

You should keep a watch on your scope.

Here is how you can do it:

<layout layoutId="myScope"></layout>

Your directive should look like

app.directive('layout', function($http, $compile){
    return {
        restrict: 'E',
        scope: {
            layoutId: "=layoutId"
        },
        link: function(scope, element, attributes) {
            var layoutName = (angular.isDefined(attributes.name)) ? attributes.name : 'Default';
            $http.get(scope.constants.pathLayouts + layoutName + '.html')
                .success(function(layout){
                    var regexp = /^([\s\S]*?){{content}}([\s\S]*)$/g;
                    var result = regexp.exec(layout);

                    var templateWithLayout = result[1] + element.html() + result[2];
                    element.html($compile(templateWithLayout)(scope));
        });
    }
}

$scope.$watch('myScope',function(){
        //Do Whatever you want
    },true)

Similarly you can models in your directive, so if model updates automatically your watch method will update your directive.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.