ng-options with disabled rows - angularjs

Is it possible to use ng-options that it will render into disabled rows based on criteria?
this:
<select ng-options="c.name group by c.shade for c in colors">
maybe possible to turn into something like this:
<select ng-options="c.name group by c.shade for c in colors | disabled(c.shade)">
and let's say via a filter that could return disabled='disabled' for all the colors that have shade = "dark"
<select>
<optgroup label="dark">
<option value="0" disabled="disabled">black</option>
<option value="2" disabled="disabled">red</option>
<option value="3" disabled="disabled">blue</option>
</optgroup>
<optgroup label="light">
<option value="1">white</option>
<option value="4">yellow</option>
</optgroup>
</select>

#lucuma's answer (originally the accepted answer) was correct, but by now should be updated, because this was fixed in Angular 1.4. See the docs of ng-options which also contains an example.
I'm using Angular 1.5 and this works for me:
View
<select ng-model="$ctrl.selectedItem" ng-options="item as item.label disable when item.disabled for item in $ctrl.listItems">
Controller
vm.items = [
{ id: 'optionA', label: 'Option A' },
{ id: 'optionB', label: 'Option B (recommended)' },
{ id: 'optionC', label: 'Option C (Later)', disabled: true }
];
vm.selectedItem = vm.items[1];

As pointed by #Lod Angular added support for this in 1.4.0-beta.5.
For angular js >= 1.4.0-beta.5.
<select ng-options="c.name disable when c.shade == 'dark'
group by c.shade for c in colors">
And for angular js < 1.4.0-beta.5 refer the solution below:
Similar to the one given by #lucuma but without jQuery dependency.
Check this http://jsfiddle.net/dZDLg/46/
Controller
<div ng-controller="OptionsController">
<select ng-model="selectedport"
ng-options="p.name as p.name for p in ports"
options-disabled="p.isinuse for p in ports"></select>
<input ng-model="selectedport">
</div>
Directive
angular.module('myApp', [])
.directive('optionsDisabled', function($parse) {
var disableOptions = function(scope, attr, element, data,
fnDisableIfTrue) {
// refresh the disabled options in the select element.
var options = element.find("option");
for(var pos= 0,index=0;pos<options.length;pos++){
var elem = angular.element(options[pos]);
if(elem.val()!=""){
var locals = {};
locals[attr] = data[index];
elem.attr("disabled", fnDisableIfTrue(scope, locals));
index++;
}
}
};
return {
priority: 0,
require: 'ngModel',
link: function(scope, iElement, iAttrs, ctrl) {
// parse expression and build array of disabled options
var expElements = iAttrs.optionsDisabled.match(
/^\s*(.+)\s+for\s+(.+)\s+in\s+(.+)?\s*/);
var attrToWatch = expElements[3];
var fnDisableIfTrue = $parse(expElements[1]);
scope.$watch(attrToWatch, function(newValue, oldValue) {
if(newValue)
disableOptions(scope, expElements[2], iElement,
newValue, fnDisableIfTrue);
}, true);
// handle model updates properly
scope.$watch(iAttrs.ngModel, function(newValue, oldValue) {
var disOptions = $parse(attrToWatch)(scope);
if(newValue)
disableOptions(scope, expElements[2], iElement,
disOptions, fnDisableIfTrue);
});
}
};
});
Note: This solution doesn't work with group by as rightly pointed by everyone. Refer the solution below by #DHlavaty if you are looking to make it work with group by.

Angular added support for this in 1.4.0-beta.5
<select ng-options="c.name disable when c.shade == 'dark' group by c.shade for c in colors">

I do not believe there is a way to do what you are asking just using ng-options. This issue was raised on the angular project and is still open:
https://github.com/angular/angular.js/issues/638
It seems that the work around is to use a directive which is referenced here and in the github issue: http://jsfiddle.net/alalonde/dZDLg/9/
Here is the entire code from the jsfiddle for reference (the code below is from alande's jsfiddle):
<div ng-controller="OptionsController">
<select ng-model="selectedport"
ng-options="p.name as p.name for p in ports"
options-disabled="p.isinuse for p in ports"></select>
<input ng-model="selectedport">
</div>
angular.module('myApp', [])
.directive('optionsDisabled', function($parse) {
var disableOptions = function(scope, attr, element, data, fnDisableIfTrue) {
// refresh the disabled options in the select element.
$("option[value!='?']", element).each(function(i, e) {
var locals = {};
locals[attr] = data[i];
$(this).attr("disabled", fnDisableIfTrue(scope, locals));
});
};
return {
priority: 0,
require: 'ngModel',
link: function(scope, iElement, iAttrs, ctrl) {
// parse expression and build array of disabled options
var expElements = iAttrs.optionsDisabled.match(/^\s*(.+)\s+for\s+(.+)\s+in\s+(.+)?\s*/);
var attrToWatch = expElements[3];
var fnDisableIfTrue = $parse(expElements[1]);
scope.$watch(attrToWatch, function(newValue, oldValue) {
if(newValue)
disableOptions(scope, expElements[2], iElement, newValue, fnDisableIfTrue);
}, true);
// handle model updates properly
scope.$watch(iAttrs.ngModel, function(newValue, oldValue) {
var disOptions = $parse(attrToWatch)(scope);
if(newValue)
disableOptions(scope, expElements[2], iElement, disOptions, fnDisableIfTrue);
});
}
};
});
function OptionsController($scope) {
$scope.ports = [{name: 'http', isinuse: true},
{name: 'test', isinuse: false}];
$scope.selectedport = 'test';
}

A similar effect can be achieved using ng-repeat and ng-disabled on the option itself, avoiding the use of a new directive.
HTML
<div ng-controller="ExampleController">
<select ng-model="myColor">
<option ng-repeat="c in colors" ng-disabled="c.shade=='dark'" value="{{$index}}">
{{c.name}}
</option>
</select>
</div>
Controller
function ExampleController($scope, $timeout) {
$scope.colors = [
{name:'black', shade:'dark'},
{name:'white', shade:'light'},
{name:'red', shade:'dark'},
{name:'blue', shade:'dark'},
{name:'yellow', shade:'light'}
];
$timeout(function() {
$scope.myColor = 4; // Yellow
});
}
Fiddle
http://jsfiddle.net/0p4q3b3s/
Known issues:
Does not use ng-options
Does not work with group by
Selects the index, not the object
Requires $timeout for initial selection
Edit :
Any object property can be selected (besides the index), but not the object itself. Also, if you have a simple array and not an array of objects, below method will work.
Change this line in HTML :
<option ng-repeat="c in colors" ng-disabled="c.shade=='dark'" value="{{c.name}}">
Change this line in Controller :
$scope.myColor = $scope.colors[4].name; // Yellow

Since February 2015 there has been a way to disable options in your ng-options tag.
This Link shows the addition of the feature on github
I found that using angular 1.4.7, the syntax had changed from 'disable by' to 'disable when'.
The syntax for this is:
'ng-options': 'o.value as o.name disable when o.unavailable for o in options'

I had an interesting situation. An array of dropdowns and I need it to disable the options that were already selected in each of the dropdowns, but I also need it to keep enable the one that was selected already...
here is my plunker: Enable/Disable values with ng-options
var app = angular.module('ngoptions', []);
app.controller('MainCtrl', function($scope) {
// disable the fields by default
$scope.coverage = [
{ CoverageType: '', CoverageName: 'No Coverage' },
{ CoverageType: 'A', CoverageName: 'Dependent Only' },
{ CoverageType: 'B', CoverageName: 'Employee Plus Children' },
{ CoverageType: 'C', CoverageName: 'Employee Only' },
{ CoverageType: 'D', CoverageName: 'Employee Plus One' },
{ CoverageType: 'E', CoverageName: 'Employee Plus Two' },
{ CoverageType: 'F', CoverageName: 'Family' },
];
// values already set ex: pulled from db
$scope.rates = ['A','C', 'F'];
$scope.changeSelection = function (index, rate){
$scope.rates[index] = rate;
disableRecords();
}
// start by disabling records
disableRecords();
function disableRecords () {
// set default values to false
angular.forEach($scope.coverage, function (i, x) {
i.disable = false;
});
// set values to true if they exist in the array
angular.forEach($scope.rates, function (item, idx) {
angular.forEach($scope.coverage, function (i, x) {
if (item == i.CoverageType) {
i.disable = true;
}
});
});
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular.min.js"></script>
<!DOCTYPE html>
<html ng-app="ngoptions">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script data-require="angular.js#1.4.7" data-semver="1.4.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<table>
<thead></thead>
<tbody>
<tr ng-repeat="rate in rates">
<td>
<select
ng-model="rate"
ng-change="changeSelection($index, rate)"
ng-options="type.CoverageType as type.CoverageName disable when (type.disable == true && type.CoverageType != rate) for type in coverage"></select>
</td>
</tr>
</tbody>
</table>
</body>
</html>

You can disable using ngOptions in angular 1.4.1 or above
HTML template
<div ng-app="myapp">
<form ng-controller="ctrl">
<select id="t1" ng-model="curval" ng-options='reportingType.code as reportingType.itemVal disable when reportingType.disable for reportingType in reportingOptions'>
<option value="">Select Report Type</option>
</select>
</form>
Controller code
angular.module('myapp',[]).controller("ctrl", function($scope){
$scope.reportingOptions=[{'code':'text','itemVal':'TEXT','disable':false}, {'code':'csv','itemVal':'CSV','disable':true}, {'code':'pdf','itemVal':'PDF','disable':false}];
})

Similar "without jQuery" solution as #Vikas-Gulati's, but it works with group by
In my case, group by doesn't work, because my first <option> was without value, just with Please select and item from dropdown text. This is a slightly modified version, that fixes this particular situation:
Usage is simmilar to #Vikas-Gulati answer: https://stackoverflow.com/a/20790905/1268533
Directive
angular.module('disabledModule', [])
.directive('optionsDisabled', function($parse) {
var disableOptions = function(scope, attr, element, data, fnDisableIfTrue) {
var realIndex = 0;
angular.forEach(element.find("option"), function(value, index){
var elem = angular.element(value);
if(elem.val()!="") {
var locals = {};
locals[attr] = data[realIndex];
realIndex++; // this skips data[index] with empty value (IE first <option> with 'Please select from dropdown' item)
elem.attr("disabled", fnDisableIfTrue(scope, locals));
}
});
};
return {
priority: 0,
require: 'ngModel',
link: function(scope, iElement, iAttrs, ctrl) {
// parse expression and build array of disabled options
var expElements = iAttrs.optionsDisabled.match(/^\s*(.+)\s+for\s+(.+)\s+in\s+(.+)?\s*/);
var attrToWatch = expElements[3];
var fnDisableIfTrue = $parse(expElements[1]);
scope.$watch(attrToWatch, function(newValue, oldValue) {
if(newValue)
disableOptions(scope, expElements[2], iElement, newValue, fnDisableIfTrue);
}, true);
// handle model updates properly
scope.$watch(iAttrs.ngModel, function(newValue, oldValue) {
var disOptions = $parse(attrToWatch)(scope);
if(newValue)
disableOptions(scope, expElements[2], iElement, disOptions, fnDisableIfTrue);
});
}
};
});

As I cannot upgrade to latest angularJS, so created a simpler directive to handle it.
.directive('allowDisabledOptions',['$timeout', function($timeout) {
return function(scope, element, attrs) {
var ele = element;
var scopeObj = attrs.allowDisabledOptions;
$timeout(function(){
var DS = ele.scope()[scopeObj];
var options = ele.children();
for(var i=0;i<DS.length;i++) {
if(!DS[i].enabled) {
options.eq(i).attr('disabled', 'disabled');
}
}
});
}
}])
for more details: https://github.com/farazshuja/disabled-options

I also hid disabled options adding fallowing line:
$(this).css("display", fnDisableIfTrue(scope, locals) ? "none" : "block");
It was necessary as I couldn't simply filter them out, as the initial value of this select could be one of the disabled options.

Related

Angular Typeahead dynamically added results

I have having difficulties getting the uib-typeahead to work as I need it to.
See: https://jsfiddle.net/0wp1t0ut/ - if you type "g" into the input box, "germany" correctly gets added to the source array, but the typeahead view does not get updated until the next keypress. I.e. Germany is there, but I can't select it until I press the "e".
Basically what I'm trying to achieve is a typeahead that will dynamically update as new results come in (rather than having to wait for all my calls to complete before I return the array to the typeahead).
[Note in my real code, the ng-change on the typeahead is a function that makes multiple calls to different sources, and I want the typeahead to show with data as soon as the first call returns data, and add to that as later calls return more results.]
Has anyone come across this problem before, or able to offer any suggestions (happy to use a different typeahead if a better alternative already exists!)
HTML:
<div ng-app="myApp" ng-controller="MyCtrl as vm">
<label>TypeAhead:</label>
<input type="text" ng-model="selected" ng-change="vm.add()" uib-typeahead="state as state.descrizione for state in vm.states | filter:$viewValue | limitTo:8" typeahead-model-change class="form-control" >
<label>Model</label>
<pre>{{vm.states|json}}</pre>
<label>Modify Model Description:</label>
<input ng-model="selected.descrizione" class="form-control">
</div>
JS:
var myApp = angular.module('myApp', ['ui.bootstrap']);
myApp.controller('MyCtrl', [function() {
var vm = this;
vm.add = function(){
var a = {
codice: 'at',
descrizione: 'germany'};
vm.states.push(a)
}
vm.states = [{
codice: 'it',
descrizione: 'italia'
}, {
codice: 'fr',
descrizione: 'francia'
}];
}]);
myApp.directive('typeaheadModelChange', function() {
return {
require: ['ngModel', 'typeaheadModelChange'],
controller: ['$scope', '$element', '$attrs', '$transclude', 'uibTypeaheadParser', function($scope, $element, $attrs, $transclude, uibTypeaheadParser) {
var watchers = [];
var parserResult = uibTypeaheadParser.parse($attrs.uibTypeahead);
var removeWatchers = function() {
angular.forEach(watchers, function(value, key) {
value();
});
watchers.length = 0;
}
var addWatchers = function(modelCtrl) {
watchers.push($scope.$watch('selected', function(newValue, oldValue) {
if (oldValue === newValue)
return;
if (newValue) {
var locals = [];
locals[parserResult.itemName] = newValue;
$element.val(parserResult.viewMapper($scope, locals));
}
}, true));
}
this.init = function(modelCtrl) {
modelCtrl.$formatters.push(function(modelValue) {
removeWatchers();
addWatchers(modelCtrl);
return modelValue;
});
};
}],
link: function(originalScope, element, attrs, ctrls) {
ctrls[1].init(ctrls[0]);
}
};
});

ng-repeat with ng-bind-html as pre and post-markup

I have an array with multiple objects, similar to this:
[
{ title: 'abc', 'pre': '<div class="class1"><div class="class2">', 'post': '</div>' },
{ title: 'def', 'pre': <div class="class3">', 'post': '</div>' },
{ title: 'ghi', 'pre': '<div class="class3">', 'post': '</div></div>' }
]
<div ng-repeat="item in myVar">
<div ng-bind-html="item.pre" />{{ item.title }}<div ng-bind-html="item.post" />
</div>
The above does not work (I have to open two div's in one, and close in two other items in that array, as illustrated above). The problem is that ng-bind-html needs to be bound to an element, which I cannot use, neither does a filter work:
<div ng-repeat="item in myVar">
{{ item.pre | trust }}{{ item.title }}{{ item.post | trust }}
</div>
angular.module('myModule').filter('trust', ['$sce',function($sce) {
return function(value, type) { return $sce.trustAsHtml; }
}]);
Any ideas?
You'll have to perform the concatenation pre-view, trust that (or turn on ngSanitize, potentially better-yet), then inject it.
As far as I know, there's no way to inject a partial HTML element the way you're trying to.
In your controller:
$scope.items = [...];
for (var i = 0; i < $scope.items.length; i++) {
var e = $scope.items[i];
e.concatenated = $sce.trustAsHtml(e.pre + e.title + e.post);
}
Then in your view:
<div ng-repeat="item in items">
<div ng-bind-html="item.concatenated" />
</div>
Of course, you'll probably want ngSanitize turned on, just to avoid any issues with e.title. That is, if someone entered a title of <script>alert('ahh!')</script>, that would end up being trusted.
Your version did not work because of how ngBindHtml is written:
var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) {
return {
restrict: 'A',
compile: function ngBindHtmlCompile(tElement, tAttrs) {
var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) {
return (value || '').toString();
});
$compile.$$addBindingClass(tElement);
return function ngBindHtmlLink(scope, element, attr) {
$compile.$$addBindingInfo(element, attr.ngBindHtml);
scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
// we re-evaluate the expr because we want a TrustedValueHolderType
// for $sce, not a string
element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || '');
});
};
}
};
}];
It injects using element.html(...), which needs a complete HTML element.

