24 April 2016: Update to angular@2.0.0.beta.15

9 April 2016: Fix call to wrong method which was causing a runtime error

17 March 2016: Update to angular@2.0.0.beta.9 (dependencies updated as well)

There’s been a dearth of good documentation about building Angular 2 apps with vanilla ES6/ES2015 (Typescript’s been stealing the show so far), so today we’ll be using vanilla ES6 in conjunction with Angular 2 and a simple MEAN backend (MongoDB, ExpressJS, and Node.js) to create a rudimentary todo app. This todo app will:

Store todos in a MongoDB database

Access those todos via a REST API built with Express

Display those todos in a single page application built with the latest Angular 2 beta (beta.15).

The Final Product

Demo: Angular 2 Todo App

MEAN Stack Recap

Just a quick recap for those who aren’t familiar (although if you are, feel free to skip to the next section): the MEAN Stack stands for MongoDB, Express.js, AngularJS and Node.js. Here’s how each of these components fit together in our app.

Node.js is a server-side Javascript engine and what allows us to run our code!

MongoDB is our database

Express.js is a Node.js web framework that most importantly allows us to route requests to the correct destination

Angular 2 is the cutting-edge frontend framework that allows our users to interact and change data in our app

We’ll write the entire app in ES6 (aka ES2015, Harmony, ESNext) using the Babel transpiler, and we’ll package our frontend components using Webpack (which allow us to use require s in our frontend code).

Let’s begin!

File Structure

Create a new directory called todoapp with the following structure:

todoapp - src // Our ES6 backend code --- controllers // Controllers directory --- models // Mongoose models directory - views // Our Jade views --- templates // Our Angular templates - webpack // Our frontend code --- js // Our frontend JS

Dependencies

To start your app, create a file called package.json in the root app directory ( todoapp ) with the following content:

// package.json { " name " : " todoapp " , " version " : " 1.0.0 " , " description " : "" , " main " : " index.js " , " engines " : { " node " : " 4.2.1 " }, " scripts " : { " babel " : " babel src --out-dir lib " , " babel:w " : " babel -w src --out-dir lib " , " webpack " : " webpack " , " webpack:w " : " webpack --watch " , " postinstall " : " npm run babel && npm run webpack " , " start " : " node lib/app " }, " author " : "" , " license " : " ISC " , " dependencies " : { " angular2 " : " 2.0.0-beta.15 " , " babel-cli " : " 6.7.7 " , " babel-core " : " 6.7.7 " , " babel-loader " : " 6.2.4 " , " babel-preset-es2015 " : " 6.6.0 " , " body-parser " : " 1.15.0 " , " bootstrap " : " 3.3.6 " , " css-loader " : " 0.23.1 " , " es6-promise " : " 3.1.2 " , " es6-shim " : " 0.35.0 " , " express " : " 4.13.4 " , " file-loader " : " 0.8.5 " , " jade " : " 1.11.0 " , " method-override " : " 2.3.5 " , " mongoose " : " 4.4.13 " , " morgan " : " 1.7.0 " , " reflect-metadata " : " 0.1.2 " , " rxjs " : " 5.0.0-beta.2 " , " script-loader " : " 0.6.1 " , " style-loader " : " 0.13.0 " , " systemjs " : " 0.19.26 " , " webpack " : " 1.13.0 " , " zone.js " : " 0.6.10 " } }

This file tells NPM what dependencies we need, and sets up a few helper scripts we can use to compile our ES6 and package our frontend files. The reason some versions are prefixed with ^ and others aren’t is because some dependencies are OK to be updated ( ^ ) while others are Angular peer dependencies, and Angular calls for those specific versions.

To install the dependencies, run npm install - NPM will create a new directory called node_modules and put our dependencies in there.

Dependency Overview

There are a lot of dependencies. If you’re really interested, here’s why we need all of them:

angular2 - Well, we’re making an Angular 2 app

body-parser - Parse form data and allow it to be accessed by the server

bootstrap - Base CSS styles

es6-promise - Angular 2 dependency

es6-shim - Another Angular dependency

express - Our server

jade - Template engine for terse HTML

method-override - Allows PUT and DELETE requests on old browsers

mongoose - MongoDB wrapper

morgan - Request logger

reflect-metadata - Another Angular dependency

rxjs - Another Angular dependency

systemjs - Another Angular dependency

zone.js - Another Angular dependency

babel-cli - Allows us to transpile ES6 via babel in npm scripts

in npm scripts babel-core - To transpile files passed via babel-loader

babel-loader - Webpack Babel loader, so that webpack can transpile

babel-preset-es2015 - ES2015 Babel preset

css-loader - Allows Webpack to parse @import and url() in CSS

and in CSS file-loader - Allows Webpack to load font files (e.g. in CSS)

script-loader - Allows Webpack to inline JS without parsing require s

s style-loader - Allows Webpack to link CSS sheets in HTML files

webpack - Webpack packages and processes frontend files

Setting Up Our Environent

In order to use Babel, we need to define which future version of Javascript we’re using. Babel supports ES6, JSX (React), ES7 and other upcoming versions of Javascript, but we’re going to stick with the nearest future release, ES6 (aka ES2015). To let Babel know of that, we need to create a .babelrc file in our app root (the todoapp directory) with the following content:

// .babelrc { " presets " : [ " es2015 " ] }

We also need to define our Webpack config, which we’ll write in ES6. Create a new file, in the root app directory as well, called webpack.config.babel.js with the following contents:

import path from ' path ' ; // Join paths with the right type of slash let config = { entry : path . join ( __dirname , ' webpack ' , ' index.js ' ), // We'll create this file later, when we write the frontend code output : { path : path . join ( __dirname , ' public ' ), filename : ' bundle.js ' }, module : { loaders : [ { test : / \. js$/ , // Transpile all .js files from ES6 to ES5 loaders : [ ' babel-loader ' ] }, { test : / \. css$/ , // Use the style-loader for all .css files loaders : [ ' style ' , ' css ' ] }, { test : / \.( ttf|eot|svg|woff ( 2 )?)(\?[ a-z0-9 ] + )? $/ , // Use the file-loader for fonts loaders : [ ' file-loader ' ] } ] } }; export default config ;

Backend

Configuring Express

Now that the dependencies have been installed, let’s create our app. To begin, create a file called app.js in the src directory. The following code will set up our dependencies, connect to our DB, and create a rudimentary HTTP server.

// src/app.js /** * Import dependencies */ import express from ' express ' ; import logger from ' morgan ' ; // Logs each server request to the console import bodyParser from ' body-parser ' ; // Takes information from POST requests and puts it into an object import methodOverride from ' method-override ' ; // Allows for PUT and DELETE methods to be used in browsers where they are not supported import mongoose from ' mongoose ' ; // Wrapper for interacting with MongoDB import path from ' path ' ; // File path utilities to make sure we're using the right type of slash (/ vs \) /** * Configure database */ mongoose . connect ( ' mongodb://localhost:27017/todoDB ' ); // Connects to your MongoDB. Make sure mongod is running! mongoose . connection . on ( ' error ' , function () { console . log ( ' MongoDB Connection Error. Please make sure that MongoDB is running. ' ); process . exit ( 1 ); }); /** * Configure app */ let app = express (); // Creates an Express app app . set ( ' port ' , process . env . PORT || 3000 ); // Set port to 3000 or the provided PORT variable app . set ( ' views ' , path . join ( __dirname , ' .. ' , ' views ' )); // Set our views directory to be `/views` (in the app root, which is one level above) app . set ( ' view engine ' , ' jade ' ); // Set our view engine to be Jade (so when we render these views, they are compiled with the Jade compiler) app . use ( express . static ( path . join ( __dirname , ' .. ' , ' public ' ))); // Set the static files directory - /public will be / on the frontend app . use ( logger ( ' dev ' )); // Log requests to the console app . use ( bodyParser . json ()); // Parse JSON data and put it into an object which we can access app . use ( methodOverride ()); // Allow PUT/DELETE /** * Start app */ app . listen ( app . get ( ' port ' ), function () { console . log ( `App listening on port ${ app . get ( ' port ' )} !` ); });

We can transpile our code to ES5 via npm run babel , which will output to the lib directory. Running node lib/app will start our little HTTP server, which you can visit at http://localhost:3000. Also, if you want your code to be continuously compiled on changes, run npm run babel:w , which will run the same Babel build script but will also watch for changes.

Jade

Taking a close look at the code, you may have noticed the line app.set('view engine', 'jade'); . What this does is tell Express that the files in the views directory should be compiled using the Jade template engine. Jade is a very popular templating language for Node.js, and it’s pretty much a terser version of HTML:

doctype html html head title My First Jade meta(charset='utf-8') meta(name='viewport' content='width=device-width,initial-scale=0') body h1 My First Jade p This is my first Jade template!

We’ll go into more depth later, when we write the code for the frontend.

Creating a Model

Now that we’ve got the basics of our app setup, we need to create a model for the Todos. This model will define the shape of our documents (analagous to rows in SQL), and Mongoose will use this model to create a collection in MongoDB (analagous to a table in SQL) for the Todos. To create our Todo model, in the src/models directory create a new file called Todo.js with this code:

// src/models/Todo.js import mongoose from ' mongoose ' ; // Create a schema for the Todo object let todoSchema = new mongoose . Schema ({ text : String }); // Expose the model so that it can be imported and used in the controller (to search, delete, etc) export default mongoose . model ( ' Todo ' , todoSchema );

Creating a Controller

Now, we need to create a controller. This controller will handle the routes of our Todos REST API (e.g. GET /todos , POST /todos ). Create a new file, main.js , in the src/controllers directory with this code:

// src/controllers/main.js import Todo from ' ../models/Todo ' ; // Import the Todo model so we can query the DB let mainController = { getIndex : ( req , res ) => { res . render ( ' index ' ); // Compiles the file named "index" in the views directory (`/views`) using the view engine (Jade). // We'll create this Jade file later }, // Allows us to access our Angular templates (more on that later) getTemplate : ( req , res ) => { res . render ( ' templates/ ' + req . params . template ); }, // This gets all Todos in the collection and sends it back in JSON format getAllTodos : ( req , res ) => { Todo . find ({}, ( err , todos ) => { if ( err ) { // Send the error to the client if there is one return res . send ( err ); } // Send todos in JSON format res . json ( todos ); }); }, postNewTodo : ( req , res ) => { // This creates a new todo using POSTed data (in req.body) Todo . create ({ text : req . body . text , done : false }, ( err , todo ) => { if ( err ) { return res . send ( err ); } Todo . find ({}, ( err , todos ) => { if ( err ) { return res . send ( err ); } // Send list of all todos after new one has been created and saved res . json ( todos ); }); }); }, deleteTodo : ( req , res ) => { Todo . remove ({ _id : req . params . id }, ( err , todo ) => { if ( err ) { return res . send ( err ); } Todo . find ({}, ( err , todos ) => { if ( err ) { return res . send ( err ); } res . json ( todos ); }); }); } } export default mainController ;

Creating the Routes

Back in the main app file, src/app.js , we need to define our API routes. First, we need to import our route handlers (“controllers”). Right under the dependencies, add the main controller:

// src/app.js ... import path from ' path ' // File path utilities to make sure we're using the right type of slash (/ vs \) /** * Import controllers */ import mainController from ' ./controllers/main ' ; /** * Configure database */ ...

Now, we need to configure the routes. Below, under the “Configure app” section (where we have all the app.set and app.use code), add this:

// src/app.js ... app . use ( methodOverride ()); // Allow PUT/DELETE /** * Configure routes */ app . get ( ' / ' , mainController . getIndex ); app . get ( ' /templates/:template ' , mainController . getTemplate ); app . get ( ' /todos ' , mainController . getAllTodos ); app . post ( ' /todos ' , mainController . postNewTodo ); app . delete ( ' /todos/:id ' , mainController . deleteTodo ); /** * Start app */ ...

How does this work? Here’s an example: when you GET /todos in your browser, Express will execute the middleware above (logging, body parsing, method overriding) and then call the mainController.getAllTodos function with (req, res) as arguments. The getAllTodos function will query the database for all the todos and send them to the client in JSON format.

Compiling the code again ( npm run babel ) and running node lib/app will start up our server again, however this time you can visit http://localhost:3000/todos and see our list of todos (currently empty).

Frontend

Our API has been built. Now, we need a way to interact with it on the frontend. To do so, we can use Angular!

Creating an Angular Service

Let’s begin by creating a service to interact with our API. Create a file in /webpack/js called TodoService.js . This will servee as a wrapper for the Angular 2’s HTTP function. Put the following code inside:

// webpack/js/TodoService.js import { Inject } from ' angular2/core ' ; // Allows us to inject a dependency into a module that's not a component import { Http , Headers } from ' angular2/http ' ; import ' rxjs/add/operator/map ' // Allows us to map the HTTP response from raw to JSON format class TodoService { constructor ( http ) { this . http = http ; // http is an instance of the main Http class } getAllTodos () { return this . http . get ( ' /todos ' ) . map (( res ) => { return JSON . parse ( res . _body ); }); } postNewTodo ( data ) { let headers = new Headers (); headers . append ( ' Content-Type ' , ' application/json ' ); // Set JSON header so that data is parsed by bodyParser on the backend return this . http . post ( ' /todos ' , JSON . stringify ( data ), { headers : headers }). map (( res ) => { return JSON . parse ( res . _body ); }); } deleteTodo ( id ) { return this . http . delete ( ' /todos/ ' + id ) . map (( res ) => { return JSON . parse ( res . _body ); }); } } // Declares that Http should be injected each time a new instance of TodoService is created TodoService . parameters = [ new Inject ( Http )]; export { TodoService }

Creating an Angular Component

Now, we need to define a component. This will hold our main frontend logic and is, to some extent, analagous to a controller in Angular 1. Create a file in webpack/js called TodoComponent.js with the following code:

