Getting Started With Angular2 Using Docker Compose

title-websites-angular

 

Update 2016/11/13: The git repository that goes along this article has been updated for the latest post-release version of Angular, which is 2.1.1.  The original article used 2.0.0-rc1, and that version can be found at the corresponding tag.

AngularJS is a popular javascript framework that can help to mitigate complexity on the front-end of your web application.  The most basic way that Angular does this is by providing out-of-the-box databinding so that the changes in your application’s data can be immediately and automatically reflected in the display, and vice-versa.

Coming soon from the Angular team is their 2.0 release which is actually a rewrite from the ground up, and therefore includes some pretty significant changes from v1, though the main concepts are mostly intact.  Perhaps most noticeably, the Angular2 source is written in TypeScript and the use of TypeScript to compose your own applications using Angular2 is heavily encouraged.  In fact, the only tutorial currently available is for TypeScript!  This might put you off if you’ve never used TypeScript before.  Not to worry!

TypeScript is just Javascript, better

TypeScript describes itself as a superset of Javascript, which it is.  Any valid Javascript is also valid TypeScript.  You also have access to many features that you will be familiar with if you’ve done any Object Oriented programming.  In fact you will find that many of the tricks you learned in Javascript are no longer necessary when using Typescript.  The Angular2 tutorial introduces you in such a way that when you finish, you can mostly write an Angular application in TypeScript without ever looking at the language documentation.  Javascript only has one real advantage in my book: No dependencies! Let’s see if we can solve that problem!

The rest of this post is meant to aid you in doing primarily 2 things:

  1. Skip the dependency installation section of the  angular tutorial
  2. Re-organize the tutorial app into something resembling a production application.

Enter Docker Compose

If you don’t know anything about docker, it would probably be helpful to know something about it before continuing.  Crucially, you will need to install Docker Compose to run the application.

We are going to use Docker Compose to take care of dependency installation and automatically compile typescript to javascript every time you save.  The angular tutorial will teach you to do this as well, but our goal is for one simple command, docker-compose up to automatically make sure all of your dependencies are installed in any environment that wants to run the app.  This works seamlessly across Windows, Mac, and Linux.  Bringing new developers onto your project does not require them to have any knowledge of nodeJS or npm – they can just start writing code!

What Can You Skip?

If you want you can skip the whole thing and just clone or download the whole thing.  But that probably wouldn’t be very helpful.  It might be helpful to read through the entire Quickstart just to see what you’re missing, but you don’t need to install node or npm, and you can substitute the following config files for our Docker Compose setup:

package.json

Update: Versions have been bumped; the most current typescript works fine in docker.  Typings is not used at all in the most recent versions of angular.

This is the files that tells the node package manager what dependencies to install, and some other things.  We are going to remove some things from the default package.json in the tutorial:

"scripts": {
   "postinstall": "typings install",
   "typings": "typings"
 },
 "devDependencies": {
   "typescript": "^2.0.3",
   "@types/core-js": "^0.9.34",
   "@types/node": "^6.0.45"
 }

So what’s missing?  We removed most of the entries in scripts.  We are going to use docker-compose up to handle starting both the webserver and the typescript compiler which will still watch for file changes and compile them automatically.

From devDependencies we’ve also removed lite-server and concurrently for the same reasons.

Update:  The following statement is left in if you happen to follow along with the original script, but we are now using the latest version of Typescript.

There is one more item worth mentioning about this: we have frozen the typescript dependency at version 1.6.  The reason for this is that newer versions of the compiler when using watch: true don’t play nice with mounted directories and shared folders, which is what docker uses to make its magic happen.  The typescript devs are still deciding how to handle this.

tsconfig.json

The important addition here is "watch": true. Our tsc container will read this file and watch for changes.  Later we will add rootDir and outDir entries to separate our source from the compiled script and organize our things a little bit more neatly.

Finally, the lynchpin of our setup: docker-compose.yml

This is the file that docker reads to set up the environment.  We are using three containers.  Let’s look at each one.

httpd

