angular Learning (11) - Forms

Keywords: Attribute angular xml Javascript

form

Control is mainly used to receive user input, such as input, select, textarea. Form is a collection of related controls. Form and control provide validation services and can be notified of invalid input before the user submits the form to the back end.

A simple form

form validation is much better than server-side validation because feedback is more timely to correct errors. Although the client's verification experience is good, it can be easily avoided, and the server's verification is very necessary, especially for those applications with high security requirements.

The key to understanding angular JS bidirectional binding is instruction ngModel, which provides view-to-model and model-to-view bidirectional binding. It also provides APIs for other instructions to enhance its role.

<!DOCTYPE html>
<html>
<head>
    <meta charset="uft-8"/>
    <title></title>
</head>
<script src="script/angular.min.js"></script>
<body ng-app="formExample">
<div ng-controller="ExampleController">
    <form novalidate class="simple-form">
        <label>Name: <input type="text" ng-model="user.name" /></label><br />
        <label>E-mail: <input type="email" ng-model="user.email" /></label><br />
        Best Editor: <label><input type="radio" ng-model="user.preference" value="vi" />vi</label>
        <label><input type="radio" ng-model="user.preference" value="emacs" />emacs</label><br />
        <input type="button" ng-click="reset()" value="Reset" />
        <input type="submit" ng-click="update(user)" value="Save" />
    </form>
    <pre>user = {{user | json}}</pre>
    <pre>master = {{master | json}}</pre>
</div>

<script>
    angular.module('formExample', [])
            .controller('ExampleController', ['$scope', function($scope) {
                $scope.master = {};

                $scope.update = function(user) {
                    $scope.master = angular.copy(user);
                };

                $scope.reset = function() {
                    $scope.user = angular.copy($scope.master);
                };

                $scope.reset();
            }]);
</script>

</body>
</html>

Note that novalidate can disable browsers to check the form naturally.
Unless the input passes validation, the corresponding value of ngModel is not set. For example, input boxes of email type must be in user@domain format.

Using css class

In order to style forms as well as controls, ngModel adds some CSS classes:

  • ng-valid: model valid
  • ng-invalid: model invalid
  • ng-valid-[key]: Each valid key added through $setValidity
  • ng-invalid-[key]: Each invalid key added through $setValidity
  • ng-pristine: Control value is initial state
  • ng-dirty: The value of the control has been modified
  • ng-touched: Control has been clicked and lost focus
  • ng-untouched: Control has not been clicked
  • ng-pending: And $asyncValidators are not complete.

The following example uses css to show the input validity of the control. In this example, user.name and user.email must be entered, but only when the input is blurred (after the control is clicked and lost focus) will the red background be displayed, which ensures that the user will not be prompted for errors unless the input can not meet the validation criteria after interacting with the control, and that the error will not be prompted before the exchange.

<!DOCTYPE html>
<html>
<head>
    <meta charset="uft-8"/>
    <title></title>
</head>
<script src="script/angular.min.js"></script>
<body ng-app="formExample">
<div ng-controller="ExampleController">
    <form novalidate class="css-form">
        <label>Name: <input type="text" ng-model="user.name" required /></label><br />
        <label>E-mail: <input type="email" ng-model="user.email" required /></label><br />
        Gender: <label><input type="radio" ng-model="user.gender" value="male" />male</label>
        <label><input type="radio" ng-model="user.gender" value="female" />female</label><br />
        <input type="button" ng-click="reset()" value="Reset" />
        <input type="submit" ng-click="update(user)" value="Save" />
    </form>
    <pre>user = {{user | json}}</pre>
    <pre>master = {{master | json}}</pre>
</div>

<style type="text/css">
    .css-form input.ng-invalid.ng-touched {
        background-color: #FA787E;
    }

    .css-form input.ng-valid.ng-touched {
        background-color: #78FA89;
    }
