AngularJS custom validation directive with dynamic blacklist - angularjs

I am using the example for ngMessages from this post:
How to add custom validation to an AngularJS form?
It works OK as long as the blacklist is a static list of items.
I would like to dynamically generate the blacklist but the directive seems to render before the blacklist is populated.
This is the directive:
.directive('blacklist', function () {
return {
require: 'ngModel',
link: function (scope, elem, attr, ngModel) {
var blacklist = attr.blacklist.split(',');
ngModel.$parsers.unshift(function (value) {
ngModel.$setValidity('blacklist', blacklist.indexOf(value) === -1);
return value;
});
}
};
});
This is the input where the directive is used:
<input type="text" id="DocumentName" name="DocumentName" class="form-control"
ng-model="$ctrl.document.DocumentName" ng-required="true"
blacklist="{{$ctrl.DocumentNames}}" />
In the controller when the blacklist is specified with static values it works as expected.
.component('documentDetail', {
templateUrl: '/app/document-detail/document-detail.template.html',
controller: ['Document',
function DocumentDetailController(Document) {
var self = this;
self.DocumentNames = "Install Direct Bill Invoice,Order Preacknowledgement,Order Acknowledgement"; });
When this is changed to get the DocumentNames with a service call it seems like the directive is rendered before the blacklist values are populated.
component('documentDetail', {
templateUrl: '/app/document-detail/document-detail.template.html',
controller: ['Document',
function DocumentDetailController(Document) {
var self = this;
var documentProfiles = Document.query();
documentProfiles.$promise.then(function () {
var bl = [];
for (var i = 0; i < documentProfiles.length; i++) {
bl.push(documentProfiles[i].DocumentName);
}
self.DocumentNames = bl.join(',');
});
When I inspect the element I can see the data has been populated:
But the validation acts like it is an empty string:
I tried wrapping it in a $timeout but the result was the same.
component('documentDetail', {
templateUrl: '/app/document-detail/document-detail.template.html',
controller: ['Document', '$timeout',
function DocumentDetailController(Document, $timeout) {
var self = this;
var documentProfiles = Document.query();
$timeout(function () {
documentProfiles.$promise.then(function () {
var bl = [];
for (var i = 0; i < documentProfiles.length; i++) {
bl.push(documentProfiles[i].DocumentName);
}
self.DocumentNames = bl.join(',');
});
});
How can I get these values to populate before the directive or input renders so that the blacklist can be dynamic? Thanks in advance for your help.

Use attr.$observe:
app.directive('blacklist', function () {
return {
require: 'ngModel',
link: function (scope, elem, attrs, ngModel) {
var blacklist = attrs.blacklist.split(',');
attr.$observe("blacklist",function(newValue) {
blacklist = newValue.split(',');
});
ngModel.$parsers.unshift(function (value) {
ngModel.$setValidity('blacklist', blacklist.indexOf(value) === -1);
return value;
});
}
};
});
The observer function is invoked whenever the interpolated value changes.
For more information, see AngularJS attr.$observe API Reference
Update
Using the $validators API1
The accepted answer to the referenced question uses the $parsers and $formatters pipelines to add a custom synchronous validator. AngularJS 1.3+ added a $validators API so there is no need to put validators in the $parsers and $formatters pipelines:
app.directive('blacklist', function (){
return {
require: 'ngModel',
link: function(scope, elem, attrs, ngModel) {
ngModel.$validators.blacklist = function(modelValue, viewValue) {
var blacklist = attrs.blacklist.split(',');
var value = modelValue || viewValue;
var valid = blacklist.indexOf(value) === -1;
return valid;
});
}
};
});
Notice that since the blacklist is re-computed everytime the ngModelController does a validation, there is no need to add an $observe function.
For more information, see AngularJS ngModelController API Reference - $validators.

Related

How to access ng-model from attribute directive?

I'm trying to write a custom directive to validate input value: does it belong to the specified range. The problem is that I can't access ng-model without knowing the name of the scope variable which is used for ng-model. Considering that directive has to be reused with different inputs I want to access ng-model directly. I did try to use scope[attrs.ngModel] but got the undefined value. How can read ng-model value inside directive? Thank you.
netupApp.directive('between', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
scope.$watch('dataSubmitting', function(dataSubmitting){
if (dataSubmitting) {
var min = Number(attrs.min);
var max = Number(attrs.max);
console.log(attrs.ngModel); // "number"
console.log(scope[attrs.ngModel]); // undefined
var inputText = scope.number; // that is the var used in ng-model
console.log(min); // 10
console.log(inputText); // would be the input value
console.log(max); //20
if (inputText <= min || inputText >= max) {
scope.alerts.push({
msg: 'error',
type: 'danger',
icon: 'warning',
'closable': true
});
}
}
});
}
};
});
You should hook into the Angular validation system and add your validator function to either the $validators or $asyncValidators collections (in your case I think $validators is enough, no need for async).
The validator functions receive the model value as an argument :
link: function(scope, elm, attrs, ctrl) {
var min = Number(attrs.min);
var max = Number(attrs.max);
ctrl.$validators.between = function(modelValue, viewValue) {
if (modelValue <= min || modelValue >= max) {
//do something here or just return false
return false;
}
return true;
}
}
In the view you can get the validation error messages like this :
<div ng-messages="formName.inputName.$error">
<p ng-message="between">The value is not in the required range<p>
</div>
Reference doc : https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
The proper way to get the ngModel.$viewValue is:
app.directive('between', function () {
return {
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
ngModel.$render = function () {
var newValue = ngModel.$viewValue;
console.log(newValue)
};
}
};
});
Have a look at tutorial underneath when wanting to invoke the ngModel.$setViewVAlue from the directive
https://egghead.io/lessons/angularjs-using-ngmodel-in-custom-directives
According to angluarJs doc:$parse
you need to parse the attrs, please try:
var getter=$parse(attrs.ngModel);
var setter=getter.assign;
setter(scope,getter(scope));

ngModel Not Updating In Directive

I have a directive that detects the change of a file input and aims to set a model. However the imagefile model is not being set after the file change. I have been able to print out the values of controller.modelValue[0].name and it is set. However when it returns the image file model is null.
Any suggestions on how to fix this or what am I doing incorrect?
The HTML code is here
<input type="file" id="fileInput" multiple ng-model="imagefile" file-feed/>
The directive for the file-feed attribute is
.directive('fileFeed', [
function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attributes, controller) {
element.bind("change", function(changeEvent) {
//alert("fired");
var files = [];
for (var i = 0; i < element[0].files.length; i++) {
//alert(element[0].files[i].name);
files.push(element[0].files[i]);
}
controller.$modelValue = files;
scope.$apply(function(){
**controller.$modelValue = files;**
console.log(controller.$modelValue[0].name);
});
});
}
};
}
]);
The modal controller is here
.controller('ProfilePictureModalInstanceCtrl', function ($scope, $modalInstance, items, $timeout) {
$scope.imagefile = "";
$scope.checkImageFiles = function(){
console.log($scope.imagefile);
}
$scope.ok = function () {
$modalInstance.close($scope.optionItems);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
})
I have a button in the modal that calls a function in the controller to print out the value of image file.
<button ng-click="checkImageFiles()">Check Images</button>
I am trying to return the file via the controller.modelValue property

How do I properly set the value of my timepicker directive in AngularJS?

I'm trying to create a timepicker directive in AngularJS that uses the jquery timepicker plugin. (I am unable to get any of the existing angular TimePickers to work in IE8).
So far, I was able to get the directive to work as far as updating the scope when a time is selected. However, what I need to accomplish now is getting the input to display the time, rather than the text of the model's value when the page first loads. See below:
this is what shows:
this is what I want:
Here is my directive:
'use strict';
playgroundApp.directive('timePicker', function () {
return {
restrict: 'A',
require: "?ngModel",
link: function(scope, element, attrs, controller) {
element.timepicker();
//controller.$setViewValue(element.timepicker('setTime', ngModel.$modelValue));
//ngModel.$render = function() {
// var date = ngModel.$modelValue ? new Date(ngModel.$modelValue) : null;
//};
//if (date) {
// controller.$setViewValue(element.timepicker('setTime', date));
//}
element.on('change', function() {
scope.$apply(function() {
controller.$setViewValue(element.timepicker('getTime', new Date()));
});
});
},
};
})
The commented code is what I've attempted, but it doesn't work. I get an error that reads, ngModel is undefined. So, to clarify, when the page first loads, if there is a model for that input field, I want the input to show only the time, as it does after a value is selected.
Thanks.
EDIT:
Ok, after making some trial and error changes, my link function looks like this:
link: function (scope, element, attrs, controller) {
if (!controller) {
return;
}
element.timepicker();
var val = controller.$modelValue;
var date = controller.$modelValue ? new Date(controller.$modelValue) : null;
controller.$setViewValue(element.timepicker('setTime', controller.$modelValue));
//ngModel.$render = function () {
// var date = ngModel.$modelValue ? new Date(ngModel.$modelValue) : null;
//};
if (date) {
controller.$setViewValue(element.timepicker('setTime', date));
}
element.on('change', function() {
scope.$apply(function() {
controller.$setViewValue(element.timepicker('getTime', new Date()));
});
});
},
This doesn't give me any errors, but the $modelValue is always NaN. Here is my controller code:
$scope.startTime = new Date();
$scope.endTime = new Date();
and the relevant html:
<input id="startTime" ng-model="startTime" time-picker/>
<input id="endTime" ng-model="endTime" time-picker />
Is there something else I need to do?
I spent several days trying with the same plugin without getting results and eventually I found another:
http://trentrichardson.com/examples/timepicker/
It works perfectly using the following directive:
app.directive('timepicker', function() {
return {
restrict: 'A',
require : 'ngModel',
link : function (scope, element, attrs, ngModelCtrl) {
$(function(){
element.timepicker({
onSelect:function (time) {
ngModelCtrl.$setViewValue(time);
scope.$apply();
}
});
});
}
}
});
I hope you find useful.

AngularJs Directive - Accessing attributes

I love angularjs, but I get so confused with directives lol.
I have the following:
//postcode grabber
app.directive('postcodes',
function ($rootScope, $http) {
return function (scope, element, attrs) {
element.bind('change', function () {
var targetSuburb = scope.$eval(attrs.targetSuburb);
alert(targetSuburb);
var modal_element = angular.element('#myModal');
var ctrl = modal_element.controller();
var url = '/postage/postcodes/?suburb=' + element.val();
ctrl.setModal(url);
modal_element.modal('show');
});
};
});
This my HTML:
<input type="text" class='form-control' ng-model="model.suburb" postcodes id='ca_suburb' target-suburb='ca_suburb' target-state='ca_state' target-postcode='ca_postcode'>
The alert is always "undefined" - Is there something I'm missing to be able to access the attributes correctly?
If you want to access the value of the model then
app.directive('postcodes', function ($rootScope, $http) {
return {
require: 'ngModel',
link: function (scope, element, attrs, controller) {
element.bind('change', function () {
console.log(controller.$viewVaue)
var modal_element = angular.element('#myModal');
var ctrl = modal_element.controller();
var url = '/postage/postcodes/?suburb=' + element.val();
ctrl.setModal(url);
modal_element.modal('show');
});
}
};
});
Demo: Fiddle
If you are looking to alert ca_suberb then just use
    alert(attrs.targetSuburb);
Demo: Fiddle
If ca_suberb is a scope property then your code is working fine
Demo: Fiddle
If you want to get what is typed in, you should access via scope like this
alert(scope.model[attrs.targetSuburb]);
where attrs.targetSuburb should be the field name and it should be set like this since you define the model to be model.suburb
target-suburb='suburb'

Run $watch after custom directive

I'm writing a custom directive to validate some value in the scope. It should work like the required attribute, but instead of validating the input text, it's going to validate a value in the scope. My problem is that this value is set in a $scope.$watch function and this function runs after my directive. So when my directive tries to validate the value it has not been set yet. Is it possible to run the $watch code before running my custom directive?
Here is the code:
var app = angular.module('angularjs-starter', []);
app.controller('MainCtrl', function($scope) {
var keys = {
a: {},
b: {}
};
$scope.data = {};
// I need to execute this before the directive below
$scope.$watch('data.objectId', function(newValue) {
$scope.data.object = keys[newValue];
});
});
app.directive('requiredAttribute', function (){
return {
require: 'ngModel',
link: function(scope, elem, attr, ngModel) {
var requiredAttribute = attr.requiredAttribute;
ngModel.$parsers.unshift(function (value) {
ngModel.$setValidity('requiredAttribute', scope[attr.requiredAttribute] != null);
return value;
});
}
};
});
<input type="text" name="objectId" ng-model="data.objectId" required-attribute="object" />
<span class="invalid" ng-show="myForm.objectId.$error.requiredAttribute">Key "{{data.objectId}}" not found</span>
And here is a plunker: http://plnkr.co/edit/S2NrYj2AbxPqDrl5C8kQ?p=preview
Thanks.
You can schedule the $watch to happen before the directive link function directly. You need to change your link function.
link: function(scope, elem, attr, ngModel) {
var unwatch = scope.$watch(attr.requiredAttribute, function(requiredAttrValue) {
if (requiredAttribute=== undefined) return;
unwatch();
ngModel.$parsers.unshift(function (value) {
ngModel.$setValidity('requiredAttribute', requiredAttrValue != null);
return value;
});
});
}
This approach will activate the $watch function inside the directive only once and will remove the watcher the first time your required scope variable is set.
There is also another approach where you parse the value and check it this way:
link: function(scope, elem, attr, ngModel) {
var parsedAttr = $parse(attr.requiredAttribute);
ngModel.$parsers.unshift(function (value) {
ngModel.$setValidity('requiredAttribute', parsedAttr(scope) != null);
return value;
});
}
Here you will need to use $parse AngularJS service. The difference here is that this will mark the input field as invalid without waiting for first value set on the required scope variable.
Both variants allow you to pass an expression instead of a simple variable name. This makes it possible to write something as required-attribute="object.var1.var2".
It really depends on what you need.

Resources