Todo-Angular Sample

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

Check out John Lantz's CodeProject article describing this sample.

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-Angular 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.

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 a single .NET web application project that hosts both server-side and client-side components.

Server

The server is a simple ASP.NET Web Application that acts both as a provider of client-side assets (HTML, CSS, and JavaScript) and as an ASP.NET Web API data service.

The folders and files devoted to these roles are outlined in blue .

The Controllers folder holds a single Breeze-styled Web API controller that sits in front of an Entity Framework "Code First" model talking to a SQL Server database.

The server is identical across all Todo sample variations.

Client

The client-side assets reside in the folders and files outlined in red.

There are no client-side .NET dependencies: no ASP.MVC, no Razor, just pure CSS, HTML, and JavaScript.

The app folder holds the client application JavaScript.

The Content folder holds CSS files.

3rd party vendor libraries (including Breeze) are in the Scripts folder.

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.

Angular 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 Angular 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}">
            {{item.Description}}
        </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 Angular recognizes as its directives.

Main app Module

Every Angular app needs a main module. Scripts/app/main.js defines that module, "app".

Important: the 'app' module definition takes a dependency on the "Breeze Angular 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 Angular controller that is responsible for the presentation of Todo items to the user in the "Todo" view.

The controller relies on Angular's dependency injection to acquire access to both Angular 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 Angular programming and almost every Angular application follows this formula.

Let's break it down:

angular.module('app')          // get the application module from Angular 
.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 Angular to inject a number of services:

  • $q - the Angular promises manager for this app
  • $scope - a context object to which the view binds
  • $timeout - Angular 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 Angular "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.

Angular 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.

Notice the use of Angular dependency injection.

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

We're injecting the Angular $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 Angular application. See "Breeze Angular 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 Angular'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 Angular's $log service to write a second copy of the messages to the browser console window.

Bonus: live code

You can explore Breeze + Angular binding with this Todos Plunker.

The following sample only works with modern browsers (IE10+, FF, Chrome).