2017-06-16 76 views
0

UPDATE1:開始使用ngProgress,但在IE中沒有給出所需的效果。
最終更新:找到最佳解決方案。見下面的最後一個答案在IE中慢速加載AngularJS應用程序 - 添加進度條


的AngularJS應用程序有多個選項卡,每個選項卡可以具有多達100個字段。使用多個Ajax調用從數據庫中檢索數據,並使用相關的循環來初始化以下各項:驗證規則,下拉列表項以及最後的字段值。在某些情況下,我們使用Javascript和AngularJS方法來獲得所需的效果。

請注意,驗證規則的加載涉及修改指令,如ng-requiredng-max,這將需要使用$compile來激活驗證規則。

這個問題有兩個部分:IE下

  • AngularJS應用具有顯着的加載速度慢的效果。在Chrome瀏覽器下,加載速度要好得多。

我們如何解決和分析IE下的緩慢加載問題以查明問題的位置?我如何在IE下使用性能分析工具?

  • 與此同時,想要在完成加載上述每個數據部分之後添加進度條以進行更新:驗證規則,下拉列表項和字段值。

我在我的項目中實現了ngProgress插件,它在Chrome下工作正常,但在IE下它沒有提供所需的效果。進度條將在頁面加載結束時顯示並完成。它似乎在IE瀏覽器下,進度條不會立即顯示在頁面的開始。請注意,在我的項目中,我廣泛使用指令,其中大量使用$compile服務。

我做了一些研究,並且意識到IE不會立即更新DOM顯示......它將一直等到稍後階段全部更新,或者至少這是我的理解。所以這裏的訣竅是如何強制IE儘快反映DOM的變化。

我用這種方法這有助於獲得更好的效果IE下:

app.controller('formMainController', ['$scope', '$timeout', '$interval', 'ngProgressFactory', 
       function($scope, $timeout, $interval, $q, ngDialog, ngProgressFactory) { 
    $scope.progressbar = ngProgressFactory.createInstance(); 
    $scope.progressbar.start(); 
    $scope.stopProgressbar = $interval(function(){ 
     $scope.progressbar.setParent(document.getElementsByTagName("BODY")[0]); 
    },10); 
     ... 
     ... 
     //After getting all data from DB 
    $scope.mainPromise.then(function(success) { 
     $interval.cancel($scope.stopProgressbar); 
     $timeout(function(){ 
      $scope.progressbar.complete(); 
     }, 3000); 
     return 'main promise done'; 
    }) 
}]); 

通過以上,IE下,我可以看到進度條顯示比以前更早,那麼它將使2個進步,然後它會凍結約2秒,然後正常繼續。當看到控制檯窗口時,我可以看到它會凍結,而其他許多指令正在執行中,尤其是使用$compile服務的指令,其中priority: 100terminal: true,

任何想法如何使它更好?

注:This thread也有類似的問題,但我沒有找到相關的解決方案。

塔裏克

回答

1

從你所說的加載速度慢是由於工作AngularJS,而不是數據加載[由事實比它慢的Chrome在IE證明。如果這是真的,那麼加載指示器不會有幫助,因爲它也會凍結。

您是好得多以下的角度,如正常的表現手法:在網頁上少

  • 顯示,用戶肯定犯規需要看100場一次?考慮分頁。
  • 對於不改變使用綁定一次,像這樣的項目:{{::vm.name}}
  • 上的把手主體,其通常更有效地使用<div ng-bind="::vm.name"></div>而不是車把{{::vm.name}}
+0

非常感謝。您的反饋非常有用。主要問題是隻在IE中出現問題。爲了實現分頁,這是可行的,但非常昂貴(根據我對如何實現分頁的知識)。是否值得解決IE問題?請注意,此應用程序已從使用大型動態PDF表單遷移到使用基於HTML5 Angular的表單。所以現在用戶處於一個更好的位置,因爲他現在正在使用標籤式UI,而不是一次加載15頁的PDF表單。 – tarekahf

+1

它可能只發生在計算機上的IE中,但它也可能發生在比較慢的PC上的Chrome上。正確分頁不應該更昂貴,通常更高效的性能。 – Chris

+0

猜猜看!現在,即使在將一個非常大的表單轉換爲Angularjs之後,我們仍然遇到性能問題...我認爲分頁是現在最好的方法。我想知道是否有建立這樣的應用程序的模型... – tarekahf

0

這是基於我的解決辦法通過上面的@andrew的解決方案並使用ngProgress Bar component

CSS:

#ngProgress-container.block-editing { 
    pointer-events: all; 
    z-index: 99999; 
    border: none; 
    /* margin: 0px; */ 
    padding: 0px; 
    width: 100%; 
    height: 100%; 
    top: 0px; 
    left: 0px; 
    cursor: wait; 
    position: fixed; 
    background-color: rgba(0, 0, 0, 0.33); 
    margin-top:10px; 
    #ngProgress { 
     margin-top:-9px; 
     width:5px; /* Force display progress as early as possible */ 
     opacity:1; /* Force display progress as early as possible */ 
    } 
} 

JS - 在開始的時候:

$scope.progressbar = ngProgressFactory.createInstance(); 
//To force display of progress bar as early as possible 
$scope.progressbar.setParent(document.getElementsByTagName("BODY")[0]); 
$scope.progressbar.set(1); 
$scope.progressbar.getDomElement().addClass('block-editing'); 
$scope.stopProgressbar = $timeout(function(){ 
    $scope.progressbar.setParent(document.getElementsByTagName("BODY")[0]); 
},10); 
$timeout(function(){ 
    $scope.progressbar.start(); 
},100); 

JS - 到底:

//Stop progress bar 
$interval.cancel($scope.stopProgressbar); 
$timeout(function(){ 
    //JIRA: NE-2984 - un-block editing when page loading is done 
    $($scope.progressbar.getDomElement()).fadeOut(2000, function() { 
     $($scope.progressbar.getDomElement()).removeClass('block-editing'); 
    }); 
    $scope.progressbar.complete(); 
}, 3000); 
0

問題達到1000+場後變得更糟。 IE 11花了3分鐘以上完成加載。我做了進一步的優化,現在的結果如下的時間完成加載:

據證實,瓶頸是在循環,將加載驗證規則並將其應用於元素,然後它將使用$compile服務執行編譯。使用requiredFieldsPromise檢索。查看下面的代碼示例。

以下爲指令check-if-required新更新的代碼:

app.directive('checkIfRequired', function($compile, $parse, $interpolate, $timeout, $q, BusinessLogic){ 
    return { 
     priority: 100, 
     terminal: true, 
     restrict: 'A', 
     require: '?^form', 
     link: function (scope, el, attrs, ngForm) { 
      var saveIsValidationRequired; 
      var mainElmID = $interpolate(el[0].id)(scope); 
      var resolvedPromise; 
      var getChildren = function() { 
       var resultChildren; 
       //Return list of elements which were not compiled before 'compiled === undefined' 
       resultChildren = $(':input', el); 
       //Use code below just in case we want to extract the elements which are not compiled. 
       /*resultChildren = $(':input', el).filter(function(){ 
        var result; 
        result = 
         ($(this).attr('compiled') === undefined) 
        return result; 
       });*/ 
       //Use $interpolate to get the final result for each ID... 
       for (var i=0; i < resultChildren.length; i++) { 
        if (resultChildren[i].id) { 
         resultChildren[i].id = $interpolate(resultChildren[i].id)(scope); 
        } 
       } 
       return resultChildren; 
      } 
      //User resolvedPromise when no such promise is available. 
      resolvedPromise = $q.when('resolved'); 
      //Code improvement to make this directive more general 
      //  Since this directive can be used from within an isolated scope directive such as 'photo-list-upload', then 
      //  additional parameters are required to make it work properly. 
      //  Make sure all required functions are defined or report warning. 
      //  If the function is not defined within 'scope' it will be looked up from within 'BusinessLogic.getScope()'. 
      //  If not found at all, default is used, and warning is reported. 
      scope.getIsValidationRequired = scope.getIsValidationRequired || 
             (BusinessLogic.getScope().getIsValidationRequired) || 
             (console.warn("Directive 'check-if-required' element '%s' - function 'scope.getIsValidationRequired()' is not defined. It will always be false.", mainElmID), 
             function() { 
              return false; 
             }  
             ); 
      //The promise 'requiredFieldPromise' is used to retrieve list of validation rules from DB 
      //Break Point Condition: scope.listData.photosFormName == "subjectPhotos" 
      scope.stopExecValidations = scope.stopExecValidations || BusinessLogic.getScope().stopExecValidations || 
         (console.warn("Directive 'check-if-required' element '%s' - function 'scope.stopExecValidations()' is not defined. Dummy function is used instead.", mainElmID), 
          function() { 
          //Dummy 
          } 
         ); 
      scope.requiredFieldsPromise = 
       scope.requiredFieldsPromise || (BusinessLogic.getScope().requiredFieldsPromise) || 
       (console.warn("Directive 'check-if-required' element '%s' - function 'scope.requiredFieldsPromise' is not defined. Resolved promise will be used.", mainElmID), 
       resolvedPromise); 
      //If needed, stop validation while adding required attribute 
      //Save current flag value 
      saveIsValidationRequired = scope.getIsValidationRequired(); 
      scope.stopExecValidations(); 
      //remove the attribute `check-if-required` to avoid recursive calls 
      el.removeAttr('check-if-required');   
      // NE-2808 - Define function to add validation message using $watch 
      //    As soon as an error is detected, then 'title' will be set to the error 
      //    Parameters: 
      //     - ngForm: Angualr Form 
      //     - elm: The HTML element being validated 
      //     - errAttr: the name of the error attribute of the field within ngForm: 
      //      ngFormName.FieldName.$error.errAttributeName 
      //     - errMsg: The error message to be added to the title 
      //     - msgVar1: optional substitution variable for the error message 
      var addValidationMessage = function (ngForm, elm, errAttr, errMsg, msgVar1) { 
       //Use $timeout to ensure validation rules are added and compiled. 
       //After compile is done then will start watching errors 
       $timeout(function(){ 
        var elmModel; 
        var ngModelName=""; 
        //Get the name of the 'ng-model' of the element being validated 
        elmModel = angular.element(elm).controller('ngModel'); 
        if (elmModel && elmModel.$name) { 
         ngModelName = elmModel.$name; 
        } 
        if (!ngModelName) { 
         ngModelName = angular.element(elm).attr('ng-model'); 
        } 
        if (ngModelName) { 
         scope.$watch(ngForm.$name + '.' + ngModelName + '.$error.' + errAttr, 
          function (newValue, oldValue){ 
           //console.log("elm.id =", elm.id); 
           //The validation error message will be placed on the element 'title' attribute which will be the field 'tooltip'. 
           //newValue == true means there is error 
           if (newValue) { 
            var msgVar1Val; 
            //Perform variable substitution if required to get the final text of the error message. 
            if (msgVar1) { 
             msgVar1Val = scope.$eval(angular.element(elm).attr(msgVar1)); 
             errMsg = errMsg.format(msgVar1Val); 
            } 
            //Append the error to the title if neeeded 
            if (elm.title) { 
             elm.title += " "; 
            } else { 
             elm.title = ""; 
            } 
            elm.title += errMsg; 
           } else { 
            //Remove the error if valid. 
            //child.removeAttribute('title'); 
            if (elm.title) { 
             //Remplace the error message with blank. 
             elm.title = elm.title.replace(errMsg, "").trim(); 
            } 
           } 
          }); 
        } else { 
         //console.warn("Warning in addValidationMessage() for element ID '%s' in ngForm '%s'. Message: 'ng-model' is not defined.", elm.id, ngForm.$name) 
        } 
       }, 1000);  
      }    
      function doApplyValidation(scope, el, attrs, ngForm) { 
       var children; 
       children = getChildren(); 
       mainElmID = $interpolate(el[0].id)(scope); 
       validationList=formView.getRequiredField(); 
       for (var subformIdx=0; subformIdx < Object.keys(validationList).length; subformIdx++) { 
        var keySubform = Object.keys(validationList)[subformIdx]; 
        var subform = validationList[keySubform]; 
        var lastFieldID; 
        lastFieldID = Object.keys(subform)[Object.keys(subform).length-1]; 
        for (var childIdx=0; childIdx < Object.keys(subform).length; childIdx++) { 
         var childID = Object.keys(subform)[childIdx]; 
         var validObjects; 
         var childElm; 
         var child; 
         var elmScope; 
         var elmModel; 
         childID = childID.trim(); 
         //Find the element with id = childID within the 'el' section. 
         //Use 'getChildren()' since the result list has ID values which are interpolated. 
         childElm = children.filter('#'+childID); 
         if (childElm.length) { 
          //Validation rule for 'childID': related element was found, and now will apply validation rule. 
          validObjects = subform[childID]; 
          child = childElm.get(0); 
          elmScope = angular.element(child).scope() || scope; 
          elmModel = angular.element(child).controller('ngModel'); 
          var maxlength = scope.$eval(angular.element(child).attr('ng-maxlength')); 
          //var errMsg = ("Number of characters entered should not exceed '{0}' characters.").format(maxlength); 
          // NE-2808 - add validation message if length exceeds the max 
          var errMsg = "Number of characters entered should not exceed '{0}' characters."; 
          addValidationMessage(ngForm, child, 'maxlength', errMsg, 'ng-maxlength');       //Check if the element is not in "Required" list, and it has an expression to control requried, then 
          //... add the attribute 'ng-required' with the expression specified to the element and compile. 
          if (!angular.element(child).prop('required') && child.attributes.hasOwnProperty("check-if-required-expr")) { 
           console.error("Unexpected use for attribute 'check-if-required-expr' in directive 'check-if-required' for element ID '%s'. Will be ignored.", childID); 
          } 
          if (validObjects === "") { 
           //This means the field is required 
           angular.element(child).attr('ng-required', "true"); 
          } 
          else if (angular.isArray(validObjects)) { 
           //This means that there is a list of validation rules 
           for (var idx=0; idx < validObjects.length; idx++) { 
            var validObject = validObjects[idx]; 
            var test = validObject.test || "true"; //if not exist, it means the rule should always be applied 
            var minLenExp = validObject.minlen; 
            var maxLenExp = validObject.maxlen; 
            var isRequiredExp = validObject.required || false; 
            var readonlyExp = validObject.readonly || null; 
            var pattern = validObject.pattern || ""; 
            var isCAPostalCode = validObject.isCAPostalCode || false; 
            isRequiredExp = angular.isString(isRequiredExp)?isRequiredExp:isRequiredExp.toString(); 
            if (test) { 
             var testEval = scope.$eval(test, elmScope); 
             if (testEval) { 
              if (minLenExp) { 
               angular.element(child).attr('ng-minlength', minLenExp); 
              } 
              if (maxLenExp) { 
               angular.element(child).attr('ng-maxlength', maxLenExp); 
              } 
              //If the "required" expression is '*skip*' then simply skip. 
              //If '*skip*' is used, this means the required validation is already defined in code 
              //and no need to replace it. 
              if (isRequiredExp && isRequiredExp != '*skip*') { 
               angular.element(child).attr('ng-required', isRequiredExp); 
              } 
              // NE-3211 - add readonly validation 
              if (readonlyExp && readonlyExp != '*skip*') { 
               angular.element(child).attr('ng-readonly', readonlyExp); 
              } 
              if (pattern) { 
               angular.element(child).attr('ng-pattern', pattern); 
              } 
              if (isCAPostalCode) { 
               angular.element(child).attr('ng-pattern', "/^([A-Z]\\d[A-Z] *\\d[A-Z]\\d)$/i"); 
               // NE-2808 - add validation message if postal code does not match the RegEx 
               addValidationMessage(ngForm, child, 'pattern', "Invalid postal code."); 
              } 
              //delete the validation rule after it is implemented to improve performance 
              delete subform[childID] 
              //TODO: Apply only the first matching validation rule 
              //  May required further analysis if more that one rule will be added. 
              break; 
             } 
            } 
           } 
          } 
         } 
        } // for loop 
       } // for loop 
       //After done processing all elements under 'el', compile the parent element 'el'. 
       $compile(el, null, 100)(scope); 
       //If saved flag value is true, enable back validation 
       if (saveIsValidationRequired) { 
        scope.startExecValidations(); 
       } 
      } 
      function applyValidationTimeout() { 
       //Execute 'doApplyValidation()' in the next cycle, to ensure the child elements have been rendered. 
       $timeout(function(){ 
        //console.log('applyValidationTimeout', mainElmID); 
        doApplyValidation(scope, el, attrs, ngForm);      
       }, 100) 
      } 
      scope.requiredFieldsPromise.then(function(success) { 
       //Apply validation when the Required Fields and Validation Rules have been loaded. 
       applyValidationTimeout(); 
      }, function(prmError){ 
       console.warn("Error occured in 'check-if-required' directive while retrieving 'requiredFieldsPromise' for element '%s': %s", mainElmID, prmError); 
      }); 
     } 
    } 

}); 

