[angularjs]自訂directive來驗証錯誤

angularjs-自訂directive來驗証錯誤

前言

 

自訂directive是一個很大的議題,市面上甚至有一本專門只討論directive的書,我現在只打算介紹有關自訂驗證的部份,其實官方也已經提供了很多directive給我們用,官方之外也很有多第三方的供使用,有時候自己製作之前,我們可以試著找看看是不是已經有類似的需求直接學著使用。

 

案例實作

 

拿我之前ng-message的範例,但為了把程式碼簡單化,所以我把所有input欄位只留下Name,以下是我簡略化後的code

Index.cshtml

 


<style>
    input.ng-invalid {
        border-color: red;
        border-width: 2px;
    }

    input.ng-pristine {
        border-color: blue;
        border-width: 2px;
    }

    .error {
        color: red;
    }
</style>
<div ng-app="MyApp">
    <div ng-controller="FirstCtrl">
        <div class="row">
            <form name="myForm" ng-submit="submit()" novalidate>
                Name:<input type="text" name="name" ng-model="Add.Name" class="form-control"
                            required ng-minlength="3"  />
                <div ng-messages="myForm.name.$error" class="error" ng-show="myForm.name.$dirty" ng-messages-include="commonMessage">
                   
                </div>
                <input type="submit" value="新增" class="btn btn-primary" ng-disabled="myForm.$invalid" />
            </form>
        </div>
        <hr />
        <div class="row">
            <table class="table">
                <tr>
                    <th>姓名</th>
                    <th>電話</th>
                    <th>地址</th>
                    <th>密碼</th>
                </tr>
                <tr ng-repeat="item in students">
                    <td>{{item.Name}}</td>
                    <td>{{item.PhoneNumber}}</td>
                    <td>{{item.Address}}</td>
                    <td>{{item.Password}}</td>
                </tr>
            </table>
        </div>
    </div>
    <script type="text/ng-template" id="commonMessage">
        <p ng-message="required">此欄位必填</p>
        <p ng-message="minlength">輸入太少</p>
        <p ng-message="maxlength">輸入太多</p>
        <p ng-message="number">只能輸入數字</p>
    </script>
</div>


@section scripts{
    <script src="~/Scripts/angular.js"></script>
    <script src="~/Scripts/angular-messages.js"></script>
    <script src="~/App/Index.js"></script>
}

 

Index.js

 


angular.module('MyApp', ['ngMessages']) //這邊需要加入ngMessages
    .constant('MySet', {
        "ApiUrl": 'http://localhost:58973/Api/Values'
    })
    .controller('FirstCtrl', function ($scope, $http, MySet) {
        $http.get(MySet.ApiUrl).success(function (data) {
            $scope.students = data;
        });

        $scope.submit = function () {
            if (this.myForm.$valid) alert('成功');
            if (this.myForm.$invalid) alert('驗證失敗');
        }
    });

 

但是很多時候,我們必須要檢查某個欄位在db是否已經有值,難道我們需要在每個input的事件裡面自己寫ajax比對資料嗎?這邊我想要討論的就是如何自訂directive來做驗證,在此我也是寫上註解在程式碼裡面,就不做過多說明了

 


angular.module('MyApp', ['ngMessages']) //這邊需要加入ngMessages
    .constant('MySet', {
        "ApiUrl": 'http://localhost:58973/Api/Values'
    })
    //自訂directive,大寫代表-比如ng-click就是ngClick
    .directive('kkCheckName', function ($http, MySet) { 
        return {
            require: 'ngModel',  //驗證必加的
            //這邊主要用到ctrl,則是選中的controller的意思
            link: function (scope, elemnt, attrs, ctrl) { 
                ctrl.$parsers.push(function (value) {
                    $http.get(MySet.ApiUrl + '?Name=' + value).success(function (data) {
                        //我從api撈回來,如果存在則是false,代表驗證不通過
                        ctrl.$setValidity('NameIsExist', data);  
                    })
                    return value;
                })
            }
        }
    })
    .controller('FirstCtrl', function ($scope, $http, MySet) {
        $http.get(MySet.ApiUrl).success(function (data) {
            $scope.students = data;
        });

        $scope.submit = function () {
            if (this.myForm.$valid) alert('成功');
            if (this.myForm.$invalid) alert('驗證失敗');
        }
    });

