You are on page 1of 7

Previous

Next

Understanding and fixing AngularJS directive rendering and parsing


Posted on September 24, 2013 by Emil van Galen Tweet

NOTE: This blog post is originally written for AngularJS 1.2.x; in 1.3.x the input not showing invalid model values has been fixed.
Although 1.3.x still has the inconsistencies in how AngularJS parses data entry the solution from this blog post isnt working for
1.3.x but I will try to find a fix for this within the next few weeks.
A while ago I noticed that AngularJS doesnt show invalid model values bound to an <input/>
There is also an open bug report about this: issue #1412 input not showing invalid model values
The bug can be easily illustrated through the following example:
1 <div ng-init="letters='1'">
2
letters = {{'' + letters}}<br>
3
<input type="text" ng-model="letters" ng-pattern="/^[a-zA-Z]*$/" />
4 </div>

While running the example displays letters = 1 but the <input/> element remains empty.
Additionally notice that the <input/> element (due some custom CSS styling) has a red background to indicate that its value is
invalid (since it doesnt match the regex of ng-pattern).
In this blog post I will dig into how AngularJS handles rendering, parsing and validation and will finally provide a workaround /
solution for this AngularJS bug as well as some other improvements.
Using $formatters for validating model values
In order to solve this issue we first need to understand how model values are rendered and validated.
Whenever a model value bound with ngModel changes, AngularJS uses the NgModelController#$formatters array to validate and
format the model value for rendering:
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.
By pushing a function on the $formatters array and using $setValidity(validationErrorKey, isValid) in its
implementation we could easily create a improved version using the following jdOnlyLetters directive that does show invalid
model values:
1 angular.module('jdOnlyLetters', [])
2 .directive('jdOnlyLetters', function () {
3
return {
4
require: 'ngModel',
5
link: function ($scope, $element, $attrs, ngModelController) {
6
ngModelController.$formatters.push(function (value) {
7
var onlyLettersRegex = /^[a-zA-Z]*$/;
8
9
var isValid = typeof value === 'string'
10
&& value.match(onlyLettersRegex);
11
ngModelController.$setValidity('jdOnlyLetters', isValid);
12
return value;
13
});
14
}
15
};
16
});

NOTE: the sample directive doesnt format anything we only want to validate the model value.
See the documentation of $formatters for a sample formatter that formats to upper case.
Understanding the $parsers array of NgModelController
Before we can understand why the ng-pattern="/^[a-zA-Z]*$/" isnt displaying illegal model values (while our jdOnlyLetters
directive does) we first need to understand the NgModelController#$parsers array.
The $parsers array of NgModelController is used for parsing as well as validation upon data entry:
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.
Using the $parsers array we could improve our jdOnlyLetters directly to perform validation upon data entry of the user:
1 angular.module('jdOnlyLetters', [])
2 .directive('jdOnlyLetters', function () {
3
return {
4
require: 'ngModel',
5
link: function ($scope, $element, $attrs, ngModelController) {
6
var onlyLettersRegex = /^[a-zA-Z]*$/;
7
8
ngModelController.$formatters.push(function (value) {
9
var isValid = typeof value === 'string'
10
&& value.match(onlyLettersRegex);
11
ngModelController.$setValidity('jdOnlyLetters', isValid);
12
return value;
13
});
14
15
ngModelController.$parsers.push(function (value) {
16
var isValid = typeof value === 'string'
17
&& value.match(onlyLettersRegex);
18
19
ngModelController.$setValidity('jdOnlyLetters', isValid);
20
return isValid ? value : undefined;
21
});
22
}
23
};
24
});

