The problem is that I want use ng-pattern exactly the same as ng-pattern works in input, so I want to have possibility to use regexp in the ng-pattern and also scope variable with pattern ($scope.mypattern = /^[a-z]+$/; ... ng-pattern="mypattern"). How to manage this?

I want both working:

1.

<my-input label="My Label" name="myname" ng-model="mymodel" ng-pattern="/^[a-z]+$/">

2.

$scope.myPattern = /^[a-z]+$/
...
<my-input label="My Label" name="myname" ng-model="mymodel" ng-pattern="myPattern">

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

I have three answers for you.

  1. In general, it is not recommended to support both a model property, and directly a String. This case is handled by a = declaration in your directive scope, and if you want to pass a String, you would use simple quotes. For instance ngBind works like this: ng-bind="someModel" or ng-bind="'some string'"

  2. If you really want to, you can try to parse the expression. If it is parsable, it means it is a scope model. Otherwise, it is likely a string. See working example in the code snippet below:

angular.module('test', [])

.controller('test', function($scope) {
  $scope.model = "string from scope model";
})

.directive('turlututu', function($parse) {
 return {
   restrict: 'E',
   scope: {},
   template: '<div class="tu">{{content}}</div>',
   link: function(scope, elem, attrs) {
     try {
       scope.content = $parse(attrs.text)(scope.$parent);
     } catch(err) {
     } finally {
       if (!scope.content) {
         scope.content = attrs.text;
       }
     }
   }
 };
});
body { font-family: monospace; }

.tu { padding: 10px; margin: 10px; background: #f5f5f5; border-bottom: 2px solid #e5e5e5; }
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="test">
  
  <turlututu text="hardcoded string"></turlututu>
  <turlututu text="model"></turlututu>
  
</div>
  1. In the case of ngPattern, because curiosity in code will always help you, you can see in the source code of Angular that they test the attribute first character: if it is / it is considered a regexp, otherwise a scope model (see example below)
angular.module('test', [])

.controller('test', function($scope) {
  $scope.model = /[a-z]*/;
})

.directive('turlututu', function($parse) {
 return {
   restrict: 'E',
   scope: {},
   template: '<div class="tu">{{content}}</div>',
   link: function(scope, elem, attrs) {
     if (attrs.regexp.charAt(0) === '/') {
       scope.reg = new RegExp( attrs.regexp.substring(1, attrs.regexp.length-1) );
     } else {     
       scope.reg = new RegExp( $parse(attrs.regexp)(scope.$parent) );
     }
     
     scope.content = scope.reg.toString()
   }
 };
});
body { font-family: monospace; }

.tu { padding: 10px; margin: 10px; background: #f5f5f5; border-bottom: 2px solid #e5e5e5; }
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="test">
  
  <turlututu regexp="/[0-9]*/"></turlututu>
  <turlututu regexp="model"></turlututu>
  
</div>

Solution 2

I was facing the same problem – needed to treat my custom decorated input as if it was a regular input element. I also started with isolated scope but eventually decided it is not flexible enough. Having to add every attribute I might need gets old real quick.
So I came up with the below solution. Not sure if it is the correct/canonical Angular way, but works for me.

The idea is to build a “view model” object from directive’s attributes and feed it to $interpolate. Any attributes not used in the view model are inserted into the actual < input /> element via the special attributesToCopy property of the view model. It does not create any new scopes, just puts it in the current scope.
This directive uses uses lodash kebabCase: https://lodash.com/docs/4.17.4#kebabCase

If you don’t want to include lodash you can use snake_case function from Angular itself, but it is not exposed via their API so you would need to copy-paste it somewhere in your code.

controlsModule.directive('myTextbox', ['$interpolate',function ($interpolate) {
    var tmpl =
    '<div class="form-group" title="{{hint}}"> \
                    <label class="{{labelClass}} control-label">{{label}}</label> \
                    <input type="{{type}}" class="form-control input-group-sm" {{attributesToCopy}} /> \
            </div>';

    var interpolate = $interpolate(tmpl);

    function buildModel(attrs) {

        if (!attrs.placeholder) attrs.placeholder = attrs.label;
        if (!attrs.labelClass) attrs.labelClass = "col-sm-3";
        if (!attrs.type) attrs.type = "text";

        return {
            label: attrs.label,
            labelClass: attrs.labelClass,
            type: attrs.type
        };
    }

    function template(tElement, tAttrs) {
        var model = buildModel(tAttrs);
        var atc = "";

        for (var attr in tAttrs) {
            if (!(attr.lastIndexOf('$', 0) === 0) && tAttrs.hasOwnProperty(attr) && !model[attr]) {
                    atc += ' ' + _.kebabCase(attr); //converts to snake_case , uses lodash/uderscore
                    if (tAttrs[attr] !== attr) {
                        atc += '="' + tAttrs[attr] + '" ';
                }
            }
        }

        model.attributesToCopy = atc;
        return interpolate(model);
    }

    return {
        restrict: 'EA',
        template: template
    };
}]);

Usage:

    <my-textbox type="number" ng-model="foo" ng-min="0" ng-max="5" required></my-textbox>

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply