Search code examples
javascriptangularjscompilationtransclusion

Extract ng-message key from ng-messages


I need to create a directive myDirective which reads all ng-message keys within it and it still creates a valid ng-messages element:

Directive template:

<div id="myDirective">
  <div ng-messages="myform.$error" ng-transclude></div>
</div>

Usage:

<my-directive>
  <div ng-message="required">something is required</div>
  <div ng-message="custom">custom validation</div>
</my-directive>

So in this case I'd like to get an array of "required" and "custom". The problem is I cannot do that since all ng-messsage elements are already hidden (removed from the DOM to be more accurate) by the time my directive gets into its own linking function. How to get around this?


Solution

  • The contents of the directive are removed during the compile phase because your directive has transclude: true (I'm assuming so, since you are using ng-transclude).

    In this case, the contents are available via the passed-in transclude function (this is actually what ng-transclude uses under the covers).

    transclude function is the 5th parameter to the link (or pre-link) function:

    link: function(scope, element, attrs, ctrls, transclude){
      var transcludedContents; // jqLite of the contents
    
      transclude(function(clone){
        transcludedContents = clone;
      });
    
      // do whatever you do to extract ng-message values from transcludedContents
      // just remember, that it could also come in the form:
      // <ng-message when="required"> and 
      // <div ng-message-exp="errorMessage.type">
    }
    

    Possibly the lazier among us would not want to deal with DOM counting, and so you could create another handler for ng-message directive that interacts with your my-directive to register itself:

    .directive("myDirective", function() {
      return {
        transclude: true,
        templateUrl: "myDirective.template.html",
        controller: function() {
          var messages = [];
          this.registerNgMessage = function(val) {
            messages.push(val);
          };
        }
      };
    })
    .directive("ngMessage", complementaryNgMessageDirective)
    .directive("ngMessageExp", complementaryNgMessageDirective);
    
    function complementaryNgMessageDirective() {
      return {
        priority: 10, // must be higher than ngMessage, because ngMessage is terminal
        require: "^?myDirective", // must be optional not to break existing code
        link: function(scope, element, attrs, myDirectiveCtrl) {
    
          if (!myDirectiveCtrl) return; // do nothing, if not paired with myDirective
    
          var staticExp = attrs.ngMessage || attrs.when;
          var dynamicExp = attrs.ngMessageExp || attrs.whenExp;
    
          var val;
          if (dynamicExp) {
            val = scope.$eval(dynamicExp);
          } else {
            val = staticExp;
          }
    
          myDirectiveCtrl.registerNgMessage(val);
        }
      };
    }