📜 ⬆️ ⬇️

AngularJS: how I abandoned ng-include and linked the states of two controllers

In the last article I talked about my first encounter with AngularJS. A year has passed since then, now I have new projects and other, often less trivial tasks. Here I will describe a few nuances that I had to face in the process of working on one of the systems. I hope readers can benefit from my practices.

Search and anchor


Suppose that we received the task to develop a client part for our new project. This is a directory in which hundreds of thousands of documents will be stored. Since it is quite large, the API provides the ability to load items page by page (with the indication of the initial index) and also filter by individual fields in the document.
And in order for users not to get lost in the system and share information with each other, the client must save their status in the address bar.

Well, the task is clear. Getting started.
')

To store the filter values, create a $ query service. He will have two methods:


There should be a retreat. Since the page uses several templates (for example, for pagination), a hash (#) is automatically added to the address bar. This is due to the fact that ng-include uses the $ location service, in the presence of which angular begins to assume that we are making a one-page application.

Accordingly, an object of the form

{ index: 0, size: 20 } 

will turn into
localhost:1337/catalog#?index=0&size=20

But wait. Users want to not only get the status of the page, but also mark a separate document on it.
Official documentation in this case advises using $ anchorScroll or scrollTo .

Those. now we get the following:
localhost:1337/catalog#?index=0&size=20&scrollTo=5

At this point, my aesthetic sense appealed to the search for another solution.

The first thought was to abandon ng-include , so that the address bar is no longer subjected to violence by the angulyar. But then what to do with the templates? There was only one way out: write your own directive for working with templates.

With blackjack and templates


There were no problems with the directive. To work with templates, angular uses the $ templateCache service. You can put a piece of html-code into it using text / ng-template or put () method. Also, by analogy with ng-include , we will consider the execution of the atload code onload .

Directive code:

 app.directive('template', ['$templateCache', '$compile', function ($templateCache, $compile) { return { scope: false, link: function (scope, element, attrs) { var tpl = $compile($templateCache.get(attrs.orgnTemplate))(scope); tpl.scope().$eval(attrs.onload || ''); element.after(tpl); element.remove(); } } }]); 


Now we can use the templates as follows:

 <div data-template="paging" data-onload="foo = 'bar'"> 

Having solved the problem with $ location , I rewrote the $ query service a bit so that it now works exclusively with the history API.
By the way, do not try to use them together. This will lead to an endless loop.

So now the address bar is clearer and more pleasant looking:
localhost:1337/catalog?index=0&size=20#5

And moving along anchors no longer requires additional code.

Ease of communication


Having broken the page into separate templates and controllers, I unexpectedly ran into another problem: the controllers must interact with each other. Even if they are not in parental relationships. And in some cases (again, pagination), the controllers must synchronize their state.

The first option was to interact between controllers through events. In this case, for each action, the controllers send each other events. Unfortunately, in my case the number and variety of events per square centimeter of code began to go over all reasonable limits. I decided to abandon optimization and make a separate mechanism for the exchange of information, regardless of the current scope .

So the $ store service appeared. In the first version, he had one method:


The following code has been added to the controllers:

 $scope.$watch(function () { return $store.value('foo'); }, function (data) { doSomething(data); }, true); 

Now that I needed to synchronize the state of two or more controllers, I just overwritten the value in the key:

 $store.value('stream', data); 

Do not forget that all services are singletones, so when adding a service to several controllers at the same time, we get access to the same object.

Later, when I automated some data transfer between two templates (for example, the list of elements was now automatically attached to pagination using my $ id ), the alias () method was added to the service:


Thus, I have the opportunity to specify alias in the onload attribute of the template directive. Roughly speaking, if the controller suddenly needs to request a status, this can be done not by the original key, which may be unavailable, but by a predetermined value.

Instead of an afterword


It turned out that the seemingly trivial task turned into a full-fledged refactoring. However, after its completion, at least, in my opinion, the code has become much easier to read and more predictable in work. I no longer get lost in endless events, eat only healthy food and do sports. I hope this article will help others find peace of mind and learn something new. Good luck and have a nice day!

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


All Articles