<style>
    input.ng-invalid {
        border-color: red;
        border-width: 2px;
    }

    input.ng-pristine {
        border-color: blue;
        border-width: 2px;
    }

    .error {
        color: red;
    }
</style>
<div ng-app="MyApp">
    <div ng-controller="FirstCtrl">
        <div class="row">
            <form name="myForm" ng-submit="submit()" novalidate>
                Name:<input type="text" name="name" ng-model="Add.Name" class="form-control"
                            required ng-minlength="3" kk-check-name /> @*加上自定義的kk-check-name*@
                <div ng-messages="myForm.name.$error" class="error" ng-show="myForm.name.$dirty" ng-messages-include="commonMessage">
                    <p ng-message="NameIsExist">Name不可相同</p>  @*對應後面自定義的驗證名稱*@
                </div>
                <input type="submit" value="新增" class="btn btn-primary" ng-disabled="myForm.$invalid" />
            </form>
        </div>
        <hr />
        <div class="row">
            <table class="table">
                <tr>
                    <th>姓名</th>
                    <th>電話</th>
                    <th>地址</th>
                    <th>密碼</th>
                </tr>
                <tr ng-repeat="item in students">
                    <td>{{item.Name}}</td>
                    <td>{{item.PhoneNumber}}</td>
                    <td>{{item.Address}}</td>
                    <td>{{item.Password}}</td>
                </tr>
            </table>
        </div>
    </div>
    <script type="text/ng-template" id="commonMessage">
        <p ng-message="required">此欄位必填</p>
        <p ng-message="minlength">輸入太少</p>
        <p ng-message="maxlength">輸入太多</p>
        <p ng-message="number">只能輸入數字</p>
    </script>
</div>


@section scripts{
    <script src="~/Scripts/angular.js"></script>
    <script src="~/Scripts/angular-messages.js"></script>
    <script src="~/App/Index.js"></script>
}

 

改正效能問題

 

這樣驗証雖然都正常,但我試著把值印出來,卻發現了很嚴重的效能問題,因為angularjs是twoway binding,所以每key一個字就去跟api要一次資料,這顯然很傷效能,如下圖

image

 

這時候我們可以在前端使用ng-model-options,專注在一旦我們blur了,再觸動驗證向api要資料,如下


<style>
    input.ng-invalid {
        border-color: red;
        border-width: 2px;
    }

    input.ng-pristine {
        border-color: blue;
        border-width: 2px;
    }

    .error {
        color: red;
    }
</style>
<div ng-app="MyApp">
    <div ng-controller="FirstCtrl">
        <div class="row">
            <form name="myForm" ng-submit="submit()" novalidate>
                Name:<input type="text" name="name" ng-model="Add.Name" class="form-control" ng-model-options="{updateOn:'blur'}" @*為了效能加上的directive*@
                            required ng-minlength="3" kk-check-name /> @*加上自定義的kk-check-name*@
                <div ng-messages="myForm.name.$error" class="error" ng-show="myForm.name.$dirty" ng-messages-include="commonMessage">
                    <p ng-message="NameIsExist">Name不可相同</p>  @*對應後面自定義的驗證名稱*@
                </div>
                <input type="submit" value="新增" class="btn btn-primary" ng-disabled="myForm.$invalid" />
            </form>
        </div>
        <hr />
        <div class="row">
            <table class="table">
                <tr>
                    <th>姓名</th>
                    <th>電話</th>
                    <th>地址</th>
                    <th>密碼</th>
                </tr>
                <tr ng-repeat="item in students">
                    <td>{{item.Name}}</td>
                    <td>{{item.PhoneNumber}}</td>
                    <td>{{item.Address}}</td>
                    <td>{{item.Password}}</td>
                </tr>
            </table>
        </div>
    </div>
    <script type="text/ng-template" id="commonMessage">
        <p ng-message="required">此欄位必填</p>
        <p ng-message="minlength">輸入太少</p>
        <p ng-message="maxlength">輸入太多</p>
        <p ng-message="number">只能輸入數字</p>
    </script>
