📜 ⬆️ ⬇️

Bold AngularJS for team development [2/2]

The first part of the translation is here .

After reading Google's AngularJS Guidelines , I got the impression of its incompleteness, and it also often hinted at the profit from using the Closure library. They also stated , “We do not think that these recommendations apply equally well to all projects using AngularJS. We will be happy to see a community initiative for a more generic style guide, applicable for both small and large projects. ”

Based on personal experience with Angular, several speeches , as well as experience in team development, I present to you this bold style guide on syntax, code writing and application structure on AngularJS.
')

Directives


All manipulations with the DOM should be made exclusively from directives. All code that is suitable for reuse must be encapsulated (this also applies to actions and markup).

DOM manipulations


DOM manipulations should be in the directive's link method.

Poorly:

 //    function MainCtrl (SomeService) { this.makeActive = function (elem) { elem.addClass('test'); }; } angular .module('app') .controller('MainCtrl', MainCtrl); 

Good:

 //   function SomeDirective (SomeService) { return { restrict: 'EA', template: [ '<a href="" class="myawesomebutton" ng-transclude>', '<i class="icon-ok-sign"></i>', '</a>' ].join(''), link: function ($scope, $element, $attrs) { // DOM manipulation/events here! $element.on('click', function () { $(this).addClass('test'); }); } }; } angular .module('app') .directive('SomeDirective', SomeDirective); 

Naming convention


User directives should not be prefixed with ng-* in the name, in order to avoid possible reassignment of code by future Angular releases. Surely, at the time of the emergence of ng-focus , many directives were written, with the same name, whose application work was paralyzed only because of the use of the same name. Also, the use of this prefix is ​​confusing, and apparently from the presentation code it is not clear which of the directives are written by the user, and which ones came with the library.

Poorly:

 function ngFocus (SomeService) { return {}; } angular .module('app') .directive('ngFocus', ngFocus); 

Good:

 function focusFire (SomeService) { return {}; } angular .module('app') .directive('focusFire', focusFire); 

For naming directives, camelCase is used. The first letter in the name of the directive is lowercase. It is also worth noting that in the presentation code (view) we are already operating with the name of the directive, written with a hyphen. So, to use the focusFire directive in a view, we focusFire it via <input focus-fire> .

Restrictions on use


If support of IE8 is important to you, then for directives it is necessary to use syntax with comments. In truth, there is no other reason to use this form of calling directives. If possible, even this syntax is better not to use, because later there may be confusion, where is the real comment, and where is the call of the directive.

Poorly:

This is very confusing.

 <!-- directive: my-directive --> <div class="my-directive"></div> 

Good:

Declarative user directives look more expressive.

 <my-directive></my-directive> <div my-directive></div> 

Resolution promise in the router, and defer in the controller


After creating the services, we will probably want to use them in the controllers for working with data. Therefore, over time, maintaining a neat-looking controller can be a problem, along with getting the right data.

Thanks to angular-route.js (or similar third-party add-ons, such as ui-router.js ), we can use the resolve property to resolve the promises of the next view until the finished page is displayed to us. This means that the controller for this view will be created immediately after receiving all the data, and this in turn means that up to this point there will be no function calls.