雖然性能是現在好多了,但是,我意識到這個問題是在使用$compile,因此,我現在想找到避免使用$compile的解決方案。這是我的計劃。相反通過將「NG-需要」指令修改所述元件HTML的

,然後編譯,相反,我可以跳過HTML和使用相關的HTML元素的ngModel.NgModelController,然後訪問$validators使用代碼來執行驗證。如果您閱讀上面的代碼,您將看到我已經訪問ngModel.NgModelController變量elmModel中的每個元素。我認爲這個變量將提供$validators的訪問權限,可以用來爲元素添加驗證。由於規則現在在validationList變量中可用,因此我將編寫一個函數來執行驗證,方法是查找此列表並應用可用的驗證。

這將是未來衝刺的改進。

如果您有任何反饋,請讓我知道。

塔裏克

0

最後,我是能夠實現Chrome和IE最好的可接受的性能。

以下是在前面的代碼解決了這一問題的主要變化:

  • 應用它後,不要刪除驗證規則。以下代碼已被評論:
    //delete validationList[childID]
  • 如果可能,請使用HTML規則代替AngularJS'ng-'規則。例如,使用required='required'而不是ng-required='true'
  • 僅在需要時添加驗證規則,因此如果它爲'false'不相關,請不要添加它。
  • 請勿添加動態的錯誤消息「NG-MAXLEN」
  • 使用驗證規則結構與一個層,也就是現在似乎更好的工作。

