带有$ setValidity的Angular 1.6自定义验证指令

时间:2016-12-25 19:23:43

标签: angularjs validation directive angularjs-1.6

我正在尝试编写一个自定义指令,该指令根据指令中还需要的其他值来验证输入字段。我通过使用带范围变量的隔离范围来实现此目的。更具体地说,我想比较一个产品的客户价格(即其净价)和购买价格,如果差异是负数(除了客户价格设置为0)我想让客户 - 价格输入(及其周围的表格)无效。这是我的指示:

export class CheckMarkupDirective implements ng.IDirective {
    public static create(): ng.IDirective {
        return {
            restrict: "A",
            require: "ngModel",
            scope: {
                netPrice: "<",
                markupAmount: "<"
            },
            link: (scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ngModelCtrl: ng.INgModelController) => {

                let netPrice: number;
                let markupAmount: number;
                scope.$watchGroup(["netPrice", "markupAmount"], (newValues, oldValues) => {

                    [netPrice, markupAmount] = newValues;

                    if (markupAmount >= 0) {
                        ngModelCtrl.$setValidity("markup", true);
                    } else {
                        ngModelCtrl.$setValidity("markup", netPrice === 0);
                    }
                    ngModelCtrl.$validate();
                });
            }
        };
    }
}

这就是我在一个由form-tag包围的ng-form div中使用它的方法:

<input type="text" id="customer-price" name="customerPrice"
       ng-model="ctrl.product.customerPrice"
       ng-change="ctrl.customerPriceChangeDetected()" 
       check-markup markup-amount="ctrl.product.markupAmount"
       net-price="ctrl.product.netPrice" />

它有效,但问题是验证部分似乎是“定时错误”,这意味着如果我输入的值导致“标记”第一次变为负数,那么表单的$无效value设置为false。但是当接下来输入为负时,验证将启动并运行。我认为我的问题是我在不同的步骤之间进行了大量的计算,但是我很难知道导致验证的原因是什么。我想我希望有一个对Angular JS机制有更深入了解的人有gander并告诉我,如果我做了一些明显错误的事情。如果我的描述含糊不清,请提前致谢并对不起。

编辑:我还想包含在ng-change上触发的方法:

public customerPriceChangeDetected(): void {
    this.setNetPriceFromCustomerPrice();
    this.setMarkup();
    this.changeDetected();
}
private setNetPriceFromCustomerPrice(): void {
    let customerPrice = this.product.customerPrice;
    let vatRate = this.product.vatRate;
    let netPrice = (customerPrice / (1 + vatRate));
    this.product.netPrice = parseFloat(accounting.toFixed(netPrice, 2));
}
private setMarkup(): void {
    let purchasePrice = this.product.purchasePrice;
    let markupAmount = this.product.netPrice - purchasePrice;
    this.product.markupAmount = markupAmount;
    this.product.markupPercent = markupAmount / purchasePrice;
}
public changeDetected(): void {
    let isValid = this.validationService ? this.validationService.isValid : false;
    this.toggleSaveButton(isValid);
}

验证服务getter基本上返回表单。$ valid并且对我所有其他自定义验证器都完美无缺。

编辑2:添加了屏幕截图,显示周围的ng-form标记似乎将其$ invalid属性设置为true至少为: enter image description here

编辑3: 这是已编译的JS:

var CheckMarkupDirective = (function () {
function CheckMarkupDirective() {
}
CheckMarkupDirective.create = function () {
    return {
        restrict: "A",
        require: "ngModel",
        scope: {
            netPrice: "<",
            markupAmount: "<"
        },
        link: function (scope, element, attrs, ngModelCtrl) {
            var netPrice;
            var markupAmount;
            scope.$watchGroup(["netPrice", "markupAmount"], function (newValues, oldValues) {
                netPrice = newValues[0], markupAmount = newValues[1];
                if (!markupAmount || !netPrice)
                    return;
                if (markupAmount >= 0) {
                    ngModelCtrl.$setValidity("markup", true);
                }
                else {
                    ngModelCtrl.$setValidity("markup", netPrice === 0);
                }
                //ngModelCtrl.$validate();
            });
        }
    };
};
return CheckMarkupDirective; }());