This is the official apache server image.  We aren’t changing anything, but we will add some parameters to allow the container to see our project and serve it on port 80.  You could easily drop in Nginx, or even a lite-server container if you really want to.

httpd:
    image: httpd:latest
    volumes:
      - ./webroot:/usr/local/apache2/htdocs/
      - ./node_modules:/usr/local/apache2/htdocs/node_modules
    ports:
      - "80:80"

The first volume mounts our webroot into the webroot defined by the apache container.  If you follow the tutorial exactly and put everything into the same folder, your first volume will read:

- ./:/usr/local/apache2/htdocs/

The second volume is also only necessary if you aren’t using the root directory of the project as the webroot.  This sort of volumeception is arguably unnecessary, but it does allow you to keep package.json outside of the webroot.  It mounts your node modules folder inside of the already mounted webroot.  I admit, I kinda just wanted to see if it would work.  And it does!

npm

npm:
    image: treyjones/npm:latest
    volumes:
      - ./:/npm

The Node Package Manager.  The reason for doing this.  The first time you run docker-compose up, the npm container will read your package.json and begin installing all of the dependencies defined there.  This will take a while on the first run.  When it’s finished, it will exit.  The next time you docker-compose up, it will read package.json, see that there is nothing new, and exit without doing anything else.  If someone adds a dependency in package.json, you can guess what will happen.

Finally, instead of gitignoring the entire node_modules directory, we will just ignore everything in it, so that the directory will stay in source control, and can be immediately mounted into the httpd container.

tsc

tsc:
    image: treyjones/tsc:latest
    volumes:
      - ./:/tsc

This magical 9MB container watches all the files that you tell it to and automatically compiles them into a directory of your choice.  This allows you to write TypeScript, reload the browser and see your changes.  You don’t even need to know about the compilation process.  The workflow is the same as in-browser transpiling, but you won’t take the performance hit.  In the Angular tutorial we don’t define an outDir, and the .js files just live in the same directory as the .ts files.  In our slightly modified version, the .ts source will live outside of the webroot.

Conclusion

So, if you’ve made it through the whole tutorial, or just pulled the example project, you should have a working Angular Tour of Heroes.  Various file paths have been changed from the code in the tutorial, so that we can have the source and webroot separated.

$ cd /path/to/tour-of-heroes
$ docker-compose up

You will see some output from each container, including something like this:

npm_1    | npm WARN package.json tour-of-heroes@1.1.0 No description
npm_1    | npm WARN package.json tour-of-heroes@1.1.0 No repository field.
npm_1    | npm WARN package.json tour-of-heroes@1.1.0 No README data

Once again, the first time you run the app, it won’t work until the npm container finishes installing dependencies.  You will know by some output like this:

tourofheroes_npm_1 exited with code 0

Now the final item: what is the web address of my app?  If you are using linux, you should find it at http://localhost.  If you are using Mac or Windows it is served from port 80 of the docker machine, the tiny VM that is hosting the linux kernel for docker to use.  To get this address:

$ docker-machine ip
192.168.99.100

So your project is at http://ip.of.your.machine.  Documentation will tell you that this ip address could change, but so far it’s very consistently the same for me, so I have made an entry in my hosts file to point at that address, and access all of my docker web projects at http://project.dev

Update: Docker for Mac and Docker for Windows are now available, and if you are using these projects, you will also find your project at http://localhost:80.

Finally, docker-compose can be used for more than just starting your project.  Here are a few helpful commands to know:

docker-compose restart npm

Restart the npm container.  You will need to do this if you alter package.json.  You can also restart any container by the name given in docker-compose.yml independently of the other containers.

docker-compose exec httpd bash

Open a shell on the httpd container.  This can be a valuable debugging tool when using docker compose, if something isn’t behaving as expected.  You can make sure volumes are being mounted properly, explore the directory structure, or whatever you need.  Note that this will not work for the tsc or npm containers in our project because bash is not installed.  These containers are based on Alpine Linux and are extremely minimal (and very small!). Just use sh instead of bash.

That’s it.  Go forth and dockerize.