3

我正在尝试编写一个自定义指令,该指令根据指令中也需要可用的其他值验证输入字段。我通过使用带有范围变量的隔离范围来做到这一点。更具体地说,我想将产品的客户价格(即净价格)与购买价格进行比较,如果差异为负(客户价格设置为 0 除外),我想让客户-price 输入(及其周边形式)无效。这是我的指令:

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();
                });
            }
        };
    }
}

这就是我在被表单标签包围的 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" />

它可以工作,但问题是验证部分似乎是“时间错误”,这意味着如果我输入一个导致“标记”第一次变为负数的值,则设置表单的 $invalid 值为假。但是当下一个输入为负时,验证将启动并起作用。我认为我的问题是我在不同步骤之间进行了大量计算,但我很难知道是什么导致验证如此偏离。我想我希望对 Angular JS 机制有更深入了解的人来看看我是否在做明显错误的事情。如果我的描述有点含糊,请提前致谢。

编辑:以为我还会包括在 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 基本上返回 form.$valid 并且对于我的所有其他自定义验证器都可以正常工作。

编辑 2:添加了屏幕截图,显示周围的 ng-form 标记似乎至少将其 $invalid 属性设置为 true: 在此处输入图像描述

编辑 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 检查。

4

2 回答 2

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();
                });
            }
        };
    }
}

input和指令需要一个没有范围的元素ng-modelng-change这消除了一次性绑定观察者以及与这些指令对抗的隔离范围的复杂性。

于 2016-12-25T20:09:09.790 回答
1

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>

于 2016-12-25T21:27:48.533 回答