ngModel Formatters and Parsers - angularjs

I posted the same question in different form, but no one answered. I am not getting a clear picture of what the Formatters and Parsers do in angular js.
By the definition, both the Formatters and Parsers look similar to me. Maybe I am wrong, as I am new to this angularjs.
Formatters Definition
Array of functions to execute, as a pipeline, whenever the model value changes.
Each function is called, in turn, passing the value through to the next.
Used to format / convert values for display in the control and validation.
Parsers Definition
Array of functions to execute, as a pipeline, whenever the control reads value from the DOM.
Each function is called, in turn, passing the value through to the next.
Used to sanitize / convert the value as well as validation.
For validation, the parsers should update the validity state using $setValidity(), and return undefined for invalid values.
Please help me to understand both features with a simple example. A simple illustration of both will be appreciated.

This topic was covered really well in a related question: How to do two-way filtering in AngularJS?
To summarize:
Formatters change how model values will appear in the view.
Parsers change how view values will be saved in the model.
Here is a simple example, building on an example in the NgModelController api documentation:
//format text going to user (model to view)
ngModel.$formatters.push(function(value) {
return value.toUpperCase();
});
//format text from the user (view to model)
ngModel.$parsers.push(function(value) {
return value.toLowerCase();
});
You can see it in action: http://plnkr.co/UQ5q5FxyBzIeEjRYYVGX?plnkr=legacy
<input type="button" value="set to 'misko'" ng-click="data.name='misko'"/>
<input type="button" value="set to 'MISKO'" ng-click="data.name='MISKO'"/>
<input changecase ng-model="data.name" />
When you type a name in (view to model), you will see that the model is always lowercase. But, when you click a button and programatically change the name (model to view), the input field is always uppercase.

Another usage for formatters and parsers is when you want to store dates in UTC time and display them in local time on inputs, I created the below datepicker directive and utcToLocal filter for this.
(function () {
'use strict';
angular
.module('app')
.directive('datepicker', Directive);
function Directive($filter) {
return {
require: 'ngModel',
link: function (scope, element, attr, ngModel) {
element.addClass('datepicker');
element.pickadate({ format: 'dd/mm/yyyy', editable: true });
// convert utc date to local for display
ngModel.$formatters.push(function (utcDate) {
if (!utcDate)
return;
return $filter('utcToLocal')(utcDate, 'dd/MM/yyyy');
});
// convert local date to utc for storage
ngModel.$parsers.push(function (localDate) {
if (!localDate)
return;
return moment(localDate, 'DD/MM/YYYY').utc().toISOString();
});
}
};
}
})();
It uses this utcToLocal filter that ensures the input date is in the correct format before converting to local time.
(function () {
'use strict';
angular
.module('app')
.filter('utcToLocal', Filter);
function Filter($filter) {
return function (utcDateString, format) {
if (!utcDateString) {
return;
}
// append 'Z' to the date string to indicate UTC time if the timezone isn't already specified
if (utcDateString.indexOf('Z') === -1 && utcDateString.indexOf('+') === -1) {
utcDateString += 'Z';
}
return $filter('date')(utcDateString, format);
};
}
})();
moment.js is used to convert local to utc dates.
pickadate.js is the datepicker plugin used

Related

AngularJS input date french

