My first AngularJS application
Earlier this year I blogged about the AngularJS framework. I thought the docs and the tutorial were cool and overall it felt like a real cool way to build a JavaScript "Application" - and yes, with the quotes there I'm talking about something more than a simple one page, Ajaxified file. Unfortunately, I never got the time to play more with AngularJS until this week. Yesterday I reviewed the tutorial again and decided to take a stab at building my own application. I ran into a few hiccups, but in general, the process was straight forward, and after actually building something with the framework, I love it, and can't wait to try more with it. Let's take a look at my application. (And of course, please keep in mind that this is my first AngularJS application. It's probably done completely wrong. Keep that in mind....)
For my first application, I decided to build a simple Note manager. This is something I've done many times before. I'm a huge fan of Evernote and I keep hundreds of notes stored in it. My application will allow for adding, editing, deleting, and viewing of simple notes. In this first screen shot you can see the basic UI.

You can click a note to see the details...

And of course basic editing is supported...

So what does the code look like? First, I've got my core index page:
2<html xmlns:ng="http://angularjs.org/">
3<head>
4 <meta charset="utf-8">
5 <title>Note Application</title>
6 <link rel="stylesheet" href="http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css">
7 <link rel="stylesheet" href="css/main.css"/>
8</head>
9
10<body ng:controller="NoteCtrl">
11
12 <div class="container">
13
14 <ng:view></ng:view>
15
16 </div>
17
18 <script src="lib/angular/angular.js" ng:autobind></script>
19 <script src="js/controllers.js"></script>
20 <script src="js/services.js"></script>
21</body>
22</html>
All of the real display will happen within the view, which is managed in my controller. Let's look at that file next.
2
3function NoteCtrl($route) {
4 var self = this;
5
6 $route.when('/notes',
7 {template: 'partials/note-list.html', controller: NoteListCtrl});
8 $route.when('/notes/add',
9 {template: 'partials/note-edit.html', controller: NoteEditCtrl});
10 $route.when('/notes/edit/:noteId',
11 {template: 'partials/note-edit.html', controller: NoteEditCtrl});
12 $route.when('/notes/:noteId',
13 {template: 'partials/note-detail.html', controller: NoteDetailCtrl});
14 $route.otherwise({redirectTo: '/notes'});
15
16 $route.onChange(function(){
17 self.params = $route.current.params;
18 });
19
20 $route.parent(this);
21}
22
23function NoteListCtrl(Note_) {
24 var self = this;
25 self.orderProp = 'title';
26 self.notes = Note_.query();
27
28 self.delete = function(id) {
29 console.log("delete "+id);
30 Note_.delete(id);
31 self.notes = Note_.query();
32 //refreshes the view
33 self.$root.$eval();
34 }
35
36 self.edit = function(id) {
37 window.location = "./index.html#/notes/edit/"+id;
38 }
39}
40
41function NoteDetailCtrl(Note_) {
42 var self = this;
43 self.note = Note_.get(self.params.noteId);
44
45 if(typeof self.note === "undefined") window.location = "./index.html";
46}
47
48function NoteEditCtrl(Note_) {
49 console.log('EDIT CTRL');
50 var self = this;
51
52 if(self.params.hasOwnProperty("noteId")) self.note = Note_.get(self.params.noteId);
53 else self.note = { title:"", body:""};
54
55 self.cancel = function() {
56 window.location = "./index.html";
57 }
58
59 self.save = function() {
60 Note_.store(self.note);
61 window.location = "./index.html";
62 }
63
64}
Essentially, this file consists of the main controller which simply handles routing based on the URL. The other three controls handle listing, editing, and the detail view. Now let's look at the note service.
2
3angular.service('Note', function(){
4 return {
5 query:function() {
6 var notes = [];
7 for(var key in localStorage) {
8 if(key.indexOf("note_") == 0) {
9 notes.push(JSON.parse(localStorage[key]));
10 }
11 }
12 console.dir(notes);
13 return notes;
14 },
15 delete:function(i) {
16 localStorage.removeItem("note_"+i);
17 },
18 get:function(i) {
19 if(localStorage["note_"+i]) return JSON.parse(localStorage["note_"+i]);
20 console.log("no note for "+i);
21 },
22 store:function(note) {
23 if(!note.hasOwnProperty('id')) {
24 //yep, hack, get all notes and find highest id
25 var notes = this.query();
26 var highest = 0;
27 for(var i=0; i<notes.length; i++) {
28 if(notes[i].id > highest) highest=notes[i].id;
29 }
30 note.id = ++highest;
31 }
32 note.updated = new Date();
33 localStorage["note_"+note.id] = JSON.stringify(note);
34 }
35 }
36
37});
As you can see, my note service is simply a collection of methods. My application makes use of HTML5's Local Storage feature. It looks for any key that begins with note_ and considers that application data. It stores notes as JSON strings and the service handles both deserializing and serializing the information. What's cool is that I could - in theory - replace this with a SQL version and nothing else would change.
All that's really left are the views. Here is the first view - and keep in mind - this is the entire file for the display of notes on the first screen.
2 Sort by:
3 <select name="orderProp">
4 <option value="title">Title</option>
5 <option value="-updated">Newest</option>
6 </select>
7
8
9<h2>Notes</h2>
10
11<table class="bordered-table">
12 <tr>
13 <th>Title</th>
14 <th>Updated</th>
15 <th> </th>
16 </tr>
17 <tr ng:repeat="note in notes.$filter(query).$orderBy(orderProp)">
18 <td><a href="#/notes/{{note.id}}">{{note.title}}</a></td>
19 <td>{{note.updated | date:'MM/dd/yyyy @ h:mma'}}</td>
20 <td><a href="" ng:click="edit(note.id)" title="Edit"><img src="images/page_edit.png"></a> <a href="" ng:click="delete(note.id)" title="Delete"><img src="images/page_delete.png"></a></td>
21 </tr>
22</table>
23
24<p ng:show="notes.length==0">
25 Sorry, there are no notes yet.
26</p>
27
28<a href="#/notes/add" class="btn primary">Add Note</a>
Take note of the ng:repeat and the embedded tokens wrapped in {}. Also note how easy it is to format the date. The detail view is incredibly boring.
2<p>
3<b>Last Updated:</b> {{note.updated | date:'MM/dd/yyyy @ h:mma'}}
4</p>
5
6{{note.body}}
And the edit one is also pretty trivial:
2
3<form class="form-stacked" onSubmit="return false" id="editForm">
4 <div class="clearfix">
5 <label for="title">Title:</label>
6 <div class="input">
7 <input type="text" id="title" name="note.title" class="xlarge">
8 </div>
9 </div>
10
11 <div class="clearfix">
12 <label for="body">Body</label>
13 <div class="input">
14 <textarea class="xxlarge" id="body" name="note.body" rows="3"></textarea>
15 </div>
16 </div>
17
18 <div>
19 <button ng:click="save()" class="btn primary">Save</button> <button ng:click="cancel()" class="btn">Cancel</button>
20 </div>
21
22</form>
Pretty simple, right? For the heck of it, I ported this over into a PhoneGap application, and it worked well. The UI is a bit small, but I was able to add, edit, delete notes just fine on my Xoom table. I've included a zip file of the core code for the application and the APK as well if you want to try it. You may demo the app yourself below, and note, it makes use of console for debugging, so don't run it unless you've got a proper browser.