</style>
<script>
    angular.module('formExample', [])
            .controller('ExampleController', ['$scope', function($scope) {
                $scope.master = {};

                $scope.update = function(user) {
                    $scope.master = angular.copy(user);
                };

                $scope.reset = function() {
                    $scope.user = angular.copy($scope.master);
                };

                $scope.reset();
            }]);
</script>

</body>
</html>

form and control state binding

In angualr, form is an instance of FormController, which can be published to scope through the name attribute. Similarly, input controls with ngModel instructions are also NgModel Controller instances, which can be published as an attribute of form instances through the name attribute of the control.
This means that form, like the internal state of a control, can be bound in a view in a standard way.

Let's extend the example above:

  • Display custom error messages after user-control interaction
  • Display custom error messages when a user submits a form, even if the user and control do not interact
<!DOCTYPE html>
<html>
<head>
    <meta charset="uft-8"/>
    <title></title>
</head>
<script src="script/angular.min.js"></script>
<body ng-app="formExample">
<div ng-controller="ExampleController">
    <form  novalidate class="css-form" name="form">
        <label>Name: <input type="text" ng-model="user.name" name="uName" required="" /></label><br />

        <div ng-show="form.$submitted || form.uName.$touched">
            <div ng-show="form.uName.$error.required">Tell us your name.</div>
        </div>

        <label>E-mail: <input type="email" ng-model="user.email" name="uEmail" required /></label><br />

        <div ng-show="form.$submitted || form.uEmail.$touched">
            <span ng-show="form.uEmail.$error.required">Tell us your email.</span>
            <span ng-show="form.uEmail.$error.email">This is not a valid email.</span>
        </div>

        Gender: <label><input type="radio" ng-model="user.gender"  value="male" />male</label>
        <label><input type="radio" ng-model="user.gender" value="female" />female</label><br />

        <label>
        <input type="checkbox" ng-model="user.agree" name="userAgree" required="" />
        I agree:
        </label>
        <input ng-show="user.agree" type="text" ng-model="user.agreeSign" required="" />
        <br />
        <div ng-show="form.$submitted || form.userAgree.$touched">
            <div ng-show="!user.agree || !user.agreeSign">Please agree and sign.</div>
        </div>

        <input type="button" ng-click="reset(form)" value="Reset" />
        <input type="submit" ng-click="update(user)" value="Save" />
    </form>
    <pre>user = {{user | json}}</pre>
    <pre>master = {{master | json}}</pre>
</div>

<style type="text/css">
    .css-form input.ng-invalid.ng-touched {
        background-color: #FA787E;
    }

    .css-form input.ng-valid.ng-touched {
        background-color: #78FA89;
    }
</style>
<script>
    angular.module('formExample', [])
            .controller('ExampleController', ['$scope', function($scope) {
                $scope.master = {};

                $scope.update = function(user) {
                    $scope.master = angular.copy(user);
                };

                $scope.reset = function(form) {
                    if (form) {
                        form.$setPristine();
                        form.$setUntouched();
                    }
                    $scope.user = angular.copy($scope.master);
                };

                $scope.reset();
            }]);
</script>

</body>
</html>

Custom model Update Trigger

By default, any change to the content triggers changes to the model and validation of the form. You can override this behavior by binding the ngModel Options command to a special event list. Ng-model-options="{updateOn:'blur'}" means that the model is updated and form validation is performed only when the user enters and loses focus. If there are multiple events, you can use spaces to separate them, such as ng-model-options="{updateOn:'mousedown blur'}"

If you want to retain the default behavior and just add new events to the default event to trigger updates and validation, you just need to add default as one of the common events.
ng-model-options="{ updateOn: 'default blur' }"

Here is an example of rewriting an immediate update pair. Only when the user interacts with the input box (when the control is clicked and the focus is lost), changes to the input box are updated to the model.

<!DOCTYPE html>
<html>
<head>
    <meta charset="uft-8"/>
    <title></title>
</head>
<script src="script/angular.min.js"></script>
<script>
    angular.module('customTriggerExample', [])
            .controller('ExampleController', ['$scope', function($scope) {
                $scope.user = {name:"aa"};
            }]);
