If I use ng-model for an input field and empty it, angular sets it to '' instead of null even if it was null before.
This is a problem in my case because I need to send null to the server if the input field is empty, and not ''.
Is there a way to tell angular setting "empty" input models to null?
You may want to $watch for the empty value and set it to null:
<input type="text" ng-model="test.value" />
$scope.$watch('test.value', function (newValue, oldValue) {
if(newValue === "")
$scope.test.value = null;
});
Another way would be to use a custom directive and apply a $parsers in it:
<input type="text" ng-model="test.value" empty-to-null />
myApp.directive('emptyToNull', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, elem, attrs, ctrl) {
ctrl.$parsers.push(function(viewValue) {
if(viewValue === "") {
return null;
}
return viewValue;
});
}
};
});
Related
I'm trying to implement an asynchronous validation by using a custom directive.
This is the directive
moduloArea.directive('uniqueName', function($http, $q) {
return {
require : 'ngModel',
link: function($scope, element, attrs, ngModel) {
ngModel.$asyncValidators.nombre = function(modelValue, viewValue) {
return $http.get('/checkUsernameAvailability/'+viewValue).then(
function(response) {
if (!response.data.validUsername) {
return $q.reject(response.data.errorMessage);
}
return true;
}
);
};
}
};
});
The result in console
As you can see when the root username is typed the return is an JSON object because this username is already taken.
But in the HTML the form in $invalid when the directive unique-name is inserted.
<form name="registerUsernameForm" novalidate="novalidate">
<input type="text" name="username" data-ng-model="person.userName" data-unique-name="" required="required"/>
<span data-ng-show="registerUsernameForm.username.$error.uniqueName">This username is already taken.</span>
<button type="submit" data-ng-disabled="registerUsernameForm.$invalid || registerUsernameForm.$pending" data-ng-click="registerPerson(person)"> Save Person </button>
</form>
I'm using the data-unique-name="" (="") because if I don't then thymeleaf generate the following error:
Attribute name "data-unique-name" associated with an element type
"input" must be followed by the ' = ' character.
What do you think can be wrong?
You are validating nombre not uniqueName. For this reason, uniqueName is still stay invalid.
moduloArea.directive('uniqueName', function($http, $q) {
return {
require : 'ngModel',
link: function($scope, element, attrs, ngModel) {
ngModel.$asyncValidators.uniqueName= function(modelValue, viewValue) {
var value = modelValue || viewValue;
return $http.get('/checkUsernameAvailability/'+value ).then(
function resolved(response) {
if (response.data && !response.data.validUsername) {
return $q.reject(response.data.errorMessage);
}
return true;
}, function rejected() {
//username does not exist, therefore this validation passes
return true;
}
);
};
}
};
});
Here is my custom directive:
angular
.module('accountApp')
.directive('uniqueRecord', function($q, $timeout, $http) {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$asyncValidators.uniqueRecord = function(modelValue, viewValue) {
var value = modelValue || viewValue;
var attributes = scope.$eval(attrs.uniqueRecord);
// Lookup effect by name
return $http.get(attributes.url + '/' + value + '/' + ((attributes.currentRecordName == '' || attributes.currentRecordName == 'nothing') ? '_' : attributes.currentRecordName))
.then(function resolved() {
//username exists, this means validation fails
return $q.reject('exists');
}, function rejected() {
//username does not exist, therefore this validation passes
return true;
});
};
}
}
});
Here is the HTML:
<input type="text" id="name" name="name" class="form-control form-input" ng-model="effect.name"
ng-disabled="isReadOnly" required
unique-record="{ url: '/api/effect', currentRecordName: {{currentEffectName == '' ? 'nothing' : currentEffectName}} }"
ng-uniqueRecord-err-type="duplicateRecord"/>
As you can see in the above HTML, I am passing the value of currentRecordName to directive. In directive the value of url is passed as is but the value of currentRecordName is always undefined. Why?
The problem comes from your utilisation of mustache in the evaled expression.
An evaled expression is already considered as an angular expression and does not need the mustache.
I suggest you to change your html to :
<input type="text" id="name" name="name" class="form-control form-input" ng-model="effect.name"
ng-disabled="isReadOnly" required
unique-record="{ url: '/api/effect', currentRecordName: currentEffectName }"
ng-uniqueRecord-err-type="duplicateRecord"/>
And handle the currentEffectName vs 'nothing' directly in the directive code.
var attributes = scope.$eval(attrs.uniqueRecord);
if (!attributes.currentRecordName) {
attributes.currentRecordName = 'nothing';
}
something similar may have been answered (ng-pattern + ng-change) but all responses were unable to fix this issue.
I have two imbricated directives for creating a form input, a parent directive to control name, label, validator etc. and a child directive to set pattern and input type specific stuff.
However, when setting a pattern, the value on my model is set to undefined when ng-pattern return false.
Directives:
<input-wrapper ng-model="vm.customer.phone" name="phone" label="Phone number">
<input-text type="tel"></input-text>
</input-wrapper>
Generated HTML:
<label for="phone">Phone number:</label>
<input type="text" name="phone"
ng-model="value"
ng-model-options="{ updateOn: \'blur\' }"
ng-change="onChange()"
ng-pattern="/^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/">
JS:
angular.module('components', [])
.directive('inputWrapper', function() {
return {
restrict: 'E',
require: 'ngModel',
scope: true,
link: function (scope, element, attrs, ngModel) {
scope.name = attrs.name;
scope.label = attrs.label;
scope.onChange = function () {
ngModel.$setViewValue(scope.value);
};
ngModel.$render = function () {
scope.value = ngModel.$modelValue;
};
}
}
})
.directive('inputText', function() {
return {
restrict: 'E',
template: '<label for="{{name}}">{{label}}:</label><input type="text" name="{{name}}" ng-model="value" ng-model-options="{ updateOn: \'blur\' }" ng-change="onChange()" ng-pattern="pattern">',
link: function (scope, element, attrs) {
if (attrs.type === 'tel') {
scope.pattern = /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/;
}
}
}
});
angular.module('app',['components'])
.controller('ctrl',function($scope){
var vm = this;
vm.customer = {
phone: '010203040506'
};
});
What am I doing wrong ?
Codepen for use case: https://codepen.io/Yosky/pen/yVrmvw
By default in angular if a validator fail, undefined value assigned to ng-model, You can change this setting as follow :
<div ng-model-options="{ allowInvalid: true}">
read here for detail docs
I had some requirements that meant that I really really didn't want ng-model to write out undefined to the scope when validation was invalid, and I didn't want the invalid value either, so allowInvalid didn't help. In stead I just wanted ng-model do not write anything, but I couldn't find any option for this.
So I couldn't see any way forward except for doing some monkey patching of the ng-model controller.
So first I required ngModel in the component I was building require: { model: 'ngModel' } and then I did this in the $onInit hook:
const writeModelToScope = this.model.$$writeModelToScope;
const _this = this;
this.model.$$writeModelToScope = function() {
const allowInvalid = _this.model.$options.getOption('allowInvalid');
if (!allowInvalid && _this.model.$invalid) {
return;
}
writeModelToScope.bind(this)();
};
I also didn't want to take in a new model value while the value was invalid and the component had focus, so I did:
const setModelValue = this.model.$$setModelValue;
this.model.$$setModelValue = function(modelValue) {
_this.lastModelValue = modelValue;
if (_this.model.$invalid) {
return;
}
if (_this.hasFocus) {
return;
}
setModelValue.bind(this)(modelValue);
};
element.on('focus', () => {
this.hasFocus = true;
});
element.on('blur', (event) => {
this.hasFocus = false;
const allowInvalid = this.model.$options.getOption('allowInvalid');
if (!allowInvalid && this.model.$invalid) {
this.value = this.lastModelValue;
}
event.preventDefault();
});
Feel free to judge me, just know that I already feel dirty.
I want to assign ng-model variable based on condition. For example:
<input type="text" ng-model="item.model[multilang]" >
$scope.multilang can be "ENG","JP"(languages) or false. For example, if multilang = "ENG" and user type in input "Hello",the result will be
item.model = {ENG:"Hello"}
The problem is when $scope.multilang = false , I want the result would be
item.model = "Hello"
I can't find a way to achieve above result. I think one solution is changing ng-model based on $scope.multilang so when it's false,it will change ng-model of input to be = ng-model="item.model" but I don't know how to do this.
EDITED
I thought of one solution:
<input ng-if="multilang" type="text" ng-model="item.model[multilang]" >
<input ng-if="!multilang" type="text" ng-model="item.model" >
but there is better way to achieve ?
-----plnkr example-----
Angular is very flexible and powerful framework. You should use custom directive and ngModel's getter/setter option.
Directive without ngModel's getter/setter may look like this:
<input type="text"
ng-model="val"
multilang="multilang"
multilang-model="item.model">
Directive code:
.directive('multilang', [function(){
return {
restrict: "A",
require: "ngModel",
scope: {
multilang: "=",
multilangModel: "="
},
link: function(scope, element, attr, ngModel){
ngModel.$viewChangeListeners.push(function()){
var value = ngModel.$modelValue;
if(scope.multilang !== false) {
if(typeof scope.multilangModel == 'undefined')
scope.multilangModel = Object.create(null)
scope.multilangModel[scope.multilang] = value
}
else {
scope.multilangModel = value
}
})
}
}
}])
--forked plunkr--
In case of using ngModel's getter/setter
<input type="text"
ng-model="val"
multilang="multilang"
multilang-model="item.model"
ng-model-options="{ getterSetter: true }">
Directive code:
.directive('multilang', [function(){
return {
restrict: "A",
scope: {
multilang: "=",
multilangModel: "=",
val: "=ngModel"
},
link: function(scope, element, attr){
scope.val = function(newValue) {
if(scope.multilang !== false) {
if(typeof scope.multilangModel == 'undefined')
scope.multilangModel = Object.create(null)
return arguments.length ? (scope.multilangModel[scope.multilang] = newValue) : scope.multilangModel[scope.multilang];
}
else {
return arguments.length ? (scope.multilangModel = newValue) : scope.multilangModel;
}
}
}
}
}])
--forked plunkr--
In my opinion, second one is better. It has two way binding with item.model and changes input value when item.model was changed in other place of code.
Try this:
<input ng-show="multilang" type="text" ng-model="item.model[multilang]" >
<input ng-hide="multilang" type="text" ng-model="item.model" >
i have this directive
angular.module('tutors.components')
.directive('formValidator', function() {
return {
restrict: 'A'
link: function (scope, element, attrs) {
function nameValidator (input) {
var regExp : "/^[a-zA-Z']*$/";
var validator : regExp.test(input);
return validator;
}
};
});
And this html
<input form-Validator
type="text"
required
name="firstName"
class="form-control"
ng-maxlength="20"
placeholder="First name"
ng-model="user.firstName"
ng-disabled="vm.isLoading"
tooltip="{{requiredMsg}}"
tooltip-placement="top"
tooltip-trigger="focus"
tooltip-enable="signupForm.firstName.$error.required && signupForm.firstName.$touched">
and i want to run the directive so it validates the text entered in the input, i have tried using an ng-pattern like this ng-pattern="/[a-zA-Z]+/" but that didn't work (i still prefer using it over the directive)
Your directive should be be having small case form-validator to get call to the directive by compiler.
Additionally you need to change your nameValidator function code to below.
.directive('formValidator', function() {
return {
restrict: 'A',
require: 'ngModel', //to get ngModelController
link: function(scope, element, attrs, ngModel) {
function nameValidator(input) {
var regExp = "/^[a-zA-Z]$/";
var validator = regExp.test(input);
return validator;
}
ngModel.$validators.push(nameValidator); //should pushed to validators to validate
}
}
})
OR ng-pattern should be fine, only it should have ^(start) & $(end)
ng-pattern="/^[a-zA-Z]*$/"
Demo Plunkr