以下是指令check-if-required更新的代碼:

app.directive('checkIfRequired', function($compile, $parse, $interpolate, $timeout, $q, BusinessLogic){ 
    return { 
     priority: 100, 
     terminal: true, 
     restrict: 'A', 
     require: '?^form', 
     link: function (scope, el, attrs, ngForm) { 
      var saveIsValidationRequired; 
      var mainElmID = $interpolate(el[0].id)(scope); 
      var resolvedPromise; 
      var getChildren = function(el, doInterpolate) { 
       var resultChildren; 
       doInterpolate = (doInterpolate===undefined)?true:doInterpolate; 
       //Return list of elements which were not compiled before 'compiled === undefined' 
       resultChildren = $(':input', el); 
       //Use code below just in case we want to extract the elements which are not compiled. 
       //Use $interpolate to get the final result for each ID... 
       if (doInterpolate) { 
        for (var i=0; i < resultChildren.length; i++) { 
         if (resultChildren[i].id) { 
          resultChildren[i].id = $interpolate(resultChildren[i].id)(scope); 
         } 
        } 
       } 
       //resultChildren = resultChildren.filter("[id!='']"); 
       return resultChildren; 
      } 
      //User resolvedPromise when no such promise is available. 
      resolvedPromise = $q.when('resolved'); 
      //Code improvement to make this directive more general 
      //  Since this directive can be used from within an isolated scope directive such as 'photo-list-upload', then 
      //  additional parameters are required to make it work properly. 
      //  Make sure all required functions are defined or report warning. 
      //  If the function is not defined within 'scope' it will be looked up from within 'BusinessLogic.getScope()'. 
      //  If not found at all, default is used, and warning is reported. 
      scope.getIsValidationRequired = scope.getIsValidationRequired || 
             (BusinessLogic.getScope().getIsValidationRequired) || 
             (console.warn("Directive 'apply-validation' element '%s' - function 'scope.getIsValidationRequired()' is not defined. It will always be false.", mainElmID), 
             function() { 
              return false; 
             }  
             ); 
      //The promise 'requiredFieldPromise' is used to retrieve list of validation rules from DB 
      //Break Point Condition: scope.listData.photosFormName == "subjectPhotos" 
      scope.stopExecValidations = scope.stopExecValidations || BusinessLogic.getScope().stopExecValidations || 
         (console.warn("Directive 'apply-validation' element '%s' - function 'scope.stopExecValidations()' is not defined. Dummy function is used instead.", mainElmID), 
          function() { 
          //Dummy 
          } 
         ); 
      scope.requiredFieldsPromise = 
       scope.requiredFieldsPromise || (BusinessLogic.getScope().requiredFieldsPromise) || 
       (console.warn("Directive 'apply-validation' element '%s' - function 'scope.requiredFieldsPromise' is not defined. Resolved promise will be used.", mainElmID), 
       resolvedPromise); 
      //If needed, stop validation while adding required attribute 
      //Save current flag value 
      saveIsValidationRequired = scope.getIsValidationRequired(); 
      scope.stopExecValidations(); 
      //remove the attribute `check-if-required` to avoid recursive calls 
      el.removeAttr('check-if-required');   
      //Define function to add validation message using $watch 
      //    As soon as an error is detected, then 'title' will be set to the error 
      //    Parameters: 
      //     - ngForm: Angualr Form 
      //     - elm: The HTML element being validated 
      //     - errAttr: the name of the error attribute of the field within ngForm: 
      //      ngFormName.FieldName.$error.errAttributeName 
      //     - errMsg: The error message to be added to the title 
      //     - msgVar1: optional substitution variable for the error message 
      var addValidationMessage = function (ngForm, elm, errAttr, errMsg, msgVar1, elmScope, elmModel) { 
       //Use $timeout to ensure validation rules are added and compiled and that the 'elmModel' is available. 
       //After compile is done then will start watching errors 
       $timeout(function(){ 
        var ngModelName=""; 
        //Get the name of the 'ng-model' of the element being validated 
        elmScope = elmScope || scope; 
        elmModel = elmModel || angular.element(elm).controller('ngModel'); 
        if (elmModel && elmModel.$name) { 
         ngModelName = elmModel.$name; 
        } 
        if (!ngModelName) { 
         ngModelName = angular.element(elm).attr('ng-model'); 
        } 
        if (ngModelName) { 
         elmScope.$watch(ngForm.$name + '.' + ngModelName + '.$error.' + errAttr, 
          function (newValue, oldValue){ 
           //console.log("elm.id =", elm.id); 
           //The validation error message will be placed on the element 'title' attribute which will be the field 'tooltip'. 
           //newValue == true means there is error 
           if (newValue) { 
            var msgVar1Val; 
            //Perform variable substitution if required to get the final text of the error message. 
            if (msgVar1) { 
             msgVar1Val = elmScope.$eval(angular.element(elm).attr(msgVar1)); 
             errMsg = errMsg.format(msgVar1Val); 
            } 
            //Append the error to the title if neeeded 
            if (elm.title) { 
             elm.title += " "; 
            } else { 
             elm.title = ""; 
            } 
            elm.title += errMsg; 
           } else { 
            //Remove the error if valid. 
            //child.removeAttribute('title'); 
            if (elm.title) { 
             //Replace the error message with blank. 
             elm.title = elm.title.replace(errMsg, "").trim(); 
            } 
           } 
          }); 
        } else { 
         //console.warn("Warning in addValidationMessage() for element ID '%s' in ngForm '%s'. Message: 'ng-model' is not defined.", elm.id, ngForm.$name) 
        } 
       }, 1000);  
      } 
      //Refactor - apply validation rule for a given element with `childID` 
      function applyValidationElement(childID, child, childElm, elmScope, elmModel, validationList) { 
       //Validation rule for 'childID': related element was found, and now will apply validation rule. 
       var validObjects; 
       var errMsg; 
       validObjects = validationList[childID]; 
       //Check if the element is not in "Required" list, and it has an expression to control requried, then 
       //... add the attribute 'ng-required' with the expression specified to the element and compile. 
       //No longer use `check-if-required-expr`, must report error if used. 
       if (!angular.element(child).prop('required') && child.attributes.hasOwnProperty("check-if-required-expr")) { 
        console.error("Unexpected use for attribute 'check-if-required-expr' in directive 'apply-validation' for element ID '%s'. Will be ignored.", childID); 
       } 
       if (validObjects === "") { 
        //This means the field is required 
        angular.element(child).attr('required', 'required'); 
       } 
       else if (angular.isArray(validObjects)) { 
        //This means that there is a list of validation rules 
        for (var idx=0; idx < validObjects.length; idx++) { 
         var validObject = validObjects[idx]; 
         var test = validObject.test || "true"; //if not exist, it means the rule should always be applied 
         var minLenExp = validObject.minlen; 
         var maxLenExp = validObject.maxlen; 
         var isRequiredExp = validObject.required || "false"; 
         var readonlyExp = (validObject.readonly || "").toString().trim(); 
         var pattern = validObject.pattern || ""; 
         var isCAPostalCode = (validObject.isCAPostalCode || "false").toString().trim(); 
         isRequiredExp = (angular.isString(isRequiredExp)?isRequiredExp:isRequiredExp.toString()).trim(); 
         if (test) { 
          var testEval = scope.$eval(test, elmScope); 
          if (testEval) { 
           if (minLenExp) { 
            angular.element(child).attr('ng-minlength', minLenExp); 
           } 
           if (maxLenExp) { 
            angular.element(child).attr('maxlength', maxLenExp); 
            //For now, to improve performance, do not add validation message - if length exceeds the max 
            //errMsg = "Number of characters entered should not exceed '{0}' characters."; 
            //addValidationMessage(ngForm, child, 'maxlength', errMsg, 'ng-maxlength', elmScope, elmModel); 
           } 

           //Add attributes only if needed 
           if (isRequiredExp.toLowerCase() === "true") { 
            angular.element(child).attr('required', 'required'); 
           } else 
           //If the "required" expression is '*skip*' then simply skip. 
           //If '*skip*' is used, this means the required validation is already defined in code 
           //and no need to replace it. 
           if (isRequiredExp && isRequiredExp.toLowerCase() !== "false" && isRequiredExp !== '*skip*') { 
            angular.element(child).attr('ng-required', isRequiredExp); 
           } 

           if (readonlyExp.toLowerCase() === "true" && readonlyExp != '*skip*') { 
            angular.element(child).attr('readonly', 'readonly'); 
           } else 
           //Add readonly validation 
           if (readonlyExp && readonlyExp.toLowerCase() !== "false" && readonlyExp != '*skip*') { 
            angular.element(child).attr('ng-readonly', readonlyExp); 
           } 
           if (pattern) { 
            angular.element(child).attr('ng-pattern', pattern); 
           } 
           if (isCAPostalCode.toLowerCase() === "true") { 
            angular.element(child).attr('ng-pattern', "/^([A-Z]\\d[A-Z] *\\d[A-Z]\\d)$/i"); 
            //Add validation message if postal code does not match the RegEx 
            addValidationMessage(ngForm, child, 'pattern', "Invalid postal code.", null, elmScope, elmModel); 
           } 
           //TODO: delete the validation rule after it is implemented to improve performance 
           //  verify if deleting the key is OK and will not distroy the for-loop index 
           //delete validationList[childID] 
           //TODO: Apply only the first matching validation rule 
           //  May required further analysis if more that one rule will be added. 
           break; 
          } 
         } 
        } 
       } 
      } 
      //Check of trim for Field ID is done - not needed if field ID is already timmed in `validationList`. 
      //BusinessLogic.getScope().mainVM.validFieldIDTrimDone = BusinessLogic.getScope().mainVM.validFieldIDTrimDone || false; 
      //var validFieldIDTrimDone; 
      function doApplyValidation(scope, el, attrs, ngForm) { 
       var children; 
       var fieldValidList; 
       var validationStructOpt; 
       var mainElmID; 
       children = getChildren(el, true); //Do run interpolation of elements IDs 
       //children = resultChildren = $(':input', el); //getChildren(el, false); //Do not run interpolation of elements IDs 
       mainElmID = $interpolate(el[0].id)(scope); 
       validationList=formView.getRequiredField(); 
       //Get 'validationStructOpt' option: 
       //  Option = 'onelayer' means there is no 'subform' layer 
       //  Option = 'twolayers' means there is a 'subform' layer which is the default 
       validationStructOpt = validationList.$structureOpt || 'twolayers'; 
       if (validationStructOpt === 'onelayer') { 
        //for (var fldIdx=0; fldIdx < Object.keys(validationList).length; fldIdx++) { 
        //console.log("One layer. Number of rules: ", Object.keys(validationList).length) 
        for (var fldIdx=0; fldIdx < children.length; fldIdx++) { 
         var childElm; 
         var child; 
         var childID; 
         var validObjects; 
         var elmScope; 
         var elmModel; 
         //childID = Object.keys(validationList)[fldIdx]; 
         //if (childID.startsWith('$')) { 
         // continue; 
         //} 
         //childElm = children.filter('#'+childID); 
         //child = childElm.get(0); 
         childElm = children.eq(fldIdx); 
         child = childElm.get(0); 
         child.id = $interpolate(child.id)(scope); 
         childID = child.id; 
         //if (childElm.length) { 
         if (childID && (childID in validationList)) { 
          //Validation rule for 'childID': related element was found, and now will apply validation rule. 
          validObjects = validationList[childID]; 
          elmScope = angular.element(child).scope() || scope; 
          elmModel = angular.element(child).controller('ngModel'); 
          applyValidationElement(childID, child, childElm, elmScope, elmModel, validationList); 
         } 
        } 
       } else 
       if (validationStructOpt === 'twolayers') { 
        //angular.forEach(Object.keys(validationList), function(keySubform, subformIdx){ 
        for (var subformIdx=0; subformIdx < Object.keys(validationList).length; subformIdx++) { 
         var keySubform = Object.keys(validationList)[subformIdx]; 
         if (!keySubform.startsWith('$')) { 
          var subform = validationList[keySubform]; 
          for (var childIdx=0; childIdx < Object.keys(subform).length; childIdx++) { 
           var childID = Object.keys(subform)[childIdx]; 
           var validObjects; 
           var childElm; 
           var child; 
           var elmScope; 
           var elmModel; 
           //console.log(subform, validObjects, childID); 
           //Find the element with id = childID within the 'el' section. 
           //childElm = $('#'+childID, el); 
           //Use 'getChildren()' since the result list has ID values which are interpolated. 
           childElm = children.filter('#'+childID); 
           //console.log(el[0].id, childID); 
           if (childElm.length) { 
            //Validation rule for 'childID': related element was found, and now will apply validation rule. 
            validObjects = subform[childID]; 
            child = childElm.get(0); 
            elmScope = angular.element(child).scope() || scope; 
            elmModel = angular.element(child).controller('ngModel'); 
            applyValidationElement(childID, child, childElm, elmScope, elmModel, validationList[keySubform]); 
           } 
          } 
         } 
        } //Object.keys(validationList).length 
        //}); 
       } 
       //After done processing all elements under 'el', compile the parent element 'el'. 
       $compile(el, null, 100)(scope); 
       //If saved flag value is true, enable back validation 
       if (saveIsValidationRequired) { 
        scope.startExecValidations(); 
       } 
      } 

      function applyValidationTimeout() { 
       //Execute 'doApplyValidation()' in the next cycle, to ensure the child elements have been rendered. 
       $timeout(function(){ 
        //console.log('applyValidationTimeout', mainElmID); 
        doApplyValidation(scope, el, attrs, ngForm);      
       }, 100) 
      } 
      scope.requiredFieldsPromise.then(function(success) { 
       //Apply validation when the Required Fields and Validation Rules have been loaded. 
       applyValidationTimeout(); 
      }, function(prmError){ 
       console.warn("Error occured in 'apply-validation' directive while retrieving 'requiredFieldsPromise' for element '%s': %s", mainElmID, prmError); 
      }); 
     } 
    } 
}); 