</script>
<body ng-app="customTriggerExample">
<div ng-controller="ExampleController">
    <form>
        <label>Name:
            <input type="text" ng-model="user.name" ng-model-options="{ updateOn: 'blur' }" /></label><br />
        <label>
            Other data:
            <input type="text" ng-model="user.data" /></label><br />
    </form>
    <pre>username = "{{user.name}}"</pre>
    <pre>userdata = "{{user.data}}"</pre>
</div>
</body>
</html>

Delayed model updates

You can also delay updating and validation through the debounce key of the ngModel Options instruction. This delay also works for parsers, validators, and model tags ($dirty or $pristine.).

Ng-model-options="{debounce: 500}" means that it takes 500 milliseconds to wait for the last change in content to trigger model updates and form validation.

If a custom trigger is used, then a debounce object is used to add a custom jitter elimination timeout to each event. This is useful in certain situations, such as the need to update and validate immediately after a blur event. The grammar is as follows:
ng-model-options="{ updateOn: 'default blur', debounce: { default: 500, blur: 0 } }"

Note that if these attributes are added to an element, all child elements and controls of that element will inherit their settings unless the child elements and controls override these attributes.

The following example demonstrates the shaking of model changes, which will be updated after the last change of content is 250mm.

<!DOCTYPE html>
<html>
<head>
    <meta charset="uft-8"/>
    <title></title>
</head>
<script src="script/angular.min.js"></script>
<script>
    angular.module('debounceExample', [])
            .controller('ExampleController', ['$scope', function($scope) {
                $scope.user = {};
            }]);
</script>
<body ng-app="debounceExample">
<div ng-controller="ExampleController">
    <form>
        <label>Name:
            <input type="text" ng-model="user.name" ng-model-options="{ debounce: 250 }" /></label><br />
    </form>
    <pre>username = "{{user.name}}"</pre>
</div>

</body>
</html>

Custom Validation

Angular JS provides basic implementations for the most commonly used input types (text, number, url, email, date, radio, checkbox) of HTML 5, as well as some instructions for verification (required, pattern, minlength, maxlength, min, max).

To customize the validation instructions, you need to add your custom validators to the $validators object of the ngModelController instance. You can refer to the following examples.

Each validator function of the $validators object has two parameters, modelValue and viewValue. angularjs calls the $setValidity function internally, and the parameter is the return value of the validator function. The validation function executes every time the input changes ($setViewValue is called) or the constraint model changes. After parsers and formatters are successfully run, validation is performed separately. If the validation fails, the wrong key is saved in ngModel Controller. $error.

In addition, the $asyncValidators object handles asynchronous validation, such as when data needs to be validated through http backend requests. The validation function added to this object must return a promise, resolve when valid, reject when invalid. During asynchronous validation, the validated key is saved in ngModel Controller. $pending.

The following example creates two validation instructions:

  • The integer instruction verifies that the input is an integer. For example, 1.33 is an invalid value. Notice that we validate the viewValue, the value entered in the control, not the modleValue, because the input box of type number converts the viewValue to a number through $parsers. If we enter the event of 1.00, the modelValue will be 1, and the viewValue will be 1.00, which is obviously invalid.

  • The username instruction asynchronously checks whether the value entered by the user already exists. We use $q.defer() to simulate the server request.

<!DOCTYPE html>
<html>
<head>
    <meta charset="uft-8"/>
    <title></title>
</head>
<script src="script/angular.min.js"></script>
<body ng-app="form-example1">
<form name="form" class="css-form" novalidate>
    <div>
        <label>
            Size (integer 0 - 10):
            <input type="number" ng-model="size" name="size"
                   min="0" max="10" integer />{{size}}</label><br />
        <span ng-show="form.size.$error.integer">The value is not a valid integer!</span>
        <span ng-show="form.size.$error.min || form.size.$error.max">
      The value must be in range 0 to 10!</span>
        <span ng-show="form.size.$error.require">111</span>
    </div>

    <div>
        <label>
            Username:
            <input type="text" ng-model="name" name="name" username />{{name}}</label><br />
        <span ng-show="form.name.$pending.username">Checking if this name is available...</span>
        <span ng-show="form.name.$error.username">This username is already taken!</span>
    </div>