NOTE: the sample directive above doesnt actually parse nor format anything as its only interested in model value validation.
The parser function is almost exactly the same as the formatter function. However there is one noticeable difference between
the two, a parser function should return a undefined in case of an invalid value.
After investigating the source code of AngularJS it turns out that the ngPattern directive (as well other directives of AngularJS)
use the same function as both a parser and formatter function.
Since this function returns undefined (in case of an invalid value) no value will be shown in the <input/> element.
To fix this I created a input directive that fix the parsing result.
This is done by adding a parser function which is always executed as the last parser function (of the pipeline of parser functions).
Whenever the model value is invalid and the parsed value is undefined this parser function will return the $modelValue instead.
A fully working version of ng-pattern="/^[a-zA-Z]*$/" using this fix can be found here.
Improving inconsistencies in how AngularJS parses data entry
Additionally to AngularJS not showing invalid model value in <input/> elements, I also noticed some inconsistencies in how it uses
data:
In case of an invalid value (due to the undefined returned by the parser function) the model value will be set to undefined
instead of a null value:
In my opinion its a bad practice to set variables and object property to a value of undefined. Instead I prefer that
undefined is only (implicitly) set by JavaScript in case an object property doesnt exist or a variables is not initialized yet.

Furthermore JSON doesnt support undefined, therefor (as it turns out) $resource omits any undefined object property
when doing an HTTP PUT / POST which can cause unexpected issues when using RESTful services.
After emptying the an text of the <input/> element the model value is set to an empty string instead a null value:
to prevent unneeded ambiguity between null and '' (an empty string), I prefer to only use null values instead.

A complete solution containing the fixes for parsing as well as rending values can be found here.
References
AngularJS issue #1412 input not showing invalid model values
[angular.js] Input field with types (number, email etc.) and invalid initial values bug or a feature?
This entry was posted in AngularJS and tagged AngularJS by Emil van Galen. Bookmark the permalink.

About Emil van Galen


My name is Emil van Galen, I work for JDriven. I'm passionate about software development and continuously seeking for
ways to improve code, learn new technologies, improving software design and keeping things fresh.
View all posts by Emil van Galen

12 THOUGHTS ON UNDERSTANDING AND FIXING ANGULARJS DIRECTIVE RENDERING AND PARSING

juju on November 28, 2013 at 09:05 said:

Thanks for the detailed article!


In the 2nd code example please fix line 20 isValue to isValid.
Did you submit a PR to angular for the input pipelines bug with ngPattern?
Thanks !
Reply

Emil van Galen


on November 29, 2013 at 22:51 said:

Thanks for your reply as well as the fix of the sample code.
As for a pull request I did gave it some thought, but didnt get round to creating one.
Furthermore my interest was to see if I could intervene in the formatting / parsing behavior without
having to change source-code; this way I can reuse the solution for both built-in as well as third-party
directives.
Reply

Scotty on December 2, 2013 at 14:46 said:

Brilliant stuff. I like that your solution plugs into the existing angular pipeline of parsers and takes advantage of
the fact that a custom directive gets the last say in the pipeline.
Reply

Maarten on December 4, 2013 at 15:21 said:

Seems that instead of pushing you need to unshift the custom code to the $parsers array (putting it at the
beginning instead of the end)

Reply

Emil van Galen


on December 4, 2013 at 17:09 said:

To make sure that the custom (parser) is executed before any pre-existing $parsers array functions
(assuming they are always push-ed) we do an unshift.
However in the sample jdOnlyLetters directive from the blog post Im just adding a regular parser
function (and not trying to fix pre-existing ones) and therefor we do a regular push.
Reply

Maarten
on December 5, 2013 at 09:34 said:

clear, thanks for the reply


Reply

Morten M. on March 19, 2014 at 20:58 said:

Great stuff! This was very helpful! :) Thanks for writing it up.
Reply

Johan on May 18, 2014 at 21:36 said:

Nice clear example thanks!


Reply

Steffen on July 3, 2014 at 15:12 said:

This approach is outdated (refering to version 1.3.0-beta.13), as angular has an explicit check for invalid
properties:
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue;
In NgModelController$commitViewValue.
http://github.com/angular/angular.js/blob/c90cefe16142d973a123e945fc9058e8a874c357/src/ng/directive/input.js
Reply

Emil van Galen

