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要一次資料,這顯然很傷效能,如下圖
這時候我們可以在前端使用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,但其實都只是小小改動,然後再請多多指教,如有更好的做法,再請告知小弟