I wanted to use an input[date] inside a form to enable users to use the modern browsers' support, without using a javascript datepicker.
For the ones who're using not supporting browsers, I'v set ui-mask. Both work together.
I use the french format (dd/mm/YYYY).
<input type="date" ng-model="myDate" ui-mask="99/99/9999">
$scope.myDate = new Date("2016-02-12"); // works
$scope.myDate = "12/02/2016"; // Doesn't...
In an empty form, no problem, Angular accept it.
But with a filled form, Angular expects a date format I don't want : YYYY-mm-dd (javascript date object).
I can't understand why Angular only provides support that format, and not others like french format. Because I want the input[date] to show the french format, not the standard one !
Many modern browsers provides interesting support for dates, I can't understand why Angular waste that.
Or maybe I missed something ?
To achieve your goal need to use momentJs and ngModel $parsers and $formatters. Below some code and JSFiddle example:
In your controller you prepare some string value in french date format:
$scope.myDate= '12/02/2016';
In view you set this value to ngModel attribute of input[type=date] element:
<input class="form-control" type="date" ng-model="myDate">
You need to create new directive, it can be assigned to input element:
.directive('input', function(dateFilter) {
return {
restrict: 'E',
require: '?ngModel',
link: function(scope, element, attrs, ngModel) {
if (
'undefined' !== typeof attrs.type && 'date' === attrs.type && ngModel
) {
ngModel.$formatters.push(function(modelValue) {
return moment(modelValue, "DD/MM/YYYY").toDate();
});
ngModel.$parsers.push(function(viewValue) {
return moment(viewValue).format("DD/MM/YYYY");
});
}
}
}
})
New function in $formatters array modifies your myDate to javascript date object (with moment parser it's pretty simple). New function in $parsers array modifies value of input element back to french formatted date string.
This solution provides two way binding between your myDate and input[type=date] element. :)
Look at JSFiddle exaple

How to use the last valid modelValue if a model becomes invalid?

