I know you’re all excited with Angular 2 and ES6, but that doesn’t mean we can’t use ES6 with Angular 1.x.
Today, I am going to present you my new workflow for Angular 1.x with ES6.
A brief introduction to Webpack
Webpack
is a module bundler, what does that mean? Well, it basically take modules with dependencies and generate static assets to represent those modules.
So we could have a module like:
1 2 3 |
|
And if we process it with Webpack
, it will take this my_module.js
, retrieve its dependencies (foo.js
) and generate a static file with both files.
So the idea with Webpack
is: I tell you what the entry point is and you figure out the rest. For Angular
applications, that is normally the file where we create our main module. So starting from that file, it starts pulling dependencies in (basically our entire application) and then it generates a bundle.js
file which contains our application. There is no more need of creating hundred of <script>
tags anymore.
Webpack
is not a replacement for Gulp
, but it can do all the job by itself without needing Gulp
. The philosophy is also different. In Gulp
we do something like: “Grab all the .js files from this directory, start creating a sourcemap, concat the files, if it is for production run ng-annotate, uglify and then finish the sourcemap”. In Webpack
we have loaders so if we need sass
support, we can do something like:
1 2 3 4 |
|
How does this work? First, we match all the .scss
files in our project and then we send it to the loader. If you have more than one loader, you separate them using !
and then they run starting from the last one (pretty much like Rails
and the file extensions if you’re familiar with it).
So it send all the .scss
files to the sass
loader which will transform our sass
into css
. The output of that will be sent to the css
loader which will read them and return their css
code and finally it ends on the style-loader
which Webpack
uses to generate <link>
tags and stuff.
Thanks to that, we can achieve things like:
to import that scss
into our app.
So the idea with Webpack
is just that. An entry point, and from there we import the different dependencies of our application thanks to the loaders.
Before we move on, what about the ES6
? That is another loader, in concrete, the babel-loader
:
1 2 3 4 5 6 7 8 |
|
Now when we import a javascript file, it will be processed by babel
. Notice how we exclude node_modules
to get some performance ;)
Using Angular 1.x with ES6
So having Webpack
to process everything for us, we just need to start coding our app.
I created a workflow with Webpack
for you, so you just have to clone it and stop worrying about tooling:
1 2 3 4 |
|
npm run dev
will launch the webpack-dev-server
which comes with livereload. Now you can go to http://localhost:8080
to see the application working.
From here, how you structure your app and how you name the files is up to you. I am going to share in here my conventions in how to use ES6
with Angular 1.x.
If you open src/app.js
(our entry point) you could code:
1
|
|
If you remember from what I said earlier, Webpack
will grab this file and all its dependencies to generate the final result. Is this line going to work? Try it. It doesn’t. Why? Because Angular
hasn’t been loaded yet and when Webpack
generates the final build.js
it won’t have Angular
in it so it won’t work. How do we tell Webpack
to load Angular
? As simple as:
1 2 3 |
|
That will tell Webpack
to require Angular
so the final build.js
will have Angular
loaded. Now, since Angular
creates a global angular
object, we can simply use it to create our app
module.
That is good, but not good enough. One of the best things of being able to “require” stuff is knowing where our stuff come from. Yes, requiring angular will create that global variable, but it is not that clear…
Isn’t it better to do:
1 2 3 |
|
I think it is. Angular
also exports the angular
object so we can get it like that and use it to create our modules and components. You can also use ES6 syntax to require modules:
1 2 3 |
|
I personally prefer the ES6 syntax, but the previous one is good as well.
Loading Bootstrap
Not related with Angular
, but it is a common choice to include CSS frameworks like bootstrap
. How can we use it?
1
|
|
Now, we import it:
1 2 3 4 5 |
|
And it works!
Why load it in here? Isn’t this file the one who creates the app
module? Yes, but we can think of the bootstrap.css
as the main css
of our entire application, so here we are saying: I want to apply the ‘bootstrap.css’ on our entire application. While that sentence is not completely true (you can load it in a inner file and it will still be applied in the entire app), we are talking here about the semantics, in other words, to make clear that bootstrap.css
will apply to the entire app.
Config functions
Creating a config function in ES6 is not really different to ES5, it is just a function we export to be used in a different file:
1 2 3 4 5 6 |
|
Here we created a function to configure html5Mode
and also to send us to /
on startup. Notice how we used export default
to export our function.
NOTE: There is no need of using $inject
like I did in here, there is a Webpack
loader for ng-annotate
that you can use to let webpack annotate for you. Personally I prefer the $inject
syntax. The loader is not installed on my workflow, but it is a 1 minute change.
Now with our exported function, we just need to import it somewhere and use it, for example:
1 2 3 4 5 6 7 8 |
|
Good, now our configuration is done and it is clear where that “routing” function comes from.
Ah! We are using ui-router
, so we need to install it:
1
|
|
Now we need to import it (like we did with angular) and put it is a dependency to app
. But… how? I mean, angular returns an object, but what does ui-router
return? It won’t return an object, basically because there is none.
Since angular comes with its own module system, what it expects is the name of the module we want to load, something like:
1
|
|
Alright, but we still need to import ui-router
to include it on the bundle.js
, so here we can kill two birds with one stone:
1 2 3 4 5 6 7 8 9 |
|
The convention that all external modules are following is to simply export the name of the module, so uirouter
here is the string 'ui.router'
.
A new module
So let’s code a dummy feature. We need a new module, a controller, a template and route config. Let’s start with the controller:
1 2 3 4 5 6 7 8 9 |
|
So a controller with ES6 is just a class. When using ES6 we will use controllerAs
so we put our stuff in this.
. Here we initialize a name
field and also we have a button to change it. The controller gets exported so we can use it in a different file. Let’s create the template now:
1 2 3 4 5 |
|
Also the routing config:
1 2 3 4 5 6 7 8 9 10 11 |
|
Notice how we loaded our template. Thanks to webpack, we can require html files (just read them, without processing) and that will return the template as a string. Just what template
needs.
Ok, now we just need our module:
1 2 3 4 5 6 7 8 9 10 |
|
First notice the name of the file, index.js
. Thanks to that we can import a folder and this file will be the one that runs.
Also, we return the name
of the module as we said earlier (external modules always return their name).
Since this module is also using ui-router
, we import it. There is no real need to do this, we loaded ui-router
in the app
module, but doing this we get two things: It is easy to see what dependencies our module have, and if we need this feature in another app, we can simply copy & paste it without having to worry about dependencies. Also, Angular won’t care, it will simply ignore it.
Now back to the app
module, we need to add this new module as a dependency:
1 2 3 4 5 6 7 |
|
Finally, we just need to add a ui-view
to our index.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
NOTE: If we modify the index.html
, we need to re-run webpack.
If you try it, it works!
Uhm, I would like the text to be centered in the jumbotron. First, let’s add an id to our jumbotron:
1 2 3 4 5 |
|
And now let’s create a specific css
file for this feature:
1 2 3 |
|
Like we did with bootstrap.css
, we need to load this home.css
. A good place would be the file where we create our home
module:
1 2 3 4 5 6 |
|
Now, it is a bit better.
Services
With ES6, we won’t use factories anymore, instead we are going to use services. The reason behind that is that a class maps perfectly to a service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
So here we have a service that returns a random name. In my personal convention, I decided to create both service + module in one file and export the module’s name.
The service itself, just a class with methods, nothing complex nor fancy. Let’s use it on the home
module. First import it:
1 2 3 4 5 6 7 8 |
|
Nothing new in here. Let’s use it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
We inject it on the constructor, assign it to a local variable and then we just simply use it. For the template, a little update:
1 2 3 4 5 6 |
|
Yay, new features.
Directives
Sadly, directives are not easy to implement as a class (there are some workarounds) so I prefer to keep using them as a function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Doesn’t hurt anyway. Let’s import it:
1 2 3 4 5 6 7 8 9 |
|
Finally:
1 2 3 4 5 6 |
|
Testing
Webpack is awesome for testing, but sadly Angular is a bit limited in that aspect. On a good world, we would have our test requiring different entry points so we can test our modules in isolation without having to load all the application. But yet again, angular was never made with that intention in mind so we have to forget about that and test like we always did.
There is a karma.conf.js on our project which basically uses a plugin for webpack support. There, we also load a file that I named: tests.webpack.js
. That file will simply load all the tests on our project. Yes, it is a bit of a hack but that is what we have right now:
1 2 3 4 5 |
|
We also load angular and angular-mocks for our tests.
Let’s start karma:
And code a simple test for the sake of the article:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Here we import the module we want to test, we load it and we just test it as we are used to.
NOTE: On my workflow, you need to prepend .test
to your test files.
As a freebie, there on the build
folder you can find the tests coverage.
Production
The hardest part. Or it is not? I guess not.
That will generate the final build at /dist
. Just point your server to /dist/index.html
and you’re good to go. It also includes cache-busting for free :)
Extra
What about third party libraries that doesn’t have Webpack
support? I mean, libraries that are not exporting anything. Well, we can simply require it like:
That will include thelibrary
on the build and that is good enough.
So please library creators, make your libraries to work with Webpack, it is as easy as 2-3 lines of code :)
Conclusions
Webpack is a nice way to use ES6 with Angular 1.x. It is easy to configure and does a lot of things for you.
It is not the only option tho, you can also use JSPM but that is something for another day ;)
I want to give my thanks to my friend Cesar Andreu for creating the original workflow and for helping me with this one.
If you want to play with this demo, you can clone it from here.