Angular ng-repeat cache (avoid re-rendering on state change)

我们两清 提交于 2020-02-06 04:25:29

问题


We have huge rendering spikes with ng-repeat in Angular application. Main page shows a huge list of cover images ("::" and "track by" are in place). On first load it works acceptable.

But if user changes the state (we use UI-Router) and goes back to the home page afterwards then it is about 2s delay on desktop and up to 10 sec delay on mobile.

It should be instant instead: all json queries are cached. And ng-repeat already rendered that content once.

As temporary solution we use angular ux datagrid (https://github.com/obogo/ux-angularjs-datagrid) It makes returns to front page instant, but it is not supposed to work in horizontal mode. And it seems overkill to use a dedicated grid just to cache the ng-repeat (or what ever it does behind the scene).

so the question follows: how is it possible to avoid ng-repeat re-render content on state change?


回答1:


Well if you disable the scope of the scope that the ng-repeat is on. Then it will no longer render. It essentially becomes static content. This allows you to actually control when it is rendered.

ux-datagrid actually uses this concept to turn off dom that is out of view so angular doesn't know about it and cannot render it. Then it hooks it back up when it is in view.

Each scope works on a digest cycle. In the digest cycle it processes the $watchers that are on the scope.

If you remove those watchers, it does not digest it or it's children.

These are the 2 methods that the ux-datagrid uses in it's code to activate and deactivate scopes. You could copy these to another object and use them for the same thing.

/**
 * ###<a name="deactivateScope">deactivateScope</a>###
 * One of the core features to the datagrid's performance is the ability to make only the scopes
 * that are in view to render. This deactivates a scope by removing its $$watchers that angular
 * uses to know that it needs to digest. Thus inactivating the row. We also remove all watchers from
 * child scopes recursively storing them on each child in a separate variable to activate later.
 * They need to be reactivated before being destroyed for proper cleanup.
 * $$childHead and $$nextSibling variables are also updated for angular so that it will not even iterate
 * over a scope that is deactivated. It becomes completely hidden from the digest.
 * @param {Scope} s
 * @param {number} index
 * @returns {boolean}
 */
function deactivateScope(s, index) {
    // if the scope is not created yet. just skip.
    if (s && !isActive(s)) { // do not deactivate one that is already deactivated.
        s.$emit(exports.datagrid.events.ON_BEFORE_ROW_DEACTIVATE);
        s.$$$watchers = s.$$watchers;
        s.$$watchers = [];
        s.$$$listenerCount = s.$$listenerCount;
        s.$$listenerCount = angular.copy(s.$$$listenerCount);
        subtractEvents(s, s.$$$listenerCount);
        if (index >= 0) {
            s.$$nextSibling = null;
            s.$$prevSibling = null;
        }
        return true;
    }
    return false;
}

/**
 * ###<a name="activateScope">activateScope</a>###
 * Taking a scope that is deactivated the watchers that it did have are now stored on $$$watchers and
 * can be put back to $$watchers so angular will pick up this scope on a digest. This is done recursively
 * though child scopes as well to activate them. It also updates the linking $$childHead and $$nextSiblings
 * to fully make sure the scope is as if it was before it was deactivated.
 * @param {Scope} s
 * @param {number} index
 * @returns {boolean}
 */
function activateScope(s, index) {
    if (s && s.$$$watchers) { // do not activate one that is already active.
        s.$$watchers = s.$$$watchers;
        delete s.$$$watchers;
        addEvents(s, s.$$$listenerCount);
        delete s.$$$listenerCount;
        if (index >= 0) {
            s.$$nextSibling = scopes[index + 1];
            s.$$prevSibling = scopes[index - 1];
            s.$parent = scope;
        }
        s.$emit(exports.datagrid.events.ON_AFTER_ROW_ACTIVATE);
        return true;
    }
    return !!(s && !s.$$$watchers); // if it is active or not.
}

I am not sure this will fully answer your question because you are using UI-Router. If the view is recreated and not cached then it will still re-render it all in the compile. However, if not this does not just do a watch once, when you disable this scope, it disables all children of that scope as well. Essentially detaching it from the digest and all child nodes with it.

Re-enabling it adds them all back in. So you really turn off the ng-repeat and everything in it with one call to deactivate. It becomes static all of the way down until you re-enable it.



来源:https://stackoverflow.com/questions/30986544/angular-ng-repeat-cache-avoid-re-rendering-on-state-change

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!