I'm working on an application that saves changes automatically when the user changes something, for example the value of an input field. I have written a autosave directive that is added to all form fields that should trigger save events automatically.
template:
<input ng-model="fooCtrl.name" autosave>
<input ng-model="fooCtrl.email" autosave>
directive:
.directive('autosave', ['$parse', function ($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
function saveIfModelChanged () {
// save object containing name and email to server ...
}
ngModel.$viewChangeListeners.push(function () {
saveIfModelChanged();
});
}
};
}]);
So far, this all works fine for me. However, when I add validation into the mix, for example validating the input field to be a valid email address, the modelValue is set to undefined as soon as the viewValue is changed to an invalid email address.
What I would like to do is this: Remember the last valid modelValue and use this when autosaving. If the user changes the email address to be invalid, the object containing name and email should still be saved to the server. Using the current valid name and the last valid email.
I started out by saving the last valid modelValue like this:
template with validation added:
<input type="email" ng-model="fooCtrl.name" autosave required>
<input ng-model="fooCtrl.email" autosave required>
directive with saving lastModelValue:
.directive('autosave', ['$parse', function ($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
var lastModelValue;
function saveIfModelChanged () {
// remeber last valid modelValue
if (ngModel.$valid) {
lastModelValue = ngModel.$modelValue;
}
// save object containing current or last valid
// name and email to server ...
}
ngModel.$viewChangeListeners.push(function () {
saveIfModelChanged();
});
}
};
}]);
My question is, how to use lastModelValue while saving, but preserving the invalid value in the view?
EDIT:
Another possibility, as suggested by Jugnu below, would be wrapping and manipulating the build in validators.
I tried to following: wrap all existing validators and remember the last valid value, to restore it if validations fails:
Object.keys(ngModel.$validators).forEach(function(validatorName, index) {
var validator = ngModel.$validators[validatorName];
ngModel.$validators[validatorName] = createWrapper(validatorName, validator, ngModel);
});
function createWrapper(validatorName, validator, ngModel){
var lastValid;
return function (modelValue){
var result = validator(modelValue);
if(result) {
lastValid = modelValue;
}else{
// what to do here? maybe asign the value like this:
// $parse(attrs.ngModel).assign(scope, lastValid);
}
return result;
};
}
But I'm not sure how to continue with this approach either. Can I set the model value without AngularJS kicking in and try to validate that newly set value?
I have created a simple directive that serves as a wrapper on the ng-model directive and will keep always the latest valid model value. It's called valid-ng-model and should replace the usage of ng-model on places where you want to have the latest valid value.
I've created an example use case here, I hope you will like it. Any ideas for improvements are welcomed.
This is the implementation code for valid-ng-model directive.
app.directive('validNgModel', function ($compile) {
return {
terminal: true,
priority: 1000,
scope: {
validNgModel: '=validNgModel'
},
link: function link(scope, element, attrs) {
// NOTE: add ngModel directive with custom model defined on the isolate scope
scope.customNgModel = angular.copy(scope.validNgModel);
element.attr('ng-model', 'customNgModel');
element.removeAttr('valid-ng-model');
// NOTE: recompile the element without this directive
var compiledElement = $compile(element)(scope);
var ngModelCtrl = compiledElement.controller('ngModel');
// NOTE: Synchronizing (inner ngModel -> outside valid model)
scope.$watch('customNgModel', function (newModelValue) {
if (ngModelCtrl.$valid) {
scope.validNgModel = newModelValue;
}
});
// NOTE: Synchronizing (outside model -> inner ngModel)
scope.$watch('validNgModel', function (newOutsideModelValue) {
scope.customNgModel = newOutsideModelValue;
});
}
};
});
Edit: directive implementation without isolate scope: Plunker.
Since you are sending the entire object for each field modification, you have to keep the last valid state of that entire object somewhere. Use case I have in mind:
You have a valid object { name: 'Valid', email: 'Valid' }.
You change the name to invalid; the autosave directive placed at the name input knows its own last valid value, so the correct object gets sent.
You change the email to invalid too. The autosave directive placed at the email input knows its own last valid value but NOT that of name. If the last known good values are not centralized, an object like { name: 'inalid', email: 'Valid' } will be sent.
So the suggestion:
Keep a sanitized copy of the object you are editing. By sanitized I mean that any invalid initial values should be replaced by valid pristine ones (e.g. zeros, nulls etc). Expose that copy as a controller member, e.g. fooCtrl.lastKnowngood.
Let autosave know the last known good state, e.g. as:
<input ng-model="fooCtrl.email" autosave="fooCtrl.lastKnowngood" required />
Keep the last known good local value in that object; utilize the ng-model expression, e.g. as:
var lastKnownGoodExpr = $parse(attrs.autosave);
var modelExpr = $parse(attrs.ngModel);
function saveIfModelChanged () {
var lastKnownGood = lastKnownGoodExpr(scope);
if (ngModel.$valid) {
// trick here; explanation later
modelExpr.assign({fooCtrl: lastKnownGood}, ngModel.$modelValue);
}
// send the lastKnownGood object to the server!!!
}
Send the lastKnownGood object.
The trick, its shortcomings and how can it be improved: When setting the local model value to the lastKnownGood object you use a context object different than the current scope; this object assumes that the controller is called fooCtrl (see the line modelExpr.assign({fooCtrl: lastKnownGood}, ...)). If you want a more general directive, you may want to pass the root as a different attribute, e.g.:
<input ng-model="fooCtrl.email" autosave="fooCtrl.lastKnowngood" required
autosave-fake-root="fooCtrl" />
You may also do some parsing of the ng-model expression yourself to determine the first component, e.g. substring 0 → 1st occurence of the dot (again simplistic).
Another shortcoming is how you handle more complex paths (in the general case), e.g. fooCtrl.persons[13].address['home'].street - but that seems not to be your use case.
By the way, this:
ngModel.$viewChangeListeners.push(function () {
saveIfModelChanged();
});
can be simplified as:
ngModel.$viewChangeListeners.push(saveIfModelChanged);
Angular default validators will only assign value to model if its valid email address.To overcome that you will need to override default validators.
For more reference see : https://docs.angularjs.org/guide/forms#modifying-built-in-validators
You can create a directive that will assign invalide model value to some scope variable and then you can use it.
I have created a small demo for email validation but you can extend it to cover all other validator.
Here is fiddle : http://plnkr.co/edit/EwuyRI5uGlrGfyGxOibl?p=preview