...这里是我的html的缩减版本:

<form autocomplete="off" class="form-horizontal" role="form" name="productDetailsForm" novalidate data-ng-init="ctrl.setForm(this,'productDetailsForm')">
<div data-ng-form="section2">
    <div class="form-group">
        <label for="purchase-price" class="col-sm-4 control-label">Purchase price</label>
        <div class="col-sm-4">
            <input type="text" class="form-control" id="purchase-price" name="purchasePrice"
                   data-ng-model="ctrl.product.purchasePrice"
                   data-ng-change="ctrl.purchasePriceChangeDetected();"
                   data-decimal="Currency" />
        </div>
    </div>
    <div class="form-group">
        <label for="vat-rate" class="col-sm-4 control-label">VAT rate</label>
        <div class="col-sm-4">
            <select class="form-control" id="vat-rate"
                    data-ng-model="ctrl.product.vatRate"
                    data-ng-change="ctrl.vatRateChangeDetected()"
                    data-ng-options="vatRate.value as vatRate.text for vatRate in ctrl.vatRates"></select>
        </div>
    </div>
    <div class="form-group" data-has-error-feedback="productDetailsForm.section2.customerPrice">
        <label for="customer-price" class="col-sm-4 control-label">Customer price</label>
        <div class="col-sm-4">
            <input type="text" class="form-control" id="customer-price" name="customerPrice"
                   data-ng-model="ctrl.product.customerPrice"
                   data-ng-change="ctrl.customerPriceChangeDetected();"
                   data-decimal="Currency"
                   data-check-markup
                   data-markup-amount="ctrl.product.markupAmount"
                   data-net-price="ctrl.product.netPrice" />
            <invalid-feedback item="productDetailsForm.section2.customerPrice"></invalid-feedback>
            <validation-feedback type="markup" item="productDetailsForm.section2.customerPrice" data-l10n-bind="ADMINISTRATION.PRODUCTS.NET_PRICE.INVALID"></validation-feedback>
        </div>
        <div class="col-sm-4">
            <div class="form-group" style="margin-bottom: 0;">
                <label for="net-price" class="col-lg-5 col-md-5 col-sm-5 col-xs-5" style="font-weight: normal; margin-top: 7px;">
                    <span data-l10n-bind="ADMINISTRATION.PRODUCTS.NET_PRICE"></span>
                </label>
                <label class="col-lg-7 col-md-7 col-sm-7 col-xs-7" style="font-weight: normal; margin-top: 7px;">
                    <span id="net-price">{{ ctrl.product.netPrice | currency }}</span>
                </label>
            </div>
        </div>
    </div>
    <div class="form-group" data-has-error-feedback="productDetailsForm.section2.markup">
        <label for="markup-amount" class="col-sm-4 col-xs-4 control-label">Markup</label>
        <div class="col-sm-8 col-xs-8">
            <label id="markup-percent" class="control-label" data-ng-class="{'text-danger': ctrl.product.markupPercent < 0}">
                {{ ctrl.product.markupPercent * 100 | number: 2 }}%
            </label>
            <label id="markup-amount" class="control-label" data-ng-class="{'text-danger': ctrl.product.markupAmount < 0}">
                ({{ ctrl.product.markupAmount | currency }})
            </label>
        </div>
    </div>
</div>

我在指令中将断点放在手表中,并且由于一些奇怪的原因,当我第一次在客户价格输入中输入新值时,手表似乎没有触发。相反,我发现自己直接在changeDetected()方法中。我现在真的很困惑。我认为问题与验证前触发的ng-change指令有关。我可能有一个错误的逻辑,导致我的验证服务触发的isValid检查在指令有时间实际改变有效性之前。