以下是裝載1000多場,並驗證規則的性能測試結果:

  • IE11無負載驗證規則:35〜 40秒
  • 具有驗證規則(1層)的IE11:50至60秒
  • 鉻無負載驗證規則:7-9秒
  • 鉻與驗證規則(一層):9-11秒
0

這是最後的版本,並且在性能方面是最好的。

它是基於以下幾點:元素已經呈現

  • 負載驗證規則之後。
  • 僅在需要時執行驗證規則
  • 使用ngModelController.$validators屬性加載驗證規則。
  • 請勿使用HTML代碼進行驗證。
  • 避免使用directive$compile完全

下面是一個將用於加載驗證規則的代碼:下面的代碼可用於加載和觸發驗證

//Define general function to add/remove error message from the title attribute 
var conErrMsgSep = " | "; 
var conErrMsgSepTrim = conErrMsgSep.trim(); 
function addRemoveValidationMessage(elem, isValid, errMsg) { 
     if (!isValid && elem.get(0).title.indexOf(errMsg) === -1){ 
      //Add message 
      if (elem.get(0).title.trim()) { 
       elem.get(0).title += conErrMsgSep; 
      } else { 
       elem.get(0).title = ""; 
      } 
      elem.get(0).title += errMsg; 
     } else 
     if (isValid && elem.get(0).title.indexOf(errMsg) !== -1) { 
      //Remove message 
      elem.get(0).title = (elem.get(0).title.replace(errMsg, "")).trim(); 
      if (elem.get(0).title.endsWith(conErrMsgSepTrim)) { 
       elem.get(0).title = elem.get(0).title.substring(0, elem.get(0).title.length-1).trim(); 
      } 
      if (elem.get(0).title.startsWith(conErrMsgSepTrim)) { 
       elem.get(0).title = elem.get(0).title.substring(1, elem.get(0).title.length).trim(); 
      } 
     } 
} 

//Define Class/Object to handle adding/removing error messages 
//This will be used to save the last error message used, and update 'title' correctly. 
function AddRemoveValidationMessage(elem, validatorKey) { 
    this.elem = elem; 
    //validatorKey is the rule key. For now, not yet used. 
    this.validatorKey = validatorKey; 
    this.isValid = true; //Default is always valid 
    this.errMsg = ""; 
    this.addRemoveMessage = function(isValid, errMsg) { 
     if (isValid === undefined) { 
      isValid = true; 
     } 
     if ((!this.isValid && !isValid) || isValid) { 
      //Last was invalid, and now also invalid, must reomve the old saved error message from 'title' 
      //and, if now valid, must remove the old saved error message also 
      addRemoveValidationMessage(this.elem, true, this.errMsg); //Remove message from 'title' 
     } 
     if (!isValid) { 
      //Add new error message if invalid 
      addRemoveValidationMessage(this.elem, false, errMsg, this.validatorKey); 
      //Save error message if invalid. 
      this.errMsg = errMsg; 
     } else { 
      //Clear error message if valid 
      this.errMsg = ""; 
     } 
     //Save last validation status 
     this.isValid = isValid; 
    } 
} 

function addRequriedValidation(elem, elemModel, elemScope, isRequiredExp) { 
    var result; 
    if (!elemModel.$validators.required && 
     isRequiredExp.toLowerCase() !== "false" && isRequiredExp !== '*skip*') { 
     elemModel.$validators.required = function (modelValue, viewValue) { 
      var errMsg = "Fill in the required value."; 
      var isValid; 
      var theElem = elem; 
      var theExpr = isRequiredExp; 
      var theVal = modelValue || viewValue; 
      var isRequiredExpVal = elemScope.$eval(theExpr); 
      isValid = !isRequiredExpVal || !elemModel.$isEmpty(theVal); 
      addRemoveValidationMessage(elem, isValid, errMsg) 
      return isValid; 
     } 
    } 
} 

