0

I am playing with AngularJS to discover its power but I have to admit the documentation is not very developed, so I'm asking here the community for a problem I'm facing with nested directives.

I'm more looking for the reasonment (and explanations on what I'm doing wrong) than a finished solution.

So here is the thing (I am using angular-messages but I don't think it's important as the problem would be common to any directive):

To quickly change the errors management I have decided to encapsulate the manager (angular-messages here) into a directive, so to display my errors on a form I do it this way :

<script type="text/ng-template" id="default-error-messages">
  <error-message data-error="email" data-message="This field is not a valid email"></error-message>
  <error-message data-error="required" data-message="This field is required"></error-message>
  <error-message data-error="minlength" data-message="This field is too short"></error-message>
</script>
<form data-ng-submit="submitForm(registrationForm)" method="POST" name="registrationForm" novalidate>
    <input type="email" name="email" data-ng-model="user.email" required>
    <error-container data-watch-error-on="registrationForm.email.$error" data-default-errors="default-error-messages" data-ng-if="registrationForm.email.$dirty">
        <error-message data-error="required" data-message="test"></error-message>
    </error-container>
    <button type="submit" data-ng-disabled="registrationForm.$invalid">Register</button>
</form>


directives.directive('errorContainer', ['$compile',function($compile){
    return{
        restrict: 'E',
        transclude: true,
        replace: false,
        scope: {
            watchErrorOn: '@'
        },
        template: '<div class="error-container" data-ng-transclude></div>',
        compile: function(tElt, tAttrs, ctrl) {
            return {
                pre: function(scope, iElement, iAttrs){
                    iElement.find('.error-container').attr("data-ng-messages", scope.watchErrorOn);
                },
                post: function(scope, iElement, iAttrs){
                    if (angular.isDefined(iAttrs.defaultErrors)) {
                        var errorList = angular.element("<div data-ng-messages-include='" + (iAttrs.defaultErrors || 'default-error-messages') + "'></div>");
                        iElement.find('.error-container').append(errorList);
                        $compile(iElement)(scope);
                    }
                }
            }
        },
        link: function(scope, element, attrs, ctrl){
            $compile(element)(scope);
        }
    }
}]);


directives.directive('errorMessage', ['$compile', function($compile){
    return{
        restrict: 'E',
        template: '<div class="error"></div>',
        replace: true,
        scope:{
            message:'@',
            error:'@'
        },
        compile: function(tElt, tAttrs, ctrl){
            return{
                pre: function(scope, iElement, iAttrs){
                    iElement.attr('data-ng-message', scope.error);
                    iElement.text(scope.message);
                }
            }
        }
    }
}]);

As your surely know, It doesn't work, default errors are not included at all in the template. I have try a lot of combinations on pre/post compile functions & link but nothing was successful. I think this is a problem of priority on compilation, maybe ng-messages-include should be the last to compile but no idea on how, thank you in advance

Bil5
  • 502
  • 1
  • 5
  • 15

2 Answers2

0

You should be using = instead of @ in directive isolated scope, which will pass data-watch-error-on="registrationForm.email.$error" value to the isolated scope directly watchErrorOn variable.

scope: {
   watchErrorOn: '='
},

As you were using @ that indicates one way binding & that need value to be pass in {{}} interpolation directive, but the important thing is you wanted to pass true & false value to the directive isolated scope, but after passing that value in the interpolated value to directive will convert that bool value to string like "false" & "true" that will unnecessary if else check inside our directive. Passing value using = is nothing but two way binding that will keep your value as boolean as you pass it from directive element.

Pankaj Parkar
  • 134,766
  • 23
  • 234
  • 299
  • Thanks for the answer unfortunately changing @ to = did not make any change, default messages from the ng-messages-include are still not included. But your advice is for sure welcomed as I was misunderstanding the '@' role (only the text value is passed) – Bil5 Sep 05 '15 at 20:31
  • @Bil5 I'd love to help you further..would you mind to create a plunkr with your problem.. – Pankaj Parkar Sep 05 '15 at 20:35
  • For sure Pankaj, I thank you in advance : [Pluker link](http://plnkr.co/yGYJqZmc5UAtp8KfGXlD) – Bil5 Sep 05 '15 at 20:59
  • @Bil5 will look at tomorrow – Pankaj Parkar Sep 05 '15 at 21:58
  • Pankaj must be busy, any other idea? – Bil5 Sep 07 '15 at 09:55
0

Well, I found a solution after having been oriented by a post on GitHub.

The problem is, as suspected, caused by the compilation politics of AngularJS. So in few words the key is to put include all directives directly on the template, including them after could still work but would require to compile manually the element.

The thing that is completely strange is that, naturally, pre-compile function would be same as "modifying the template string" before the compilation phase, but that is not the case, apparently.

So please consider the following solution that works well :

The html :

<form method="POST" name="registrationForm" novalidate>
    <input type="email" name="email" placeholder="Email address" data-ng-model="user.email" autocomplete="off" required>
    <button type="submit">Register</button>
<error-container data-messages-include="my-custom-messages" data-error-watch="registrationForm.email.$error">
    <error-message data-error="required" data-message="This field is required (version 1)"></error-message>
    <error-message data-error="email" data-message="This field must be an email"></error-message> 
</error-container>
</form>

The directives :

var app = angular.module('app', ['ngMessages']);
app.directive('errorContainer', function(){
    return{
        template: '<div ng-messages="watch"><div ng-messages-include="{{ messagesInclude }}"></div><div ng-transclude></div></div>',
        scope: {
          watch: '=errorWatch',                  
          messagesInclude: '@messagesInclude'                  
        },
        transclude: true
    };
});
app.directive('errorMessage', [function(){
    return{
        scope: {
          error: '@error',
          message: '@message'             
        },
        template: '<div ng-message="{{ error }}">{{ message }}</div>',
        replace: true
    }
}]);

The end word, AngularJS is definitely a great framework but some fundamental processes are, from my point of view, unfortunately not as sufficiently explicit.

Bil5
  • 502
  • 1
  • 5
  • 15