</div>


@section scripts{
    <script src="~/Scripts/angular.js"></script>
    <script src="~/Scripts/angular-messages.js"></script>
    <script src="~/App/Index.js"></script>
}

 

定義更彈性的directive

 

如果我們希望彈性一點,做成共用的,其實我們也可以在前端丟入api,來決定要拋哪個api來做驗證,這樣子我們可以決定要呼叫哪個api來與此input做驗證,如下


<style>
    input.ng-invalid {
        border-color: red;
        border-width: 2px;
    }

    input.ng-pristine {
        border-color: blue;
        border-width: 2px;
    }

    .error {
        color: red;
    }
</style>
<div ng-app="MyApp">
    <div ng-controller="FirstCtrl">
        <div class="row">
            <form name="myForm" ng-submit="submit()" novalidate>
                Name:<input type="text" name="name" ng-model="Add.Name" class="form-control" ng-model-options="{updateOn:'blur'}"
                            required ng-minlength="3" kk-check-name="Values/" /> @*加入傳前的attribute*@
                <div ng-messages="myForm.name.$error" class="error" ng-show="myForm.name.$dirty" ng-messages-include="commonMessage">
                    <p ng-message="NameIsExist">Name不可相同</p> 
                </div>
                <input type="submit" value="新增" class="btn btn-primary" ng-disabled="myForm.$invalid" />
            </form>
        </div>
        <hr />
        <div class="row">
            <table class="table">
                <tr>
                    <th>姓名</th>
                    <th>電話</th>
                    <th>地址</th>
                    <th>密碼</th>
                </tr>
                <tr ng-repeat="item in students">
                    <td>{{item.Name}}</td>
                    <td>{{item.PhoneNumber}}</td>
                    <td>{{item.Address}}</td>
                    <td>{{item.Password}}</td>
                </tr>
            </table>
        </div>
    </div>
    <script type="text/ng-template" id="commonMessage">
        <p ng-message="required">此欄位必填</p>
        <p ng-message="minlength">輸入太少</p>
        <p ng-message="maxlength">輸入太多</p>
        <p ng-message="number">只能輸入數字</p>
    </script>
</div>


@section scripts{
    <script src="~/Scripts/angular.js"></script>
    <script src="~/Scripts/angular-messages.js"></script>
    <script src="~/App/Index.js"></script>
}

angular.module('MyApp', ['ngMessages']) 
    .constant('MySet', {
        "ApiUrl": 'http://localhost:58973/Api/Values',
        "ShortUrl": 'http://localhost:58973/Api/'
    })
   
    .directive('kkCheckName', function ($http, MySet) { 
        return {
            require: 'ngModel',  
            //attrs則是要抓到外面傳進來的屬性
            link: function (scope, elemnt, attrs, ctrl) { 
                ctrl.$parsers.push(function (value) {
                    var UrlApi=attrs.kkCheckName; //從外面傳進來的
                    console.log(UrlApi);
                    $http.get(MySet.ShortUrl + UrlApi +'?Name=' + value).success(function (data) {
                        ctrl.$setValidity('NameIsExist', data);  
                    })
                    return value;
                })
            }
        }
    })
    .controller('FirstCtrl', function ($scope, $http, MySet) {
        $http.get(MySet.ApiUrl).success(function (data) {
            $scope.students = data;
        });

        $scope.submit = function () {
            if (this.myForm.$valid) alert('成功');
            if (this.myForm.$invalid) alert('驗證失敗');
        }
    });

 

雖然上面有很多code,但其實都只是小小改動,然後再請多多指教,如有更好的做法,再請告知小弟