AngularJS setting model value from directive and calling a parent scope function holds on to the previous value inside that function

js fiddle http://jsfiddle.net/suras/JzaV9/4/
This is my directive
'use strict';
barterApp.directive('autosuggest', function($timeout, $http) {
return {
restrict: "E",
scope: {
modelupdate:"=",
suggestions:"=",
urlsend:"#"
},
template: '<ul><li ng-repeat="suggest in suggestions" ng-click="updateModel(suggest)">{{suggest}}</li></ul>',
link: function (scope, element) {
scope.$watch('modelupdate', function() {
$timeout(function(){
$http.post(scope.urlsend, {q:scope.modelupdate}).then(function(data){
scope.suggestions = data.data;
console.log(data.data);
});
}, 3000);
});
scope.updateModel = function(value){
scope.modelupdate = value;
scope.$parent.getBookInfo();
}
}
};
});
controller is
barterApp.controller('openLibraryCtrl', ['$scope','$http',function ($scope,$http) {
$scope.title = "";
$scope.getBookInfo = function(value){
if($scope.title == "" || $scope.title == " ") //here title is 'r'(previous value)
{
return;
}
$http.get('/book_info.json?q='+$scope.title).then(function(res){
if(Object.keys(res).length !== 0)
{
data = res.data
console.log(data);
}
});
}
//here title is 'rails' (updated value from directive).
//( used a watch function here on model update
// and checked it but inside getBookInfo it is still 'r' )
}]);
in the update model function i set the model value and call the getBookInfo function on parent scope. but the thing here is when (this is a autocomplete) i enter the value in a input field that contains ng-model say for example 'r' then triggers the watch and i get suggestions from a post url (lets say "rails", "rock") and show it through the template as in the directive. when i click one of the suggestions (say 'rails') it triggers the updatemodel function in directive and sets the model value. its fine upto this but when i call the getBookInfo function in parent scope then $scope.title is 'r' inside the function (i checked with console log outside the function the model value was updated correctly as 'rails' ). again when i click 'rock' the model value inside getBookInfo is 'rails'.
i have no clue whats going on. (i also tested with watch function in controller the model gets updated correctly but the function call to getBookInfo holds back to the previous value)
view
<form ng-controller="openLibraryController">
<input type="text" ng-model="title" id="title" name="book[title]" />
<autosuggest modelupdate = "title" suggestions = "book_suggestions" urlsend="/book_suggestions.json"> </autosuggest>
</form>
I didn't look deep into it, but I suspect (with a high degree of confidence) that the parent scope has not been updated at the time of calling getBookInfo() (since we are still in the middle of a $digest cycle).
Not-so-good Solution 1:
You could immediately update the parent scope as well (e.g. scope.$parent.title = ...), but this is clearly a bad idea (for the same reasons as nr 2, but even more so).
Not-so-good Solution 2:
You could pass the new title as a parameter to getBookInfo().
Both solutions result in mixing controller code with directive code and creating a tight coupling between your components, which as a result become less reusable and less testable.
Not-so-bad Solution:
You could watch over the title and call getBookInfo() whenever it changes:
$scope.$watch('title', function (newValue, oldValue) {
getBookInfo();
});
This would be fine, except for the fact that it is totally unnecessary.
Better Solution:
Angular is supposed to take care of all that keep-in-sync stuff for us and it actually does. You don't have given much context on what is the purpose of calling getBookInfo(), but I am guessing you intend to update the view with some info on the selected book.
In that case you could just bind it to an element (using ng-bind) and Angular will make sure it is executed properly and timely.
E.g.:
<div>Book info: <span ng-bind="getBookInfo()"></span></div>
Further more, the autosuggest directive doesn't have to know anything about it. It should only care about displaying suggestions, manipulating the DOM (if necessary) and updating the specified model property (e.g. title) whenever a suggestion is clicked. What you do with the updated value should be none of its business.
(BTW, ideally the suggestions should be provided by a service.)
Below is a modified example (based on your code) that solves the problem. As stated above there are several methods of solving the problem, I just feel this one tobe cleaner and more aligned to the "Angular way":
Book title: <input type="text" ng-model="book.title" />
<autosuggest modelupdate="book.title"
suggestions="book.suggest()"></autosuggest>
Book info: <span ng-bind="book.getInfo()"></span>
Just by looking at the HTML (without knowing what is in JS), one can easily tell what is going on:
There is a text-field bound to book.title.
There is a custom autosuggest thingy that offers suggestions provided by book.suggest() and updates book.title.
There is a span that displays info about the book.
The corresponding directive looks like this:
app.directive('autosuggest', function() {
return {
restrict: 'E',
scope: {
modelupdate: '=',
suggestions: '&'
},
template:
'<ul><li ng-repeat="suggest in suggestions()" ' +
'ng-click="modelupdated = suggest">' +
'{{suggest}}</li></ul>'
};
});
As you can see, all the directive knows about is how to retrieve suggestions and what to update.
Note that the same directive can be used with any type of "suggestables" (even ones that don't have getBookInfo()); just pass in the right attributes (modelupdated, suggestions).
Note also, that we could remove the autosuggest element and the app would continue to work as expected (no suggestions of cource) without any further modification in HTML or JS (while in your version the book info would have stopped updating).
You can find the full version of this short demo here.

Input field number format AngularJS

I have the following input field:
<input type="text" class="span2" ng-model="mynumber">
mynumber has the value 0.55 which is loaded on pageload from a rest service. My problem is now, how can I format the number for different languages/countries? For example, in German, the value should be formatted with a comma (,) instead of a period (.). And if the user changes the number the number should be converted to . instead of ,, if I send it back to the rest service.
This should also work for larger numbers like 90,000.00, which should be 90.000,00 in German...
If I use the angular-locale_de-at.js, I can format the number on a normal output with this:
{{mynumber | number}}
but that does not work for an input field.
How can I handle this? The values should be (printed) formatted in the input field.
If I canage the type of the input field to number
<input type="number" class="span2" ng-model="mynumber">
it works in chrome but not in IE or FF. in chrome i get 0,55. but not in other browsers.
any ideas?
I've written the directive you are looking for. See the code on GitHub.
Also see this answer on SO
Using AngularJS directive to format input field while leaving scope variable unchanged
It will probably not work with <input type="number"/>, use <input type="text"/> instead
Mh,
my first idea would be to separate the concerns here. You're having the model mynumber on the one side and the representation on the other side. Those are distinct from my point of view.
So what you can do is to introduce a directive (I once did this for date values in AngularJS 1.1.5, so bit a bit of additional hacking could be necessary):
First we introduce a directive:
<input type="text" class="span2" ng-model="mynumber" number-format>
with the code:
var app = angular.module("your.directives", ['your.filters']);
app.directive("numberFormat", [
'$filter', function(filter) {
return {
replace: false,
restrict: "A",
require: "?ngModel",
link: function(scope, element, attrs, ngModel) {
var numberFormat;
if (!ngModel) {
return;
}
ngModel.$render = function() {
return element.val(ngModel.$viewValue);
};
var numberFilter = filter('myNumberFilter');
return ngModel.$formatters.push(function(value) {
return numberFilter(value);
});
}
};
}
]);
What you need for this, is a working myNumberFilter filter present, which could decide based on the language given (however you determine this) how to format the input value.
EDIT: I noticed, that AngularJS has it's own number filter, so i changed the name called. Let's add our own:
var app = angular.module("your.filters", []);
app.filter("myNumberFilter", ['SiteLanguage',
function(siteLanguage) {
return function(number) {
var language = siteLanguage.getLanguage(); //I assume you have a service that can tell you the current language
switch(language) {
case "de":
// return the variant of number for "de" here
break;
case "fr":
// return the variant of number for "fr" here
break;
default:
// return english variant here.
break;
};
}
]
});
You'll probably need a formatter function for the individual countries (instead of blindly relying on angular locale. And you probably need a service SiteLanguage which will give you the language on the current site.
Hope this helps :)