2 个答案:

答案 0 :(得分:1)

尝试删除隔离范围并直接评估属性:

export class CheckMarkupDirective implements ng.IDirective {
    public static create(): ng.IDirective {
        return {
            restrict: "A",
            require: "ngModel",
            /* REMOVE isolate scope
            scope: {
                netPrice: "<",
                markupAmount: "<"
            },
            */
            link: (scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ngModelCtrl: ng.INgModelController) => {

                let netPrice: number;
                let markupAmount: number;
                //scope.$watchGroup(["netPrice", "markupAmount"],
                //WATCH attributes directly
                scope.$watchGroup([attrs.netPrice, attrs.markupAmount], (newValues, oldValues) => {

                    [netPrice, markupAmount] = newValues;

                    if (markupAmount >= 0) {
                        ngModelCtrl.$setValidity("markup", true);
                    } else {
                        ngModelCtrl.$setValidity("markup", netPrice === 0);
                    }
                    ngModelCtrl.$validate();
                });
            }
        };
    }
}

inputng-modelng-change指令期望一个没有范围的元素。这将删除一次性绑定观察者以及与这些指令作斗争的隔离范围的复杂性。

答案 1 :(得分:1)

我已经重现了我认为您的表单正在做什么,如果我在所有字段(vatRate,purchasePrice,customerPrice)上添加ng-change,我就没有问题。

你能检查我所做的匹配是否与你的打字稿相符吗?如果没有,你能尝试以javascript的形式向我们展示结果吗?

angular.module('test',[]).directive('checkMarkup', [function(){
  return {
            restrict: "A",
            require: "ngModel",
            scope: {
                netPrice: "<",
                markupAmount: "<"
            },
            link: (scope, element, attrs, ngModelCtrl) => {
                var netPrice;
                var markupAmount;
                scope.$watchGroup(["netPrice", "markupAmount"], (newValues, oldValues) => {
                    netPrice= newValues[0];
                    markupAmount = newValues[1];
                    if (markupAmount >= 0) {
                        ngModelCtrl.$setValidity("markup", true);
                    } else {
                        ngModelCtrl.$setValidity("markup", netPrice === 0);
                    }
                    ngModelCtrl.$validate();
                });
            }
        };
}]).controller('ctrl', ['$scope', function($scope){
  $scope.customerPriceChangeDetected = function(){
    setNetPriceFromCustomerPrice();
    setMarkup();
    
};
function setNetPriceFromCustomerPrice() {
    var customerPrice = $scope.product.customerPrice;
    var vatRate = parseFloat($scope.product.vatRate);
    var netPrice = (customerPrice / (1 + vatRate));
    $scope.product.netPrice = netPrice;
};
function setMarkup(){
    var purchasePrice = $scope.product.purchasePrice;
    var markupAmount = $scope.product.netPrice - purchasePrice;
    $scope.product.markupAmount = markupAmount;
    $scope.product.markupPercent = markupAmount / purchasePrice;
}
}]);
 <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app="test" ng-controller="ctrl">
  <form name="form">
      purchasePrice : <input type="text"  name="purchasePrice"
       ng-model="product.purchasePrice"
       ng-change="customerPriceChangeDetected()" 
        />  <br/>
   vatRate : <input type="text"  name="vatRate"
       ng-model="product.vatRate"
       ng-change="customerPriceChangeDetected()" 
        />  <br/>
    
  Customer price : <input type="text" id="customer-price" name="customerPrice"
       ng-model="product.customerPrice"
       ng-change="customerPriceChangeDetected()" 
       check-markup markup-amount="product.markupAmount"
       net-price="product.netPrice" /> <br/>
  </form>
  markupAmount : {{product.markupAmount}} <br/>
  netPrice : {{product.netPrice}} <br/>
  vatRate : {{$scope.product.vatRate}}
   customerPrice invalid : {{form.customerPrice.$invalid}}<br/>
  form invalid : {{form.$invalid}}
</div>