(Also you probably shouldn't use delete as a key for an object, or as a method name. delete is a keyword in JS and some browsers won't allow you to use it like that).
For those interested I converted a standard JS application (Kevin's HTML5 Quiz) to Angularjs in a couple of hours. Link: http://www.webappstogo.com/html5quiz
Some pointers:
- Use ng:href in your templates for links that have bindings
- Rather than using a function to change browser location you could just use an href with the path, for example to edit: ng:href="#/notes/edit/{{note.id}}";
- If you do want to set location in your controller use: $location.path('/notes/edit' + self.noteId);
- you can access the $routeParams in your controller and use that to load a note using something like this
self.noteid = $routeParams.noteId as alternative to self.params.hasOwnProperty("noteId")
https://github.com/addyosmani/todomvc/tree/master/...
Angular was the best fit for me. The community is still small compared to backbone but core developers (they work for google) are very helpful and answer questions on the forums promptly.
For angular I'd recommend using latest release (unstable - 10.x) available here:
https://github.com/angular/angular.js/tags
Make sure to consult the latest docs here:
http://docs-next.angularjs.org/api
http://www.raymondcamden.com/index.cfm/2012/5/10/S...
What is the licence for your code ? Mit ? LGPL ?
Can we use your code ?
BUT - please note this blog post is very old. I know it's just one year, but Angular has changed a lot since then. (In fact, I kinda don't like it anymore. No offense to the Angular folks, but I'm just not happy with it's current form.)
Have you moved to another framework ? If so which one ?
Thanks again
The framework I'm most interested in now (but I've yet to build a POC in it it) is Knockout.
I've been looking at Angular.js the last few days. Looks v intriguing. The 3 finalists were Angular, Knockout and Ember. The latter looks too complex, and I was heading into Knockout territory until I took a closer look at Angular.
I found a good resource for knockout called Knockout.js Succinctly, helped me wrap my head around it better than the website. You can download it at:
http://www.syncfusion.com/resources/techportal/ebo...
Are you currently still working with Knockout or Angular. If the latter, did you get to the point of using a cfc to feed the view or post data to the db?
I've also been learning Coffeescript, quite like the syntax. Have you checked that out?
Best,
Pardeep.
Coffeescript: I don't care for it. I'm no JS expert, but I find it easy enough to write that a 'meta language' on top just feels wrong. The syntax feels weird to me too. Now that being said, I can say I like TypeScript quite a bit as it feels like a "slightly extended JavaScipt".
Unhandled exception at line 3, column 1 in http://localhost:36184/app/js/services.js
0x800a01b6 - JavaScript runtime error: Object doesn't support property or method 'service'
Have you seen this before?
I am just learning this too, How did you figure out how to use local storage?
For a first app it you seem to have a pretty good handle on AngularJS. Great job!
Thanks. However, I don't really use Angular anymore. I'm not a fan with how it evolved.
"How did you figure out how to use local storage? "
LocalStorage is one of the easiest, and most practical, of the HTML5 features out there. I love it. If you search my blog you will find many examples of it. And here is the MDN doc on it:
https://developer.mozilla.org/en-US/docs/DOM/Stora...
I'm still trying out Angular, Knockout and Ember. Found a lesser known but quite elegant framework called serenade.js. V diff less verbose approach.
Knockout is the one I'm most interested in now, but I haven't had a chance to build a demo with it yet.
It was not showing any error also.
When i tried to execute this application, only I found is a BLANK PAGE.
Could you please help me with this??
I did get a chance to play with Knockout finally and - I dig it. :)
https://github.com/kevinpet/angular-phonecat-coffe...
and I still haven't managed to understand quite some stuff in the code that I could not find in any other projects but yours.
As I read in the previous comments, this may be because the code is old and Angular evolved (and they kept the compatibility, but without documenting it).
The biggest thing I wonder about is where does the Note_ variable comes from ? I can't see anywhere in the doc written that the 'Note' service will be accessible by giving it to the NodeListCtrl by adding an underscore ... but so it seems.
Besides, I want to change mycode to a module structure, and whenever I write
app = angular.module('phonecat', ['phonecat.filters', 'phonecat.services'])
I get this error :
Uncaught TypeError: Object #<Object> has no method 'module'
which I don't understand since I have seen it everywhere in any other project.
A great thanks in advance, thanks for the tutorial, it has already helped me a lot.
That said, it really depends on the type of application you are building - if it's smaller app and your just looking for the data-binding KnockoutJS is simple and easy to use. If your building a full fledge application, you'll have to provide 3rd party routers, dynamic template html loading, etc... then I'd lean toward AngularJS.
EmberJS for me is nice - last time I checked though Ember-Data was pre-alpha - maybe when it's matured it would be a good candidate. Also, it had some performance issues that scared me away