Get height of div in angularjs

I have 3 div in which the data is filling from controller. and the div is dependent on dropdown select (particular div will be shown for particular dropdown value). the problem is that I am unable to get the height of that div when page is loaded and also when I changed the dropdown value. Everytime I am getting 0 height.
here is the code for html:
<div class="brand-categoryWrp">
<select ng-model="allbrandscategory" ng-change="catChange(allbrandscategory)">
<option value="all">AllBrands</option>
<option value="watches">Watches</option>
<option value="pens">Pens</option>
</select>
</div>
<div ng-show="firstdiv" allbrands-directive>
<ul>
<li ng-repeat="res in brands">
{{res.name}}
</li>
</ul>
</div>
<div ng-show="seconddiv" watches-directive>
<ul>
<li ng-repeat="res in brands1">
{{res.name}}
</li>
</ul>
</div>
<div ng-show="thirddiv" pens-directive>
<ul>
<li ng-repeat="res in brands2">
{{res.name}}
</li>
</ul>
</div>
and here is for controller:
// Code goes here
var myapp = angular.module('myModule', []);
myapp.controller('mycntrl',function($scope){
$scope.allbrandscategory = 'all';
$scope.firstdiv = true;
$scope.brands = [
{name: 'Adidas'},
{name: 'Armani'}
];
$scope.brands1 = [
{name: 'Adidas1'},
{name: 'Armani1'},
{name: 'Fossil'}
];
$scope.brands2 = [
{name: 'Adidas2'},
{name: 'Armani2'},
{name: 'Mont blanc'},
{name: 'calvin'}
];
$scope.catChange = function(val){
$scope.firstdiv = false;
$scope.seconddiv = false;
$scope.thirddiv = false;
if(val == 'all')
{
$scope.firstdiv = true;
}
else if(val == 'watches')
{
$scope.seconddiv = true;
}
else if(val == 'pens')
{
$scope.thirddiv = true;
}
};
});
myapp.directive('pensDirective', function($timeout) {
return {
restrict: 'A',
link: function(scope, element) {
//console.log(element);
console.log("pens: "+element[0].offsetHeight);
}
};
});
myapp.directive('watchesDirective', function($timeout) {
return {
restrict: 'A',
link: function(scope, element) {
// console.log(element);
console.log('watches: '+element[0].offsetHeight);
}
};
});
myapp.directive('allbrandsDirective', function($timeout) {
return {
restrict: 'A',
link: function(scope, element) {
//console.log(element);
console.log("brand: "+element[0].offsetHeight);
}
};
});
Here is the plunker
The link function is executed before the model data is bound to the view (the div does not contain any child elements when you're requesting the offsetHeight. Try wrapping it into a $timeout(function() { /* ... */ }), which will execute at the end of the current digest cycle.
Check out AngularJs event to call after content is loaded
This has a nice answer to your problem and seems to work. Second answer down.
I tested on yours by adding the below to your allbranddirective:
element.ready(function(){
console.log("brand: "+element[0].offsetHeight);
});
EDIT:
HTML:
<html>
<div class="brand-categoryWrp">
<select ng-model="divtoshow" ng-change="catChange(divtoshow)">
<option value="allbrands-directive">AllBrands</option>
<option value="watches-directive">Watches</option>
<option value="pens-directive">Pens</option>
</select>
</div>
<div allbrands-directive><ul><li ng-repeat='res in brands'>{{res.name}}</li></ul></div>
JS:
var myapp = angular.module('myModule', []);
myapp.controller('mycntrl',function($scope){
$scope.brands = [{name: 'Adidas'}, {name: 'Armani'}];
$scope.divtoshow = 'allbrands-directive'
$scope.catChange = function(val){
$scope.divtoshow = val;
if (val == 'allbrands-directive'){
$scope.brands = [{name: 'Adidas'}, {name: 'Armani'}];
}else if (val == 'watches-directive') {
$scope.brands = [{name: 'Adidas1'},{name: 'Armani1'},{name: 'Fossil'}];
}else if (val == 'pens-directive') {
$scope.brands = [{name: 'Adidas2'},{name: 'Armani2'},{name: 'Mont blanc'},{name: 'calvin'}];
}
}
});
myapp.directive('allbrandsDirective', function($timeout) {
return {
restrict: 'A',
link: function(scope, element) {
scope.$watch('brands', function(){
console.log("brand: "+element[0].offsetHeight);
});
}
};
});
So I have changed your code a bit, I have set up only one directive and in here I watch on a change of the brands variable, when this changes we then get the height of the element.
See plunker - http://plnkr.co/edit/vZhn2aMRI6wKn3yVharS?p=preview
If you need each section in separate directives see nulls answers

How to use bootstrap-select with angular

I would like to style my dropdowns with bootstrap-select.
There are two dropdowns in the page. The first one has hardcoded options. The second one is bound dynamically with data from database.
The bootstrap-select is applied all well for the first one but not the second. It adds a text 'Nothing selected' and the options also are not bound.
How to fix this.
aspx:
<link href="../assets/css/bootstrap-select.css" rel="stylesheet" />
<script src="../assets/js/bootstrap-select.js"></script>
<script src="../assets/js/angular-1.3.16.min.js"></script>
<script src="../scripts/app.js"></script>
<script src="../scripts/controllers/Ctrl.js"></script>
<script src="../scripts/services/Factory.js"></script>
<script src="../scripts/directives/directives.js"></script>
<select id="ddlReports" bootstrapselectpicker >
<option data-ng-repeat="item in Reports" value="{{item.Id}}">{{item.Name}}</option>
</select>
<select id="ddlSets" bootstrapselectpicker>
<option value="0">-- Select Question Set --</option>
<option data-ng-repeat="item in QuestionSets" value="{{item.SetId}}">{{item.Title}}</option>
</select>
Ctrl.js
app.controller("Ctrl", ["$scope", "Factory", function ($scope, Factory) {
var init = function() {
$scope.Reports = [
{ Id: 1, Name: "abc" },
{ Id: 2, Name: "def" }
];
var promise = Factory.GetData();
promise.then(function (success) {
if (success.data != null && success.data != '') {
var data = success.data;
$scope.QuestionSets = data.QuestionSets;
}
},
function (error) {
});
}
init();
}]);
directive.js
app.directive('bootstrapselectpicker', function ($timeout) {
return {
restrict: "A",
link: function (scope, element, attrs, ctrl) {
$timeout(function () {
element.selectpicker();
});
}
};
});

ng-minlength in directive not populated when invalid

I'm creating an input directive that encapsulates a label, and some error messaging but I'm getting stuck with ng-minlength and ng-maxlength. The issue is that they don't populate the model value until it's a valid value, so my parent scope can display a value while my directive doesn't, and vice versa. View this plunk for an example of what I mean.
Is the only way around this to define my own minlength and maxlength validators? Is there some way configure this behaviour so the model is always populated? I want to use all the built in validators in my directive, so no doubt this will be an issue with all of them and I'd rather not redefine them all.
HTML:
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="app" ng-controller="Controller">
<label>Outer scope 1</label>
<input name="input1" type="text" ng-model="model1" ng-minlength="4"/>
<br/>
<label>Directive scope 1</label>
<input-dir ng-model="model1" ng-minlength="0"></input-dir>
<br/>
<br/>
<br/>
<label>Outer scope 2</label>
<input name="input3" type="text" ng-model="model2"/>
<br/>
<label>Directive scope 2</label>
<input-dir ng-model="model2" ng-minlength="4"></input-dir>
</body>
</html>
Javascript:
var app = angular.module('app', []);
app.controller('Controller', function($scope){
$scope.model1 = "Model1";
$scope.model2 = "Model2";
});
app.directive('inputDir', function(){
return {
restrict: 'E',
template: '<input type="text" ng-model="model" ng-minlength="{{ minlength }}" />',
scope: {
model: '=ngModel',
minlength: '=ngMinlength'
}
};
});
For anyone else with the same issue, I solved this by redefining all the validators, except now they return the value when invalid so the model is populated. Just include the valuedValidators module. These validators are available on the $error object as vvminlength etc.
vv-validators.js
(function () {
'use strict';
var validators = {};
validators.vvUrl = function(value){
var urlRegex = new RegExp(/[-a-zA-Z0-9#:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9#:%_\+.~#?&//=]*)?/gi);
return urlRegex.test(value);
}
validators.vvEmail = function(value){
var emailRegex = new RegExp(/^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
return emailRegex.test(value);
}
validators.vvMinlength = function(value, min){
var huh = value.length >= parseInt(min);
return min && value && huh;
}
validators.vvMaxlength = function(value, max){
return max && value && value.length <= parseInt(max);
}
validators.vvMin = function (value, min) {
return min && parseFloat(value) >= parseFloat(min);
}
validators.vvMax = function (value, max) {
return max && parseFloat(value) <= parseFloat(max);
}
validators.vvPattern = function (value, pattern) {
return pattern && new RegExp(pattern).test(value);
}
validators.vvFloat = function (value) {
var floatRegex = new RegExp(/^\-?\d+((\.|\,)\d+)?$/);
return floatRegex.test(value);
}
validators.vvInteger = function (value) {
var integerRegex = new RegExp(/^\-?\d+$/);
return integerRegex.test(value);
}
var app = angular.module('valuedValidators', []);
var names = Object.keys(validators);
names.forEach(function(name){
app.directive(name, function(){
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, controller) {
controller.$parsers.unshift(function (viewValue) {
var valid = true;
var attrValue = attrs[name];
var func = validators[name];
if (attrValue !== 'false') {
valid = func(viewValue, attrValue);
controller.$setValidity(name.toLowerCase(), valid);
}
return viewValue;
});
}
};
});
});
}());
There is an even easier solution: use two textareas.
The first serves as a draft, the second is the model where the verification is to take place.
<textarea name="message_body_draft" cols="47" rows="15" style="width: 100%;"
ng-model="message_body_draft"
ng-change="message_body=message_body_draft"></textarea>
<textarea name="message_body" style="width: 100%;" disabled ng-model="message_body"
ng-minlength="100" required></textarea>
When the user writes in the first one, it automatically will populates the right model (the second one).

Resources