function addReadonlyRule(elem, elemModel, elemScope, readonlyExp) { 
    var result=false; 
    var conSkip = "*skip*" 
    if (readonlyExp.toLowerCase() === "true" && readonlyExp !== conSkip && readonlyExp) { 
     elem.attr('readonly', true); 
     result = true; 
    } else 
    //Add readonly validation 
    if (readonlyExp && readonlyExp.toLowerCase() !== "false" && readonlyExp != conSkip) { 
     elemScope.$watch(readonlyExp, function(newVal){ 
      var theElem = elem; 
      theElem.attr('readonly', newVal); 
     }) 
     result = true; 
     //angular.element(child).attr('ng-readonly', readonlyExp); 
    } 
    return result; 
} 

function addMaxlengthValidation(elem, elemModel, elemScope, maxLenExp) { 
    var result; 
    if (!elemModel.$validators.maxlength && maxLenExp) { 
     elemModel.$validators.maxlength = function (modelValue, viewValue) { 
      var errMsg = "Number of characters should not exceeded '{0}' characters."; 
      var isValid; 
      var theElem = elem; 
      var theExpr = maxLenExp; 
      var maxLenExpVal = elemScope.$eval(theExpr); 
      isValid = (maxLenExpVal < 0) || elemModel.$isEmpty(viewValue) || (viewValue.length <= maxLenExpVal); 
      addRemoveValidationMessage(elem, isValid, errMsg.format(maxLenExpVal)) 
      return isValid; 
     } 
    } 
} 

function addMinlengthValidation(elem, elemModel, elemScope, minLenExp) { 
    var result; 
    if (!elemModel.$validators.minlength && minLenExp) { 
     elemModel.$validators.minlength = function (modelValue, viewValue) { 
      var errMsg = "Number of characters should not be less than '{0}' characters."; 
      var isValid; 
      var theElem = elem; 
      var theExpr = minLenExp; 
      var minLenExpVal = elemScope.$eval(theExpr); 
      isValid = elemModel.$isEmpty(viewValue) || viewValue.length >= minLenExpVal; 
      addRemoveValidationMessage(elem, isValid, errMsg.format(minLenExpVal)) 
      return isValid; 
     } 
    } 
} 

function addPatternValidation(elem, elemModel, elemScope, patternExp, validatorKey, errMsgMask) { 
    var result; 
    validatorKey = validatorKey || 'pattern'; 
    if (!elemModel.$validators[validatorKey] && patternExp) { 
     //Use closures and self invoking function to maintain static value for the related validation function. 
     elemModel.$validators[validatorKey] = function() { 
      errMsgMask = errMsgMask || "The entered value '{0}' doesn't match the validation pattern '{1}'."; 
      var oAddRemoveValidationMessage; 
      var errMsg; 
      return function(modelValue, viewValue) { 
       //This is the actual validation function 
       var isValid; 
       var theElem = elem; 
       var theElemModel = elemModel; 
       var theExpr = patternExp.replaceAll("\\", "\\\\"); 
       var patternExpVal = elemScope.$eval(theExpr); 
       if (angular.isString(patternExpVal) && patternExpVal.length > 0) { 
        patternExpVal = eval(patternExpVal) //new RegExp('^' + patternExpVal + '$'); 
       } 
       if (patternExpVal && !patternExpVal.test) { 
        errMsg = 'Expected {0} to be a RegExp but was {1}. Element ID: {2}'; 
        throw Error(errMsg.format(theExpr, patternExpVal, theElem[0].id)); 
       } 
       patternExpVal = patternExpVal || undefined; 
       isValid = theElemModel.$isEmpty(viewValue) || angular.isUndefined(patternExpVal) || patternExpVal.test(viewValue); 
       //Create object to deal with adding and removing error messages from the element 'title' attribute. 
       //This object is saved within the 'elemModel' and will be used to save the last error message generated. 
       //This will allow the same object to remove the error message when the status becomes valid. 
       oAddRemoveValidationMessage = 
        oAddRemoveValidationMessage || (new AddRemoveValidationMessage(elem, validatorKey)); 
       if (!isValid) { 
        errMsg = errMsgMask.format(viewValue, patternExpVal) 
       } 
       oAddRemoveValidationMessage.addRemoveMessage(isValid, errMsg); 
       return isValid; 
      } 
     }(); //Self invoking function 
    } 
} 