Poorly:

 function MainCtrl (SomeService) { var self = this; //    self.something; //    SomeService.doSomething().then(function (response) { self.something = response; }); } angular .module('app') .controller('MainCtrl', MainCtrl); 

Good:

 function config ($routeProvider) { $routeProvider .when('/', { templateUrl: 'views/main.html', resolve: { doSomething: function (SomeService) { return SomeService.doSomething(); } } }); } angular .module('app') .config(config); 

At this stage, within our service, a promise will bind to a separate object, which in turn can be transferred to the “pending” controller:

Good:

 function MainCtrl (SomeService) { //  ! this.something = SomeService.something; } angular .module('app') .controller('MainCtrl', MainCtrl); 

But there is something else that can be improved. You can transfer the resolve property directly to the controller code, thus avoiding any logic in the router code.

Fine:

 // ,  resolve       function config ($routeProvider) { $routeProvider .when('/', { templateUrl: 'views/main.html', controller: 'MainCtrl', controllerAs: 'main', resolve: MainCtrl.resolve }); } // ,  function MainCtrl (SomeService) { //  ! this.something = SomeService.something; } //   resolve    MainCtrl.resolve = { doSomething: function (SomeService) { return SomeService.doSomething(); } }; angular .module('app') .controller('MainCtrl', MainCtrl) .config(config); 

Route change and spinner


In the process of resolving a new route, we will probably want to show something to indicate progress. As usual, Angular creates the $routeChangeStart event when we leave the current page. At this very moment you can show the spinner. And you can remove it at the moment of occurrence of the $routeChangeSuccess ( more details here ).

Avoid $ scope. $ Watch


Use $scope.$watch only when there is no way to do without it. It is worth remembering that in terms of performance, it is significantly inferior to ng-change solutions.

Poorly:

 <input ng-model="myModel"> <script> $scope.$watch('myModel', callback); </script> 

Good:

 <input ng-model="myModel" ng-change="callback"> <!-- $scope.callback = function () { // go }; --> 

Project structure


Each controller, service or directive should be placed in a separate file. You should not push all controllers into one file, at least for the reason that later it will be extremely difficult to find anything there.
Poorly:

 |-- app.js |-- controllers.js |-- filters.js |-- services.js |-- directives.js 

Good:

Stick to the capacious and speaking file names in order to have a maximum idea of ​​its contents based only on the name.

 |-- app.js |-- controllers/ | |-- MainCtrl.js | |-- AnotherCtrl.js |-- filters/ | |-- MainFilter.js | |-- AnotherFilter.js |-- services/ | |-- MainService.js | |-- AnotherService.js |-- directives/ | |-- MainDirective.js | |-- AnotherDirective.js 

Depending on the size of the code base of your project, it may be more appropriate for you to use a functional approach to file naming.

Good:

 |-- app.js |-- dashboard/ | |-- DashboardService.js | |-- DashboardCtrl.js |-- login/ | |-- LoginService.js | |-- LoginCtrl.js |-- inbox/ | |-- InboxService.js | |-- InboxCtrl.js 

Naming Conventions and Conflicts


In Angular, there are many objects whose name begins with the $ sign, for example, $scope or $rootScope . This symbol as if hints to us that an object is public and can be interacted with from different places. We are also familiar with things like $$listeners , which are also available in the code, but are considered private.

All of the above only means that you should avoid using $ and $$ as prefixes in the names of your own directives / services / controllers / providers / factories.

Poorly:

Here we set $$SomeService as the definition, while leaving the name of the function without prefixes.

 function SomeService () { } angular .module('app') .factory('$$SomeService', SomeService); 

Good:

Here we set SomeService as the definition and name of the function itself for a more expressive stack trace.

 function SomeService () { } angular .module('app') .factory('SomeService', SomeService); 

Minification and annotation


Annotation order


It is considered good practice to first indicate Angular providers in the list of module dependencies, and after that - their own.

Poorly:

 //    function SomeCtrl (MyService, $scope, AnotherService, $rootScope) { } 

Good:

 //   Angular ->  function SomeCtrl ($scope, $rootScope, MyService, AnotherService) { } 

Automate minification


Use ng-annotate for automatic annotation of dependencies, because ng-min outdated and no longer supported . As for ng-annotate , so more about it here .

In our case, when we describe the module code in a separate function, for correct minification, it will be necessary to use the @ngInject comment before those functions with dependencies. This comment is a ng-annotate instruction for automatically describing the dependencies of a module.

Poorly:

 function SomeService ($scope) { } //    –     SomeService.$inject = ['$scope']; angular .module('app') .factory('SomeService', SomeService); 

Good:

 /** * @ngInject */ function SomeService ($scope) { } angular .module('app') .factory('SomeService', SomeService); 

As a result, it will turn into the following:

 /** * @ngInject */ function SomeService ($scope) { } //   ng-annotate   SomeService.$inject = ['$scope']; angular .module('app') .factory('SomeService', SomeService); 

This style guide is in the process of being finalized. Always up to date recommendations can be found on Github .

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


All Articles