其實我沒有那麼喜歡寫程式

Validation : The Angular Way(預先定義錯誤訊息)

Angular做驗證的確相當方便,但有一點是非常麻煩的,就是定義錯誤訊息很麻煩(且無聊)。

下面範例有一個姓名欄位為必填欄位,且字數必須大於3個字元和小於10個字元,我們必須幫每個錯誤類型寫一段html的code,如果表單欄位很多肯定很浪費時間。

   <form name="myForm">
       <input name="name" type="text" ng-minlength="3" ng-maxlength="10" required />
       <div ng-show="myForm.name.$error.required">
           請輸入姓名
       </div>
       <div ng-show="myForm.name.$error.minlength">
           至少輸入三個字
       </div>
       <div ng-show="myForm.name.$error.maxlength">
           最多輸入10個字
       </div>
   </form>

定義錯誤訊息的Directive

Angular會給每一個input一個$error的object,我們可以透過key值取得boolean判斷該驗證是否通過,例如$error.required,所以我們利用這個屬性去顯示所有驗證的錯誤訊息。 但我們可以自己定義directive去做這件事,這會節省掉很多打字的時間。 下面範例是定義一個validationErrors的directive,它會注入errorMessages這個constant(我們預先定義好的錯誤訊息)來顯示所有的錯誤訊息。

errorModule
    .directive('validationErrors', function (errorMessages) {
    return {
        restrict: 'A',
        scope: {
            errors: '=',
            errorClass: '@'
        },
        template: '<div ng-class="buildErrorClass()" ng-repeat="(errorKey, isError) in errors track by $index" ng-show="isError">' +
                      '{{errorFor(errorKey)}}' +
                  '</div>',
        controller: function ($scope) {
            $scope.errorFor = function (errorKey) {
                return errorMessages[errorKey];
            };

            $scope.buildErrorClass = function () {
                return this.errorClass || 'inline-help text-error"';
            };
        }
    };
});
errorMessages constant
errorModule
    .constant({
        required: '為必填欄位',
        email: '必須為正確的email格式',
        url: '必須為url格式',
        minlength: '您輸入太少字',
        maxlength: '您輸入太多字了',
        /*
            省略
        */
    });

用Provider取代Constant

但用constant定義錯誤訊息其實彈性不夠,如果你的module希望以後還能用(或是給別人用),它們就必須回去修改你的原始碼。這裡可以改用provider(不要用service或factory,因為我們希望能夠在config block階段定義錯誤訊息,然後提供整個應用程式使用)。

errorMessages provider
errorModule.provider('errorMessages', function () {
    this.errorMessages = {};

    this.$get = function () {
        var errorMessages = this.errorMessages;
        return {
            getError: function () {
                return errorMessages;
            }
        };
    };

    this.setErrorMessages = function (messages) {
        this.errorMessages = messages;
    };

});

接著我們就可以在應用程式的module config注入errorMessagesProvider來定義我們的錯誤訊息,接著我們就可以在應用程式裡使用validationError這個directive了。

myApp module
angular.module('myApp', ['validationError'])
    .config(function(errorMessagesProvider){
        errorMessagesProvider.setErrorMessages({
            required: '為必填欄位',
            email: '必須為正確的email格式',
            url: '必須為url格式',
            minlength: '您輸入太少字',
            maxlength: '您輸入太多字了',
            /*
                省略
            */
        });
    });
});
myApp view
<div ng-app="myApp">
    <form class="form-horizontal" name="logOnForm" novalidate ng-controller="formtrl">
        <input name="userName" type="text" ng-model="userName" required />
        <div errors="logOnForm.userName.$error" error-class="text-danger" validation-errors></div>
        <input name="email" type="email" ng-model="email" required />
        <div errors="logOnForm.email.$error" error-class="text-danger" validation-errors></div>
    </form>
</div>

客製化錯誤訊息

我們已經在config裡定義各種類型的錯誤訊息了,但是有時候你可能希望某些欄位要顯示特定的錯誤訊息。 要做到這一點只需修改directive。

validationErrors directive
errorModule
    .directive('validationErrors', function (errorMessages) {
    return {
        restrict: 'A',
        scope: {
            errors: '=',
            errorClass: '@'
        },
        template: '<div ng-class="buildErrorClass()" ng-repeat="(errorKey, isError) in errors track by $index" ng-show="isError">' +
                      '{{errorFor(errorKey)}}' +
                  '</div>',
        controller: function ($scope, $element, $attrs) {
            var error = errorMessages.getError();
            
            if ($attrs.validationErrors) {
                angular.extend(error, $scope.$eval($attrs.validationErrors));
            };

            $scope.errorFor = function (errorKey) {
                return error[errorKey];
            };

            $scope.buildErrorClass = function () {
                return this.errorClass || 'inline-help text-error"';
            };
        }
    };
})
myApp view
<div ng-app="myApp">
    <form class="form-horizontal" name="logOnForm" novalidate ng-controller="formtrl">
        <input name="userName" type="text" ng-model="userName" required />
        <div errors="logOnForm.userName.$error" 
             error-class="text-danger" 
             validation-errors="{required: '使用者名稱是必填欄位'}">
        </div>
        <input name="email" type="email" ng-model="email" required />
        <div errors="logOnForm.email.$error" 
             error-class="text-danger" 
             validation-errors="{required: 'Email是必填欄位', email: '請輸入正確的Email格式'}">
        </div>
    </form>
</div>