function addCAPostalCodeValidation(elem, elemModel, elemScope, isCAPostalCode) { 
    var result; 
    var errMsg = "The entered value '{0}' must be a valid Canadian Postal Code."; 
    var patternExp = "'/^([A-Z]\\d[A-Z] *\\d[A-Z]\\d)$/i'"; 
    var validatorKey = 'caPostalCode'; 
    isCAPostalCode = isCAPostalCode.toLowerCase(); 
    if (isCAPostalCode === "true") { 
     addPatternValidation(elem, elemModel, elemScope, patternExp, validatorKey, errMsg) 
    } else 
    if (isCAPostalCode && isCAPostalCode !== "false") { 
     elemScope.$watch(isCAPostalCode, function(newVal){ 
      if (elemModel.$validators[validatorKey]) { 
       delete elemModel.$validators[validatorKey]; 
      } 
      if (newVal) { 
       addPatternValidation(elem, elemModel, elemScope, patternExp, validatorKey) 
      } 
     }); 
    } 
} 

//Implement Load Validation Rules. 
//  - doReadonly - if true, it will only load readonly rules 
//      if false, it will only load validation rules 
theService.loadValidationRules = function (doReadonly) { 
    var validationList; 
    var validationListKeys; 
    var validationKey; 
    var elem; 
    var elemModel; 
    var elemScope; 
    var validationRule; 
    var conLoaded = '*loaded*'; 
    doReadonly = doReadonly || false; 
    validationList = formView.getRequiredField(); 
    if (!validationList) { 
     console.error("Unexpected error in 'loadValidationRules()': 'validationList' is not initialized.") 
     return; 
    } 
    validationListKeys = Object.keys(validationList); 
    for (var idx=0; idx < validationListKeys.length; idx++) { 
     validationKey = validationListKeys[idx]; 
     if (validationKey.startsWith('$')) { 
      continue; 
     } 
     elem = angular.element('#'+validationKey); 
     if (!elem.length) { 
      continue; 
     } 
     elemModel = elem.controller('ngModel'); 
     if (!elemModel && !doReadonly) { 
      //We don't need 'ngMode' for readonly 
      console.warn("'ngModel' was not defined for element ID '%s'.", validationKey); 
      continue; 
     } 
     elemScope = elem.scope() || scope; 
     validationObjects = validationList[validationKey]; 
     if (validationObjects === "") { 
      //This means field is required. 
      if (elemModel.$isEmpty(elemModel.$viewValue)){ 
       elem.addClass('ng-invalid'); 
       result = false; 
      } 
     } else 
     if (angular.isArray(validationObjects)) { 
      //Loop through validation rules, and flag invalid field by adding the relevant class 
      for (var ruleIdx=0; ruleIdx < validationObjects.length; ruleIdx++){ 
       validationRule = validationObjects[ruleIdx]; 
       var test = validationRule.test || "true"; //if not exist, it means the rule should always be applied 
       if (test) { 
        var testEval = elemScope.$eval(test); 
        if (testEval) { 
         var readonlyExp = ((validationRule.readonly || "").toString().trim()) || "false"; 
         if (!doReadonly) { 
          var isRequiredExp = validationRule.required || "false"; 
          var isRequiredExpVal; 
          var minLenExp = (validationRule.minlen || "").toString().trim(); 
          var maxLenExp = (validationRule.maxlen || "").toString().trim(); 
          var pattern = (validationRule.pattern || "").toString().trim(); 
          var isCAPostalCode = (validationRule.isCAPostalCode || "false").toString().trim(); 
          isRequiredExp = (angular.isString(isRequiredExp)?isRequiredExp:isRequiredExp.toString()).trim(); 
          //Required Validation: add attributes only if needed 
          addRequriedValidation(elem, elemModel, elemScope, isRequiredExp); 
          addMaxlengthValidation(elem, elemModel, elemScope, maxLenExp); 
          addMinlengthValidation(elem, elemModel, elemScope, minLenExp); 
          addCAPostalCodeValidation(elem, elemModel, elemScope, isCAPostalCode); 
         } else 
         if (readonlyExp && readonlyExp !== conLoaded){ 
          var readonlyLoaded; 
          readonlyLoaded = addReadonlyRule(elem, elemModel, elemScope, readonlyExp); 
          if (readonlyLoaded) { 
           validationRule.readonly = conLoaded; 
          } 
         } 
        } 
       } 
       //For now, just evaluate the first rule; 
       break; 
      } 
     } else { 
      console.error("Unexpected error in 'loadValidationRules()': type of 'validationObjects' is unknown with value = %o", validationObjects); 
     } 
    } 
} 

和:

function ngProcessReviewCore() { 
    //Add css class for invalid radion buttons 
    var appUrl = getAppURL(); 
    var isFormValid = false; 
    if (BusinessLogic.isValidationDynamic()) { 
     isFormValid = $scope.mainForm.$valid; 
    } else { 
     isFormValid = $scope.mainForm.$valid; 
    } 
    if(isFormValid) { 
     //Submit to server here 
    } else { 
     $timeout(function() { 
      $scope.addValidationClassRadio(); 
     }); 
     popUpMsg("infoPopUp", "Please fill the required field and clear validation errors!"); 
    } 
} 

//Integrated with Angular. 
//This is needed to ensure validation is integrated with Angular. 
//Implement manual validation 
$scope.ngProcessReview = function() { 
    $scope.startExecValidations(); 
    if (BusinessLogic.isValidationManual()) { 
     //Load validation rules before start of validation 
     BusinessLogic.loadValidationRules(); 
    } 
    //Use timeout to give time for validations to be reflected 
    $timeout(function(){ 
     ngProcessReviewCore(); 
    }, 100) 
} 

並且,爲了加載readonly規則,您需要在完成折扣後運行此代碼E-環的所有元素:

$scope.runWhenDone = function() { 
    console.log('Load only readonly rules...'); 
    var loadOnlyReadonlyRules = true; 
    BusinessLogic.loadValidationRules(loadOnlyReadonlyRules) 
} 

你也可以使用該指令when-rendering-doneas defined in this solutions

<body ng-app="myApp" ng-controller="formMainController as MainController" when-rendering-done="runWhenDone()"> 
... 
... 
</body> 
相關問題