首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >IE中AngularJS应用程序的慢加载-添加进度条

IE中AngularJS应用程序的慢加载-添加进度条
EN

Stack Overflow用户
提问于 2017-06-16 16:37:19
回答 5查看 1.4K关注 0票数 0

UPDATE1:开始使用ngProgress,但在IE中没有提供所需的效果。

最后更新:找到最佳解决方案。见下面最后的答案。

AngularJS应用程序有多个选项卡,每个选项卡可能有多达100个字段。使用几个Ajax调用从DB检索数据,并使用一个相关的循环来初始化以下每一项:验证规则、下拉列表项以及最后的字段值。在某些情况下,我们使用Javascript和AngularJS相结合的方式来获得所需的效果。

请注意,加载验证规则涉及修改指令,如ng-requiredng-max,这将需要使用$compile来激活验证规则。

这个问题有两部分:

  • AngularJS应用程序在IE环境下具有明显的慢加载效果。在Chrome浏览器下,加载速度要好得多。

如何排除和分析IE下的缓慢加载问题,以确定问题的位置?如何在IE下开发性能分析工具?

  • 同时,考虑在加载完上面提到的每个数据部分之后添加要更新的进度栏:验证规则、下拉列表项和字段值。

我在我的项目中实现了ngProgress插件,它在Chrome下运行得很好,但在IE下却没有达到所需的效果。进度条将在页面加载的末尾显示和完成。在IE下,进度条似乎不会在页面呈现开始时立即显示。请注意,在我的项目中,我广泛地使用了指令,其中很多使用$compile服务。

我做了一些研究,发现IE不会立即更新DOM显示.它将等待一个较晚的阶段,以更新所有一次,或至少这是我的理解。因此,这里的诀窍是如何迫使IE尽快反映DOM更改。

我使用了这种方法,这有助于在IE下获得更好的结果:

代码语言:javascript
运行
复制
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秒,然后继续正常。当观察控制台窗口时,我可以看到,当其他许多指令正在执行时,它会冻结,特别是与priority: 100terminal: true,一起使用priority: 100服务的指令。

知道怎么做才能更好吗?

注意:这个帖子也有类似的问题,但我没有找到相关的解决方案。

塔里克

EN

回答 5

Stack Overflow用户

发布于 2017-07-01 22:54:51

从您所说的缓慢加载是由于AngularJS的工作,而不是数据加载,事实证明,它在IE比Chrome慢。如果这是真的,那么加载指示器不会有帮助,因为它也会冻结。

您最好采用角度方面的正常性能技术,例如:

  • 在页面上显示更少,用户肯定不需要一次看到100个字段?考虑寻呼。
  • 对于不会更改的项,请使用bind一次如下所示:{{::vm.name}}
  • 在句柄条的主题上,通常使用<div ng-bind="::vm.name"></div>而不是句柄条{{::vm.name}}更有效。
票数 1
EN

Stack Overflow用户

发布于 2017-08-16 22:46:21

下面是我的解决方案,基于以上@andrew的解决方案,并使用ngProgress条形件

CSS:

代码语言:javascript
运行
复制
#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 -一开始:

代码语言:javascript
运行
复制
$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 -最后:

代码语言:javascript
运行
复制
//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
EN

Stack Overflow用户

发布于 2017-10-10 22:29:13

到达1000+字段后,问题变得更严重了。IE 11用了3+分钟完成装载。我做了进一步的优化,现在的结果是完成加载的时间如下:

确认瓶颈在循环中,该循环将加载验证规则并将它们应用于元素,然后使用$compile服务执行编译。

验证规则使用json格式存储在DB中。并使用requiredFieldsPromise检索。请参阅下面的代码示例。

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

代码语言:javascript
运行
复制
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并使用相关的ngModel.NgModelController元素,然后访问$validators来使用代码执行验证。如果您阅读上面的代码,您将看到我已经访问了变量ngModel.NgModelController中的每个元素的elmModel。我认为这个变量将提供对$validators的访问,它可以用于向元素添加验证。由于规则现在在validationList变量中可用,我将编写一个函数来执行验证,方法是查找这个列表并动态应用可用的验证。

这将是未来短跑的改进。

如果你有任何反馈,请告诉我。

塔里克

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/44594112

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档