</form>
</body>
<script>
    var app = angular.module('form-example1', []);


    var INTEGER_REGEXP = /^-?\d+$/;
    app.directive('integer', function() {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                ctrl.$validators.integer = function(modelValue, viewValue) {
                    if (ctrl.$isEmpty(modelValue)) {
                        // consider empty models to be valid
                        return true;
                    }

                    if (INTEGER_REGEXP.test(viewValue)) {
                        // it is valid
                        return true;
                    }

                    // it is invalid
                    return false;
                };
            }
        };
    });

    app.directive('username', function($q, $timeout) {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                var usernames = ['Jim', 'John', 'Jill', 'Jackie'];

                ctrl.$asyncValidators.username = function(modelValue, viewValue) {

                    if (ctrl.$isEmpty(modelValue)) {
                        // consider empty model valid
                        return $q.resolve();
                    }

                    var def = $q.defer();

                    $timeout(function() {
                        // Mock a delayed response
                        if (usernames.indexOf(modelValue) === -1) {
                            // The username is available
                            def.resolve();
                        } else {
                            def.reject();
                        }

                    }, 2000);

                    return def.promise;
                };
            }
        };
    });
</script>
</html>

Modify built-in validation

Angular JS has built-in validators that you can easily remove or replace. The following example demonstrates how to override a validator of type email.

<!DOCTYPE html>
<html>
<head>
    <meta charset="uft-8"/>
    <title></title>
</head>
<script src="script/angular.min.js"></script>
<body ng-app="form-example-modify-validators">
<form name="form" class="css-form" novalidate>
    <div>
        <label>
            Overwritten Email:
            <input type="email" ng-model="myEmail" overwrite-email name="overwrittenEmail" />
        </label>
        <span ng-show="form.overwrittenEmail.$error.email">This email format is invalid!</span><br>
        Model: {{myEmail}}
    </div>
</form>

</body>
<script>
    var app = angular.module('form-example-modify-validators', []);

    app.directive('overwriteEmail', function() {
        var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@example\.com$/i;

        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                // ngModel is specified, and the input type is email
                if (ctrl && ctrl.$validators.email) {

                    // Rewriting the built-in email validator for angular JS
                    ctrl.$validators.email = function(modelValue) {
                        return ctrl.$isEmpty(modelValue) || EMAIL_REGEXP.test(modelValue);
                    };
                }
            }
        };
    });
</script>
</html>

Custom form control

Angular JS implements all the basic html controls (input, select, textarea), which are sufficient in most cases, but if you need more flexibility, you can customize form controls.

In order for a custom control to support ngModel with two-way bindings, you need to do this:

  • Implement the $render method, which is responsible for rendering data after NgModelController.$formatters
  • When the model needs to be updated after the user interacts with the control, the $setViewValue method is called. Usually done by DOM event listeners.

See the following example:

<!DOCTYPE html>
<html>
<head>
    <meta charset="uft-8"/>
    <title></title>
</head>
<script src="script/angular.min.js"></script>
<body ng-app="form-example2">
<div contentEditable="true" ng-model="content" title="Click to edit">Some</div>
<pre>model = {{content}}</pre>

<style type="text/css">
    div[contentEditable] {
        cursor: pointer;
        background-color: #D0D0D0;
    }
</style>

</body>
<script>
    angular.module('form-example2', []).directive('contenteditable', function() {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                // view -> model
                elm.on('blur', function() {
                    ctrl.$setViewValue(elm.html());
                });

                // model -> view
                ctrl.$render = function() {
                    elm.html(ctrl.$viewValue);
                };

                // load init value from DOM
                ctrl.$setViewValue(elm.html());
            }
        };
    });
</script>
</html>





If my article is helpful to you, please reward it with Alipay.

Posted by Zangakat on Mon, 25 Mar 2019 07:36:30 -0700