on July 13, 2014 at 14:01 said:

Steffen,
Thank you very much for your response.
And also for your in-depth pointers
I will (hopefully) find some time next week to dive into it and update the article to reflect the changes
in Angular 1.3.x.
Emil
Reply

MohamedJubair on November 19, 2014 at 13:33 said:

Thanks for the useful info


AngularJS Development
Reply

Marcin on September 24, 2015 at 15:05 said:

Hi
Your first approach works fine when you provide
ng-model-options={ allowInvalid: true }
You dont have to create custom directive to handle that problem.
https://docs.angularjs.org/api/ng/directive/ngModelOptions
allowInvalid: boolean value which indicates that the model can be set with values that did not validate correctly
instead of the default behavior of setting the model to undefined.
Here you have your code with this option:
http://plnkr.co/edit/1E6deo6Llqi2s4rLYXne?p=preview
Marcin
Reply

Leave a Reply
Your email address will not be published. Required fields are marked *

Comment

Name

*
Email

*
Website

Post Comment

Search

J D r i v e n
blog.jdriven.com www.jdriven.com

F e a t u r e d

P o s t s

Safe-guarding AngularJS scopes with ECMAScript 5 Strict Mode


Keynote SpringOne 2GX

R e c e n t

The Lean-Agile Connection

Day two of the SpringOne 2GX in Washington.

AngularJS made me stop hiding from JavaScript

P o s t s

Mission to Mars follow up Grasping AngularJS 1.5 directive bindings by learning from Angular 2
Gradle Goodness: Source Sets As IntelliJ IDEA Modules Gradle Goodness: Add Spring Facet To IntelliJ IDEA Module
Gradle Goodness: Set VCS For IntelliJ IDEA In Build File

T a g s
agile

AngularJS Annotations ApplicationContext asciidoc asciidoctor Bean coding controller Git Google Guava

Gradle Grails Grails 3 Grape Groovy groovy 2.3 Groovy 2.4 HTML intellij intellij idea Jasmine Java

JavaScript Logging Maven Mockito Ratpack rest RESTful scala scrum Spock Spray Spring Spring Boot spring
integration SpringOne 2GX TDD Test Driven Development

testing unittest verify Websockets XML

C a t e g o r i e s
Agile (5) Akka (7) Ant (1) Apache Hadoop (1) Caching (1)
Coding (333)
Gradle (55) Grails (69) Groovy (69) Java (29) JUnit (2) Maven (7) Ratpack (38) Scala (17) Spock (20)
Spring (21)

TDD (12) Wicket (1)

Roo (1) WS (1)


DevOps (1) Devoxx13 (1) Documenting (31)

FE (3) Featured (6) Front End (9)

Asciidoc (14) Asciidoctor (22)


Hibernate (2) IDE (13)
IntelliJ IDEA (13)

Geb (1)

Canvas (1) GemFire (1)

IoT (1) Javascript (25)


AngularJS (19)

Cujo (1) Jasmine (3) Shadow Dom (1)

Transclude (1)
JFokus (1) JoyOfCoding (2) Mobile (1) News (1) NoSQL (2)

OS X (1)

HBase (1) MongoDB (1) Neo4j (1) Redis (1) RabbitMQ (1)
Refactoring (2) REST (10) Scrum (5) Security (3) Spring HATEOAS (1) SpringOne (1) Testing (6)

Vert.x (1)

Geb (1)
Webcomponent (1)

A r c h i v e s
April 2016 March 2016 February 2016 January 2016 November 2015 October 2015 September 2015 August 2015
July 2015 June 2015 May 2015 April 2015 March 2015 February 2015 January 2015 December 2014
November 2014 October 2014 September 2014 August 2014 July 2014 June 2014 May 2014 April 2014
March 2014 February 2014 December 2013 November 2013 October 2013 September 2013 August 2013 July 2013
June 2013 May 2013 April 2013 March 2013 February 2013 January 2013 December 2012 November 2012
October 2012 September 2012

You might also like