What's the difference between ngModel.$modelValue and ngModel.$viewValue

I have the following ckEditor directive. At the bottom are two variations that I have seen from examples on how to set the data in the editor:
app.directive('ckEditor', [function () {
return {
require: '?ngModel',
link: function ($scope, elm, attr, ngModel) {
var ck = null;
var config = attr.editorSize;
if (config == 'wide') {
ck = CKEDITOR.replace(elm[0], { customConfig: 'config-wide.js' });
} else {
ck = CKEDITOR.replace(elm[0], { customConfig: 'config-narrow.js' });
}
function updateModel() {
$scope.$apply(function () {
ngModel.$setViewValue(ck.getData());
});
}
$scope.$on('modalObjectSet', function (e, modalData) {
// force a call to render
ngModel.$render();
});
ck.on('change', updateModel);
ck.on('mode', updateModel);
ck.on('key', updateModel);
ck.on('dataReady', updateModel);
ck.on('instanceReady', function () {
ngModel.$render();
});
ck.on('insertElement', function () {
setTimeout(function () {
$scope.$apply(function () {
ngModel.$setViewValue(ck.getData());
});
}, 1000);
});
ngModel.$render = function (value) {
ck.setData(ngModel.$modelValue);
};
ngModel.$render = function (value) {
ck.setData(ngModel.$viewValue);
};
}
};
}])
Can someone tell me what is the difference between:
ck.setData(ngModel.$modelValue);
ck.setData(ngModel.$viewValue);
And which should I use. I looked at the angular documentation and it says:
$viewValue
Actual string value in the view.
$modelValue
The value in the model, that the control is bound to.
I have no idea what the author meant when he wrote this in the document :-(
You are looking at the correct documentation, but it might just be that you're a little confused. The $modelValue and $viewValue have one distinct difference. It is this:
As you already noted above:
$viewValue: Actual string (or Object) value in the view.
$modelValue: The value in the model, that the control is bound to.
I'm going to assume that your ngModel is referring to an <input /> element...? So your <input> has a string value that it displays to the user, right? But the actual model might be some other version of that string. For example, the input might be showing the string '200' but the <input type="number"> (for example) will actually contain a model value of 200 as an integer. So the string representation that you "view" in the <input> is the ngModel.$viewValue and the numeric representation will be the ngModel.$modelValue.
Another example would be a <input type="date"> where the $viewValue would be something like Jan 01, 2000 and the $modelValue would be an actual javascript Date object that represents that date string. Does that make sense?
I hope that answers your question.
You can see things like this :
$modelValue is your external API, that is to say, something exposed to your controller.
$viewValue is your internal API, you should use it only internally.
When editing $viewValue, the render method won't be called, because it is the "rendered model". You will have to do it manually, whereas the rendering method will be called automatically upon $modelValue modifications.
However, the information will remain consistent, thanks to $formatters and $parsers :
If you change $viewValue, $parsers will translate it back to
$modelValue.
If you change $modelValue, $formatters will
convert it to $viewValue.
Angular has to keep track of two views of ngModel data- there's the data as seen by the DOM(browser) and then there's Angular's processed representation of those values. The $viewValue is the DOM side value. So, for example, in an <input> the $viewValue is what the user typed in to their browser.
Once someone types something in <input> then $viewValue is processed by $parsers and turned into Angular's view of the value which is called $modelValue.
So you can think of $modelValue being angular's processed version of the value, the value you see in the model, while $viewValue is the raw version.
To take this one step further imagine we do something that changes the $modelValue. Angular sees this change and calls $formatters to create an updated $viewValue (based on the new $modelValue) to be sent to the DOM.

Resources