📜 ⬆️ ⬇️

Splitting an AngularJS application into isolated modules

When developing a large enough application, there inevitably comes a moment when the application finally becomes large enough to slow down. For AngularJS, there are many techniques 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 they all allow you to do only local improvements without helping out globally: you can’t get rid of the $ rootScope calls completely. $ Digest doesn’t work, and it can take a very long time to check the status of the entire application.

In this article I want to propose an architectural solution: splitting the application into several parts unrelated from the framework point of view and self-realizing the connections between them.

In Angular, there is the concept of bootstrap . This is the method that is usually called after the page loads if there is an element with the ng-app attribute. It associates this element with the module specified in the attribute value. This element must be one, 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 $ injector, $ rootScope, $ compile, etc. - the entire internal kitchen of Angular will be recreated. The parent isolation module will not know about the existence of nested into it, no events (emit and broadcast) will pass between the modules, and $ digest called in one isolation module will not leak into the other. For loosely coupled components, this is what is needed.
')
For easy 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:
 <body ng-app="App"> <new-app module=" 'SomeModule' "> <some-module-directive/> </new-app> </body> 


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

We should also mention memory leaks. For their absence, the $ destroy event handler is responsible for the directive. It sends this event inside an isolated module so that everyone knows about it and overwrites the module to remove registered directives, controllers, etc. However, the memory still leaks, for example, because of the cache of elements in angular.element.cache and many other things . This question deserves a separate study and article.

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

Now I spend testing and design code in the form of a library, interested in the opinion of the community.

Source: https://habr.com/ru/post/213705/


All Articles