Slow loading of AngularJS app in IE - add progress bar

前端 未结 5 1199
谎友^
谎友^ 2021-01-27 08:08

UPDATE1: Started using ngProgress, but not giving required effect in IE.
Final Update: Best solution found. See last answer below.


The AngularJS application ha

5条回答
  •  猫巷女王i
    2021-01-27 08:47

    This is the final version, and the best in terms of performance.

    It is based on the following:

    • Load validation rules after elements have been rendered.
    • Execute validation rules just when required
    • Load validation rules using ngModelController.$validators property.
    • Do not use HTML code for validation.
    • Avoid using directive and $compile completely

    Following is the code that will be used to load the validation rules:

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

    And the code below can be used to load and trigger validation:

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

    and, to load the readonly rules, you need to run this code when done rendering all elements:

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

    And you can use the directive when-rendering-done as defined in this solutions:

    
    ...
    ...
    
    

提交回复
热议问题