2016-12-25 100 views
3

我正在尝试编写一个自定义指令,该指令根据指令中需要的其他值验证输入字段。我通过使用范围变量使用隔离范围来实现这一点。更具体地说,我想比较产品的客户价格(即其净价格)与购买价格,如果差异是负数(客户价格设置为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(); 
       }); 
      } 
     }; 
    } 
} 

这是我如何使用它AA NG型格由表单标签包围内:

<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值设置为false。但是当下一个输入是负数时,验证就会启动并运行。我认为我的问题是我在不同的步骤之间进行了大量的计算,但是我很难知道是什么导致验证如此关闭。我想我希望有更深入的Angular JS机制知识的人有更大的胆量,并告诉我我是否做了明显错误的事情。如果我的描述有些模糊,请提前致谢并抱歉。

编辑:思想我还包括那些在NG-变化引发的方法:

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

验证服务吸气基本形式返回$有效和工作完全为我所有的其他自定义验证。

编辑2:新增截图,显示了周围的NG-form标签似乎有$无效的属性设置为true ATLEAST: enter image description here

编辑3: 这里的transpiled 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检查在指令有时间来真正改变有效性之前触发。

+0

我'不知道明白为什么你叫'$的validate()'。此外,当标记为无效的字段由于ng-model不会更新为无效值时,不会调用ng-change。因此,尝试在表单外打印“customerPrice/markupAmount/netPrice”的值以查看值是否已刷新。 – Walfrat

+0

嗨,感谢您花时间帮助我。我已经在手表中使用过console.log,看到每次更改客户价格输入字段的值时,markupAmount和netprice都会更新,以至于atleast的部分似乎按预期工作(?)在我看来,验证总是落后一步,因此如果我从一个有效的输入开始并将其更改为无效的输入(负面标记),表单仍然会有$ invalid = false。真烦人的部分是,与ng形式的div似乎有$ invalid = true Whick真的让我困惑。 – Kristofer

回答

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-model,和ng-change指令不期望范围的元件。这消除了一次性绑定观察者以及与这些指令相冲突的隔离范围的复杂性。

+0

尝试删除隔离范围,并使用attrs.markupAmount和attrs.netPrice,但我仍然得到相同的行为。我不知道关于ng-model和ng-change的部分虽然不期望范围。不知道我是否真的了解它。 :( – Kristofer

1

我有什么重现,我认为你的形式在做什么,我有没有问题,如果我添加了ng-change所有字段(vatRate,进价,customerPrice)。

你可以检查我的确匹配了你的打字稿吗?如果不是,你可以尝试向我们展示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>

+0

感谢您的帮助,我想我已经设法缩小到我的ng-change方法customePriceChangeDetected在指令有机会真正改变合法性之前触发我的验证服务的isValid-check(表单)假设我需要重新思考这个逻辑 – Kristofer

+0

$ watch在角度循环结束时被触发,奇怪的是对于那个样本,我没有重现你的问题。我也很奇怪,我会做的只是将整个产品对象传递给您的指令并执行指令中的所有操作(使用双向绑定可以更新netPrice/markupAmount) – Walfrat

+0

您可能是对的。最重要的是,我开始时已经在productDetailsForm的控制器内部进行了计算,然后在稍后阶段,您不应该能够使用负值ive标记被添加,这就是为什么我一直在考虑我的指令纯粹是作为自定义验证,而不是实际执行任何计算的东西。 – Kristofer