Controls (input, select, textarea) are a way for users to enter data. Form is a collection of these controls, with the purpose of grouping related controls.
Forms and controls provide verification services, so users can receive prompts for invalid input. This provides a better user experience as users can get feedback immediately and know how to fix the error. Remember that while client verification plays an important role in providing a good user experience, it can be simply bypassed, so client verification is untrustworthy. Server-side verification is still necessary for a secure application.
1. Simple form
The key directive to understanding two-way data binding is ngModel. ngModel provides bidirectional data binding from model to view and view to model. And, it also provides APIs to other directives to enhance their behavior.
<!DOCTYPE HTML><html lang="zh-cn" ng-app="SimpleForm"><head> <meta charset="UTF-8"> <title>PropertyEvaluation</title> <style type="text/css"> .ng-cloak { display: none; } </style></head><body><div ng-controller="MyCtrl"> <form novalidate> Name: <input ng-model="user.name" type="text"><br/> Email: <input ng-model="user.email" type="email"><br/> Gender: <input value="male" ng-model="user.gender" type="radio">male<input value="female" ng-model="user.gender" type="radio">female<br/> <button ng-click="reset()">restore last saved</button> <button ng-click="update(user)">Save</button> </form> <pre>form = {{user | json}}</pre> <pre>saved = {{saved | json}}</pre></div><script src="../angular-1.0.1.js" type="text/javascript"></script><script type="text/javascript"> var app = angular.module("SimpleForm", []); app.controller("MyCtrl", function ($scope,$window) { $scope.saved = {}; $scope.update = function(user) { $scope.saved = angular.copy(user); }; $scope.reset = function() { $scope.user = angular.copy($scope.saved); }; $scope.reset(); //Illegal values will not enter user });</script></body></html>2. Using CSS classes
In order to make form, controls and ngModel rich in style, the following class can be added:
In the following example, use CSS to display the validity of each form control. In the example, user.name and user.email are required, but when they are modified (dirty), the background will appear red. This ensures that the user does not get distracted by a mistake until after interacting with the form (after submission?) and discovering that its validity is not met.
<!DOCTYPE HTML><html lang="zh-cn" ng-app="CSSClasses"><head> <meta charset="UTF-8"> <title>CSSClasses</title> <style type="text/css"> .ng-cloak { display: none; } .css-form input.ng-invalid.ng-dirty { background-color: #fa787e; } .css-form input.ng-valid.ng-dirty { background-color: #78fa89; } </style></head><body><div ng-controller="MyCtrl"> <form novalidate name="formName"> Name: <input ng-model="user.name" type="text" required><br/> Email: <input ng-model="user.email" type="email" required><br/> Gender: <input value="male" ng-model="user.gender" type="radio">Male<input value="female" ng-model="user.gender" type="radio">Female<br/> <button ng-click="reset()">RESET</button> <button ng-click="update(user)">SAVE</button> </form> <pre>form = {{user | json}}</pre> <pre>saved = {{saved | json}}</pre></div><script src="../angular-1.0.1.js" type="text/javascript"></script><script type="text/javascript"> var app = angular.module("CSSClasses", []); app.controller("MyCtrl", function ($scope,$window) { $scope.saved = {}; $scope.update = function(user) { $scope.saved = angular.copy(user); }; $scope.reset = function() { $scope.user = angular.copy($scope.saved); }; $scope.reset(); //Illegal values will not enter user });</script></body></html>3. Binding to form and control state
In angular, a form is an instance of FormController. Form instances can be exposed to the scope at will using the name attribute (I don’t understand it here, there is no property in the scope that has always been with the form name attribute, but because there is a method like “document. form name”, it can still be obtained). Similarly, the control is an instance of the NgModelController. Control instances can be exposed to form similarly using the name attribute. This shows that the internal properties of both form and control are feasible for binding in views using standard binding primitives.
This allows us to extend the above example by following features:
<!DOCTYPE HTML><html lang="zh-cn" ng-app="ControlState"><head> <meta charset="UTF-8"> <title>ControlState</title> <style type="text/css"> .ng-cloak { display: none; } .css-form input.ng-invalid.ng-dirty { background-color: #fa787e; } .css-form input.ng-valid.ng-dirty { background-color: #78fa89; } </style></head><body><div ng-controller="MyCtrl"> <form novalidate name="formName"> Name: <input ng-model="user.name" name="userName" type="text" required><br/> <div ng-show="formName.userName.$dirty&&formName.userName.$invalid"> <span>Please fill in the name</span> </div> Email: <input ng-model="user.email" name="userEmail" type="email" required><br/> <div ng-show="formName.userEmail.$dirty && formName.userEmail.$invalid">Tip: <span ng-show="formName.userEmail.$error.required">Please fill in the Email</span> <span ng-show="formName.userEmail.$error.email">This is not a valid Email</span> </div> Gender: <input value="male" ng-model="user.gender" type="radio">Male<input value="female" ng-model="user.gender" type="radio">Female<br/> <input type="checkbox" ng-model="user.agree" name="userAgree" required/> I agree: <input type="text" ng-show="user.agree" ng-model="user.agreeSign" required/> <div ng-show="!formName.userAgree || !user.agreeSign">Please agree and sign~</div> <button ng-click="reset()" ng-disabled="isUnchanged(user)">RESET</button> <button ng-click="update(user)" ng-disabled="formName.$invalid || isUnchanged(user)">SAVE</button> </form> <pre>form = {{user | json}}</pre> <pre>saved = {{saved | json}}</pre></div><script src="../angular-1.0.1.js" type="text/javascript"></script><script type="text/javascript"> var app = angular.module("ControlState", []); app.controller("MyCtrl", function ($scope,$window) { $scope.saved = {}; $scope.update = function(user) { $scope.saved = angular.copy(user); }; $scope.reset = function() { $scope.user = angular.copy($scope.saved); }; $scope.isUnchanged = function(user) { return angular.equals(user, $scope.saved); }; $scope.reset(); //Illegal values will not enter user });</script></body></html>4. Custom Validation
angular provides implementations for most public HTML form fields (input, text, number, url, email, radio, checkbox) types, and there are also directives (required, pattern, inlength, maxlength, min, max) for form verification.
We can define our own verification plug-in by defining the directive in the ngModel controller (Is this the ngModelController connected together?). To get a controller, directive specifies dependencies as in the following example (directive defines the require attribute in the object).
Model to View Update - Whenever the Model changes, all functions in the ngModelController.$formatters (triggering data validation and formatting conversion when the model changes) array will be queued for execution, so each function here has a chance to format the value of the model and modify the control's verification status through NgModelController.$setValidity (http://code.angularjs.org/1.0.2/docs/api/ng.directive:ngModel.NgModelController#$setValidity).
View to Model Update - A similar way, no matter whenever a user interacts with a control, will trigger NgModelController.$setViewValue. It is now the turn to execute NgModelController$parsers (when the control obtains the value from the DOM, all methods in this array will be executed, the values will be reviewed, filtered or converted, and also verified).
In the following example we will create two directives.
The first one is integer, which is responsible for verifying whether the input is a valid integer. For example, 1.23 is an illegal value because it contains the fractional part. Note that we use unshift to replace pushing the tail by inserting it in the array head, because we want it to execute first and use the value of this control (estimating that Array is used as a queue), we need to execute the verification function before the transformation occurs.
The second directive is smart-float. He converts "1.2" and "1,2" into a legal floating point number "1.2". Note that we cannot use HTML5 input type "number" here, because the browser does not allow users to enter illegal characters we expect, such as "1,2" (it only recognizes "1.2").
<!DOCTYPE HTML><html lang="zh-cn" ng-app="CustomValidation"><head> <meta charset="UTF-8"> <title>CustomValidation</title> <style type="text/css"> .ng-cloak { display: none; } .css-form input.ng-invalid.ng-dirty { background-color: #fa787e; } .css-form input.ng-valid.ng-dirty { background-color: #78fa89; } </style></head><body><div> <form novalidate name="formName"> <div> Size (integer 0 - 10):<input integer type="number" ng-model="size" name="size" min="0" max="10"/>{{size}}<br/> <span ng-show="formName.size.$error.integer">This is not a valid integer</span> <span ng-show="formName.size.$error.min || formName.size.$error.max"> The value must be between 0 and 10</span> </div> <div> Length (floating point): <input type="text" ng-model="length" name="length" smart-float/> {{length}}<br/> <span ng-show="formName.length.0error.float">This is not a valid floating point number</span> </div> </form></div><script src="../angular-1.0.1.js" type="text/javascript"></script><script type="text/javascript"> var app = angular.module("CustomValidation", []); var INTEGER_REGEXP = /^/-?/d*$/; app.directive("integer", function () { return { require:"ngModel",//NgModelController link:function(scope,ele,attrs,ctrl) { ctrl.$parsers.unshift(function (viewValue) {//$parsers, updates from View to Model if(INTEGER_REGEXP.test(viewValue)) { //Certify and be sure to ctrl.$setValidity("integer", true); return viewValue; }else { //Private meat slaughter... ctrl.$setValidity("integer", false); return undefined; } }); } } }; }); var FLOAT_REGEXP = /^/-?/d+(?:[.,]/d+)?$/; app.directive("smartFloat", function () { return { require:"ngModel", link:function(scope,ele,attrs,ctrl) { ctrl.$parsers.unshift(function(viewValue) { if(FLOAT_REGEXP.test(viewValue)) { ctrl.$setValidity("float", true); return parseFloat(viewValue.replace(",", ".")); }else { ctrl.$setValidity("float", false); return undefined; } }); } } } });</script></body></html>5. Implementing custom form control (using ngModel)
angular implements the basic controls of all HTML (input, select, textarea) and is competent for most scenarios. However, if we need to be more flexible, we can achieve the purpose of customizing the form control by writing a directive.
In order to develop controls and ngModel to work together and implement bidirectional data binding, it requires:
Implementing the render method is the method responsible for rendering data after executing and passing all NgModelController.$formatters methods.
Call the $setViewValue method, and the model needs to be updated in response no matter when the user interacts with the control. This is usually implemented in the DOM event listener.
You can view $compileProvider.directive for more information.
The following example shows how to add a two-way data binding feature to an element that can be edited.
<!DOCTYPE HTML><html lang="zh-cn" ng-app="CustomControl"><head> <meta charset="UTF-8"> <title>CustomControl</title> <style type="text/css"> .ng-cloak { display: none; } div[contenteditable] { cursor: pointer; background-color: #D0D0D0; } </style></head><body ng-controller="MyCtrl"><div> <div contenteditable="true" ng-model="content">My Little Dada</div> <pre>model = {{content}}</pre> <button ng-click="reset()">reset model tirgger model to view($render)</button></div><script src="../angular-1.0.1.js" type="text/javascript"></script><script type="text/javascript"> var app = angular.module("CustomControl", []); app.controller("MyCtrl", function ($scope) { $scope.reset = function() { $scope.content = "My Little Dada"; //How to get NgModelController here? If you know, I hope you can give me some advice! Thanks}; }); app.directive("contenteditable", function () { return { require:"ngModel", link:function (scope, ele, attrs, ctrl) { //view -> model ele.bind("blur keyup",function() { scope.$apply(function() { console.log("setViewValue"); ctrl.$setViewValue(ele.text()); }); }); //model -> view ctrl.$render = function(value) { console.log("render"); ele.html(value); }; //Read the initial value ctrl.$setViewValue(ele.text()); } }; });</script></body></html>