Separating an AngularJS Application Into Isolated Modules

    When developing a sufficiently large application, a moment inevitably arises when the application finally becomes large enough to slow down. There are many methods for AngularJS to achieve the desired performance: bindonce , filtering lists, using $ digest instead of $ apply, ng-if instead of ng-show (or vice versa), and others . But all of them allow you to make only local improvements without helping globally: getting rid of $ rootScope calls completely. $ Digest fails, and checking the status of the entire application can take a very long time.

    In this article I want to offer an architectural solution: splitting an application into several parts that are not related in terms of the framework and independent implementation of the relationships between them.

    Angular has the concept of bootstrap . This is the method that is usually called after the page loads if an element with the ng-app attribute exists. It binds this element to the module specified in the attribute value. There should be only one such element, otherwise the documentation does not guarantee anything. However, you can use it manually:angular.bootstrap(element, [/*Module*/]);This will launch the specified module, all its dependencies, as well as the ng module and its dependencies. Therefore, the new application (let's call it an isolated module) will have its own $ injector, $ rootScope, $ compile, etc. - The entire internal Angular kitchen will be re-created. The parent isolated module will not know about the existence of nested modules, events (emit and broadcast) will not pass between the modules, and $ digest, called in one isolated module, will not leak into another. For loosely coupled components, this is what you need.

    For convenient creation of new applications, the following directive can be used:
    directive('newApp', function () {
        return{
            restrict: 'EA',
            transclude: true,
            scope: {
                module: '='
            },
            link: function (scope, element, attr, ctrl, transclude) {
                var div = document.createElement('app');
                var module = angular.module(scope.$id, [scope.module]).run(['$rootScope', function ($rootScope) {
                    scope.$on('$destroy', function() {
                        $rootScope.$destroy();
                        angular.module(scope.$id, []);
                    });
                    transclude($rootScope, function (el) {
                        angular.element(div).append(el);
                        element.append(div);
                    });
                }]);
                angular.bootstrap(div, [scope.$id]);
            }
        };
    });
    

    Using:


    To compare the performance before and after, you can see the synthetic example: the slow version and the fast version .

    We should also mention memory leaks. The missing handler of the $ destroy event in the directive is responsible for their absence. He sends this event inside the isolated module so that everyone knows about it and rewrites the module to remove registered directives, controllers, etc. However, the memory still leaks, for example, due to the cache of elements in angular.element.cache and much more . This issue deserves a separate study and article.

    Another issue discovered is the $ location service. Among other things, he monitors the address of the page, and when changing it, makes some kind of gestures, for example, updates the contents of ngView. In the case of several isolated modules, several instances of $ location will be created, several handlers for changing the url, which is not good. So far I've come up with the following workaround:
    .config(function ($locationProvider) {
        $locationProvider.$get = function () {
            return angular.element(document).injector().get('$location');
        };
    })
    

    Now I'm testing and designing the code in the form of a library, I'm interested in the opinion of the community.

    Also popular now: