Todo-AngularJS-Sequelize Sample

The Todo-AngularJS-Sequelize sample demonstrates Breeze, AngularJS and Sequelize working together in a single page CRUD (Create/Read/Update/Delete) application.

The user experience is the same for this and all Todo Sample variations. The source lies within the "Samples" package which you can download here.

Todo-AngularJS runs in modern browsers such as IE9, IE10, and recent Chrome, Safari, Firefox, and WebKit browsers. Breeze does not support AngularJS apps running in older browsers that lack ECMAScript 5 property getters and setters.

Download

Download ALL of the Breeze JavaScript samples from github as a zip file.

In this case we’re interested in the “Todo-AngularJS” sample, located in the node/todo-angular directory. These instructions assume this will be your current working directory.

At the top level you will find:

  • a readme.md explaining how to install and run the app
  • the db-script folder
  • the client folder full of client application HTML, CSS, and JavaScript
  • the server folder containing the node/express server application JavaScript.

You can view, edit, and run the code in this project using the tools of your choice.

The sample assumes that you've installed node.js, MySql and/or PostgreSQL

App Architecture

Todo is the simplest full-CRUD app we could think of. The architecture is deliberately primitive and simplistic. There's only one model type (TodoItem) and only one screen. It’s all about the mechanics of manipulating data and presenting them for user review and edit.

The entire app is organized in 3 folders that host server-side and client-side components as well as database scripts.

Server

The server is written for Node.js running Express, and can be run on a MySql or PostgreSQL database.

Client

The app folder holds the client application JavaScript.

The css folder holds CSS files.

3rd party vendor libraries (including Breeze) are in the bower_components folder. These dependencies will be installed automatically via Bower Package Manager.

The index.html file is both the host page that loads the assets and the residence of the one-and-only view, written in Angularized-HTML.

We divide the client app into four functional areas:

Area Comment
Layout Index.html contains the "View" HTML with AngularJS data binding markup. It's also the application host page with css and script tags.
Presentation
Logic
JavaScript in the controller.js exposes data and method binding points to the view. All AngularJS code lives here. Many of the methods implement CRUD actions by delegating to methods of the service layer.
Model &
Data Access
JavaScript in the dataservice.js creates, queries, deletes, and saves entities using BreezeJS. All BreezeJS code lives here.
Logging The logger.js presents activity messages and errors as "toasts" popping from the lower right via the toastr.js 3rd party library.

AngularJS highlights

We assume you're acquainted with AngularJS and that the markup in index.html and the JavaScript programming model are familiar to you.

View

The Todo app's  "View" is embedded in index.html. You'll recognize the way AngularJS markup binds buttons to actions, value attributes to data properties, CSS classes to controller properties, and repeats Todo items within a list using an <li> tag template.

The “data-ng-app” attribute references the “app” module; the “data-ng-controller” attribute references the “TodoCtrl” controller module.

Here’s an excerpt from the view showing the repeated TodoItem template within the <li> tag:

<li data-ng-repeat="item in items | filter:itemFilter">
    
    <!-- Readonly View -->
    <div data-ng-hide="isEditing(item)">
        <input type="checkbox"
               data-ng-model="item.IsDone"
               data-ng-class="{done: item.IsDone}" />
        <label data-ng-click="editBegin(item)"
               data-ng-class="{done: item.IsDone, archived: item.IsArchived}">
            
        </label>
        <a href="#" data-ng-click="deleteItem(item)">X</a>
    </div>

    <!-- Editing View -->
    <div data-ng-show="isEditing(item)">
        <form data-ng-submit="editEnd()">
            <input type="text" autofocus
                   data-ng-model="item.Description"
                   data-ng-blur="editEnd()"
                   data-ng-mouseout="editEnd()" />
        </form>
    </div>
</li>

We're using the HTML “data-“ prefix for attributes that AngularJS recognizes as its directives.</p>

Main app Module

Every AngularJS app needs a main module. Scripts/app/main.js defines that module, “app”.

Important: the ‘app’ module definition takes a dependency on the “Breeze AngularJS Service” module.

angular.module('app', ['breeze.angular']);

That module has a “breeze” service which, when injected into any application component, will configure Breeze for this application. We’ll see it injected into the dataservice component.

All application JavaScript files follow the Immediately Invoked Function Execution (IIFE) pattern that keeps the global window namespace free of application variable pollution. Such modules begin and end in this fashion.

(function() {

   ... your code here ...

})();

Controller

The Scripts/app/controller.js file defines the AngularJS controller that is responsible for the presentation of Todo items to the user in the “Todo” view.

The controller relies on AngularJS’s dependency injection to acquire access to both AngularJS and application services.

angular.module('app').controller('TodoCtrl',
['$q', '$scope', '$timeout', 'dataservice', 'logger', controller]);

The syntax is a bit peculiar but you get used to it quickly. “DI” is an essential aspect of AngularJS programming and almost every AngularJS application follows this formula.

Let’s break it down:

angular.module('app')          // get the application module from AngularJS 
.controller('TodoCtrl',        // name the controller
['service1', 'service2', ...   // name(s) of services to inject
controller]);                  // the definition function is last item in the array

// The definition function w/ a parameter for each injectable named above
function controller(service1, service2, ...) {...}

Many folks prefer an anonymous definition function instead of the named controller definition function that we like. It’s up to you.

This controller asks AngularJS to inject a number of services:

  • $q - the AngularJS promises manager for this app
  • $scope - a context object to which the view binds
  • $timeout - AngularJS equivalent of ‘setTimeout’
  • dataservice - the application data access service
  • logger - the application’s logging facility

The $scope object is the vehicle for binding the controller to the HTML in the view. The controller add properties and methods to the $scope object. The HTML binds to these $scope members declaratively with AngularJS “directives”.

“directives” is a fancy word for “data bindings”

A controller written in this “MVVM” style makes no references to view elements at all. The View (the HTML) only uses JavaScript expressions to bind to the $scope.

AngularJS binding declarations are written in a kind of JavaScript syntax. But, as a matter of principle, we keep those JavaScript expressions clean and free of all decision logic. Logical expressions belong in the controller, not in the HTML.

Dataservice

The dataservice.js file handles the creation of new Todo objects and all interactions with the server. It's written in Breeze and almost all Breeze-related code is in this dataservice. See the "Todo Sample Dataservice" page for details.</p>

Notice the use of AngularJS dependency injection.

angular.module('app').factory('dataservice',
['$timeout', 'breeze', 'logger', dataservice]);

We’re injecting the AngularJS $timeout service (an abstraction over JavaScript’s setTimeout), the ‘breeze’ service, and the application’s ‘logger’ service (described below).

Inject the ‘breeze’ service is a way of accessing Breeze itself without getting it from the global (window) namespace. But that’s not why we’re injecting it. We’re actually looking for a side-effect of that injection: the configuration of Breeze for an AngularJS application. See “Breeze AngularJS Service” to learn more.

The controller is insulated from data access details and has no direct connection with Breeze. The dataservice api provides it with all that it needs … and no more.

The dataservice illustrates the “Revealing Module” module pattern by listing its api near the top of the file:

var service = {
    addPropertyChangeHandler: addPropertyChangeHandler,
    createTodo: createTodo,
    deleteTodo: deleteTodo,
    getTodos: getTodos,
    hasChanges: hasChanges,
    purge: purge,
    reset: reset,
    saveChanges: saveChanges
};

Now for a few comments about these methods.

The controller considers issuing a save whenever a property changes. It attaches a handler to the addPropertyChangeHandler. That method invokes the handler whenever there is a change to any entity property of any entity. You don’t have to listen to every entity or every property individually. Breeze offers a “one stop shop” via the EntityManager.entityChanged event.

createTodo creates a new instance of a TodoItem initialized as requested. This item is new in the cache but not yet saved to the database. By design, the command to save it comes from the controller. Note that we did not define the TodoItem in JavaScript; Breeze figured that out from metadata.

deleteTodo marks the TodoItem to be deleted but does not physically delete it. By design, the command to update the database (to “save” the delete) comes from the controller.

getTodos issues a Breeze EntityQuery for all Todos, optionally excluding archived Todos (whose .isArchived flag is true). The code illustrates how to construct a query based on user input.

hasChanges indicates if the cache holds any unsaved changes. It delegates to EntityManager.hasChanges().

purge and reset are demo-only methods that reset the database to initial conditions after you’ve wrecked havoc with your trials. They post messages to the Web API controller with AngularJS’s $http service. $http is just fine for simple AJAX commands while we let Breeze handle data access.

saveChanges asks Breeze to save all cached entities with pending changes. There might be one entity to save (as when you change a description or delete a Todo). There might be many entities to save (as when you click “Mark all completed”). You don’t have to keep track of which entities are “dirty”. You don’t have to differentiate between add, modify and delete operations. That’s Breeze’s job.

Save Queuing

This application takes care of one potential ‘gotcha’: an attempt to save a second change to an entity when a save request for that entity is already “in flight”.

That can happen in an app like this which saves automatically when it detects changes. It has no Save button.

Duplicate saves are rarely acceptable. For example, you don’t want to add the same TodoItem twice.

We guard against duplicate save by putting save requests on a queue. If there is a save request in process, the EntityManager.saveChanges queues subsequent requests until it receives a server response for the current save. It processes the queue in order until it runs dry.

“Save Queuing” is a Breeze Labs feature and you can learn more about it here. We enabled “Save Queuing” for this application’s EntityManager at the top of the dataservice:

manager.enableSaveQueuing(true);

Logger

The logger.js file is an abstraction for logging messages and displaying them to the user. Internally it uses John Papa’s open source toastr library to display messages as "toasts" that float up from the bottom right of the screen.

It also relies on AngularJS’s $log service to write a second copy of the messages to the browser console window.