// webpack/js/TodoComponent.js import { Component , View } from ' angular2/core ' ; // Import Component and View constructor (for metadata) import { HTTP_PROVIDERS } from ' angular2/http ' ; // We're using http in our TodoService, but we can only specify providers in the component import { TodoService } from ' ./TodoService ' class TodoComponent { constructor ( todoService ) { this . todos = []; this . todoData = { text : '' }; this . todoService = todoService ; this . todoService . getAllTodos () // Rxjs, we subscribe to the response . subscribe (( res ) => { this . todos = res ; }); } createTodo () { this . todoService . postNewTodo ( this . todoData ) . subscribe (( res ) => { this . todos = res ; this . todoData . text = '' ; }); } deleteTodo ( id ) { this . todoService . deleteTodo ( id ) . subscribe (( res ) => { this . todos = res ; }) } }; TodoComponent . annotations = [ new Component ({ selector : ' todo-app ' , // Tag to show app providers : [ TodoService , HTTP_PROVIDERS ], // Lets Angular know about TodoService and Http templateUrl : ' templates/TodoComponent ' // Our template, we'll create this next }), ]; TodoComponent . parameters = [[ TodoService ]]; export { TodoComponent };

Angular Bootstrapping

In webpack/js , create a new file called boot.js with the following contents:

// webpack/js/boot.js import { bootstrap } from ' angular2/platform/browser ' ; // Angular magic to bootstrap the application in a web browser import { TodoComponent } from ' ./TodoComponent ' ; let boot = document . addEventListener ( ' DOMContentLoaded ' , () => { bootstrap ( TodoComponent ); }); // Expose boot so it can be required by webpack. module . exports = boot ;

Webpack Index

Now, we can put everything in a unified webpack file. Create a new file in webpack called index.js with the following content:

// webpack/index.js require ( ' bootstrap/dist/css/bootstrap.min.css ' ) // Import modules without parsing with script-loader, // !! to override all loaders require ( ' !!script!angular2/bundles/angular2-polyfills.min.js ' ); require ( ' !!script!rxjs/bundles/Rx.umd.min.js ' ); require ( ' !!script!angular2/bundles/angular2-all.umd.min.js ' ); // Import boot, resolve imports/requires, and pass through Babel require ( ' ./js/boot ' );

The !!script! is a special flag that will inline the script without using any loaders, equivalent to using a <script> tag in HTML. Webpack will use the index.js file to create a new file, bundle.js , with all the code bundled up and minified.

Jade Template

Now that we’ve gotten our frontend Javascript finished, we need to create our HTML. In the views/templates directory, create a file called TodoComponent.jade and put the following code inside:

// views/templates/TodoComponent.jade .container .jumbotron.text-center h1 Todos span.label.label-info {{todos.length}} .row .col-sm-6.col-sm-offset-3 table.table.table-bordered thead tr th.text-center Todos tbody tr(*ngFor="var todo of todos") td input(type='checkbox' on-click='deleteTodo(todo._id)' bind-checked="false") | | {{todo.text}} .row .col-sm-6.col-sm-offset-3 form(on-ngSubmit='createTodo()') .form-group input.form-control.input-lg.text-center(type='text' placeholder='What to do next?' bindon-ngModel='todoData.text' required) .form-group.text-center br button.btn.btn-success.btn-lg(type='submit') Do Something

Main Jade Template

Now, create a file in views called index.jade and put the following:

// views/index.jade doctype html html head title Angular 2/MEAN Stack Todo App meta(name='description' content='A small todo app written using the Angular 2 beta and the rest of the MEAN stack') meta(charset='utf-8') meta(name='viewport' content='width=device-width,initial-scale=1.0') style. /* Initial CSS to prevent FOUC (Flash of Unstyled Content) */ .loading { padding-top: 15px; font-size: 36px; font-family: Arial; text-align: center; } body todo-app .loading Loading... script(src="/bundle.js")

Compiling the Frontend Resources

Let’s compile everything with Webpack now. Running npm run webpack will create a bundle file with all of our Angular code and dependencies packaged up and ready for deployment. If you run node lib/app and visit http://localhost:3000, you should now see this:

Conclusion

We now have a rudimentary Todo app with a Node.js/Express server and Angular frontend. Let me know what you make of it in the comments!

There’s a demo online on at Angular 2 Todo App Demo, and the full source is viewable at Angular 2 Todo App Source

Other Notes

If you get a warning like the one below, it’s because one of the Angular 2 dependencies, Zone.js, deprecated a couple of methods which the latest Angular still uses. We’ll just have to wait until Angular is updated for the warnings to stop showing up.

Acknowledgments

This was based on Scotch.io’s great blog post, and the architecture is based on sahat’s Hackathon Starter

Tags