📜 ⬆️ ⬇️

Using ES6 in AngularJs 1.x with Browserify + Babel build

In this article we will look at how to write the components of AngularJs on an ES6 application, then build using Browserify and Babel based on a small application that you can download from github and play around.

Writing Controller

The controller in AngularJs is a constructor function that can extend the generated scope either by injecting the $ scope parameter into the controller's constructor, or by using the controller as approach. First, we consider a more common approach through injecting $ scope using the example of a registration controller:
class SignupController { constructor($scope, $state, accountService) { this.init($scope, $state, accountService); } init($scope, $state, accountService) { $scope.signup = function () { accountService.signup().then(()=> { $state.go('main.list'); }); }; } } SignupController.$inject = ['$scope', '$state', 'accountService']; export {SignupController} 

As you can see, the controller is represented by the ES6 class, which injects dependencies of $ scope and two services into the constructor.

Here I want to immediately note that we have lost the ability to enumerate dependencies using inline array annotation, that is, as follows:
 someModule.controller('MyController', ['$scope', 'greeter', function($scope, greeter) { // ... }]); 

Thus, the possibility of specifying dependencies and their order of injection remains only through the $ inject property defined in the created SignupController class.
')
The second way to define a controller using the “controller as” approach looks more “magical” in combination with the ES6 class. And when writing a controller, I think it is most preferable.

 var _state = new WeakMap(); var _accountService = new WeakMap(); class SigninController { constructor($state, accountService) { _state.set(this, $state); _accountService.set(this, accountService); } login() { _accountService.get(this).login().then(()=> { _state.get(this).go('main.list'); }); }; } SigninController.$inject = ['$state', 'accountService']; export {SigninController} 

Apparently the class has lost the explicit mention of scope, has become a bit more independent of AngularJs, and even dependencies are injected through the constructor. But now private variables appear in the class, and with them the problem of using them within the class. The article " Implementing private fields using WeakMap in JavaScript " is very accessible about this and the best solution to guarantee the release of resources and the belonging of variables only to this class will be the use of WeakMap- from the minuses - we write a little more code, we sleep well.

Now we have to do the last step - declare the Controller in the Angular module.

To do this, I created a separate module.js file, in which ES6 modules are imported and registered in Angular modules.

 import router from './router.js'; import {SigninController} from './controllers/signin/signin.controller.js'; angular.module('account').controller('SigninController', SigninController); 

Writing Provider, Factory, Service

The next step will be the introduction of some business logic class — in my case this will be the AccountService class.

It looks as magical as the previous class - without any mention of AngularJs

 import api from './accountApi.factory.js'; class AccountService { login(){ return api.login(); } signup(){ return api.signup(); } } export {AccountService} 

Note that the AccountService class depends on the module declared in the accountApi.factory.js file, but the dependency is imported, rather than being injected using the DI mechanism provided by AngularJs. In principle, the AccountService controller described above could have been imported, not injected. It all depends on how you want to build your application.

So the service class is described, it now remains to declare the service in the Angular module.

The Angular service is the easiest to announce. With Factory and Provider, everything is a few lines more complicated.

We declare the service in our module.js file:

 ........ import {AccountService} from './services/accountService.factory.js'; ......... angular.module('account').service('accountService', AccountService); 

Everything is simple here - an instance of the AccountService class will be created using the new operator, since the service method expects a constructor function.

What code would look like if we needed to declare a provider:

 angular.module('account').provider('accountService', providerBuilder(AccountService)); function providerBuilder(obj) { return function () { this.$get = [function () { return new obj(); }]; } } 

And finally, if we needed a factory:

 angular.module('account').factory('accountService', function(){return new AccountService()}); 

And it is better to declare a static function in the class AccountService, which will create an instance of the class and then the code will look like this:

 angular.module('account').factory('accountService', AccountService.createInstance); 

I will give an example with similar behavior below.

Write directive

The directive will look like this:
 var _accountService = new WeakMap(); class Copyright { constructor($templateCache, accountService) { _accountService.set(this, accountService); this.restrict = 'E'; this.template = $templateCache.get('account/directives/copyright/copyright.directive.html'); this.scope = {}; this.controller = ['$scope', function ($scope) { $scope.copyright = function () { return 'Page - 2015'; }; }]; } link(scope) { scope.doSomething = function () { //-  var accountService= _accountService.get(Copyright.instance); //-  } } static createInstance($templateCache, accountService) { Copyright.instance = new Copyright($templateCache, accountService); return Copyright.instance; } } Copyright.createInstance.$inject = ['$templateCache', 'accountService']; export {Copyright} 

My directive does nothing, but it has all the main parts.

In class, I define all the standard directive fields that are needed and I want to focus your attention on how the directive is declared.
The directive is declared in the Angular module almost as well as the factory, but there is one small difference: this constructor function will not be equal to this in the link function and therefore I keep a reference to this in the instance field of the class.

Similarly, you can declare filter, constant, and value.

Build project

So, we wrote some code that is broken into ES6 modules and now we need to put it together. Browserify is responsible for finding dependencies of modules and assembling them into one file. To do this, first determine the entry point from which the assembly will begin.

I propose to define 2 points of entry - entry point of the module - that is, this is a file that imports modules / files of its module only and a common entry point that combines the entry points of the modules.

But I have only 1 module and therefore there will be only 2 such files too:
  1. The module.js file located in the root of the account folder and having relative links to all used module files
  2. The app.js file located in the root of the application and having links to all the module.js files of the project

The second task is to convert the code written on ES6 to ES5. This task will be performed by Babel, connected to Browserify as a plugin using the transform option.

The code for the builder, as well as the project code, can be found in the github repository .

Literature:
  1. Guide to AngularJS Documentation
  2. Implementing Private Fields with WeakMap in JavaScript
  3. Fast browserify builds with watchify

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


All Articles