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:
<!DOCTYPE html>
<html xmlns:ng="http://angularjs.org/">
<head>
<meta charset="utf-8">
<title>Note Application</title>
<link rel="stylesheet" href="http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css">
<link rel="stylesheet" href="css/main.css"/>
</head> <body ng:controller="NoteCtrl"> <div class="container"> <ng:view></ng:view> </div> <script src="lib/angular/angular.js" ng:autobind></script>
<script src="js/controllers.js"></script>
<script src="js/services.js"></script>
</body>
</html>
All of the real display will happen within the view, which is managed in my controller. Let's look at that file next.
function NoteCtrl($route) {
var self = this; $route.when('/notes',
{template: 'partials/note-list.html', controller: NoteListCtrl});
$route.when('/notes/add',
{template: 'partials/note-edit.html', controller: NoteEditCtrl});
$route.when('/notes/edit/:noteId',
{template: 'partials/note-edit.html', controller: NoteEditCtrl});
$route.when('/notes/:noteId',
{template: 'partials/note-detail.html', controller: NoteDetailCtrl});
$route.otherwise({redirectTo: '/notes'}); $route.onChange(function(){
self.params = $route.current.params;
}); $route.parent(this);
} function NoteListCtrl(Note_) {
var self = this;
self.orderProp = 'title';
self.notes = Note_.query(); self.delete = function(id) {
console.log("delete "+id);
Note_.delete(id);
self.notes = Note_.query();
//refreshes the view
self.$root.$eval();
} self.edit = function(id) {
window.location = "./index.html#/notes/edit/"+id;
}
} function NoteDetailCtrl(Note_) {
var self = this;
self.note = Note_.get(self.params.noteId); if(typeof self.note === "undefined") window.location = "./index.html";
} function NoteEditCtrl(Note_) {
console.log('EDIT CTRL');
var self = this; if(self.params.hasOwnProperty("noteId")) self.note = Note_.get(self.params.noteId);
else self.note = { title:"", body:""}; self.cancel = function() {
window.location = "./index.html";
} self.save = function() {
Note_.store(self.note);
window.location = "./index.html";
} }
/* App Controllers */
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.
/* http://docs.angularjs.org/#!angular.service */ angular.service('Note', function(){
return {
query:function() {
var notes = [];
for(var key in localStorage) {
if(key.indexOf("note_") == 0) {
notes.push(JSON.parse(localStorage[key]));
}
}
console.dir(notes);
return notes;
},
delete:function(i) {
localStorage.removeItem("note_"+i);
},
get:function(i) {
if(localStorage["note_"+i]) return JSON.parse(localStorage["note_"+i]);
console.log("no note for "+i);
},
store:function(note) {
if(!note.hasOwnProperty('id')) {
//yep, hack, get all notes and find highest id
var notes = this.query();
var highest = 0;
for(var i=0; i<notes.length; i++) {
if(notes[i].id > highest) highest=notes[i].id;
}
note.id = ++highest;
}
note.updated = new Date();
localStorage["note_"+note.id] = JSON.stringify(note);
}
} });
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.
Search: <input type="text" name="query"/>
Sort by:
<select name="orderProp">
<option value="title">Title</option>
<option value="-updated">Newest</option>
</select> <h2>Notes</h2> <table class="bordered-table">
<tr>
<th>Title</th>
<th>Updated</th>
<th> </th>
</tr>
<tr ng:repeat="note in notes.$filter(query).$orderBy(orderProp)">
<td><a href="#/notes/{{note.id}}">{{note.title}}</a></td>
<td>{{note.updated | date:'MM/dd/yyyy @ h:mma'}}</td>
<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>
</tr>
</table> <p ng:show="notes.length==0">
Sorry, there are no notes yet.
</p> <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.
{{note.body}}
<h1>{{note.title}}</h1>
<p>
<b>Last Updated:</b> {{note.updated | date:'MM/dd/yyyy @ h:mma'}}
</p>
And the edit one is also pretty trivial:
<h1>Edit Note</h1> <form class="form-stacked" onSubmit="return false" id="editForm">
<div class="clearfix">
<label for="title">Title:</label>
<div class="input">
<input type="text" id="title" name="note.title" class="xlarge">
</div>
</div> <div class="clearfix">
<label for="body">Body</label>
<div class="input">
<textarea class="xxlarge" id="body" name="note.body" rows="3"></textarea>
</div>
</div> <div>
<button ng:click="save()" class="btn primary">Save</button> <button ng:click="cancel()" class="btn">Cancel</button>
</div> </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.
Archived Comments
Awesome!
(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).
Good point Elliott. I didn't notice that until I put it into Android. Eclipse complained about it. I just deleted the errors though and it ran on my device.
I wonder if AngularJS has been used by any major company. There are so many javascript mvc frameworks, I'm confused which one to use. The two main ones that I hear a lot are backbone.js and JavaScript MVC.
Hopefully Elliott will chime in here. I'm not sure who out there is using it.
I convinced my company to go with Angularjs for a new project and so far so good. There is a bit of a learning curve but I think it is worth it. Anyway great tut!
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/...
Nice - and yes - I viewed source so I could cheat.
Thanks! I forgot to add I have the source on github -> http://bit.ly/rCVLXO
I've been using angular on a few projects and really like it. Apparently angular is used quite a lot internally at google.
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")
Thanks for the tips Johan. I've been trying to find time to build another Angular app - I just haven't had the time.
No worries Ray - given the diversity and amount of useful stuff you explore/publish I'll cut you a bit of slack on getting to your next angular post ;-)
@Dmitry - yes there seems to be a new JavaScript MVC framework each day. I tried a few and found the TODO MVC resource from Addy Osmani useful for comparision:
https://github.com/addyosma...
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/...
Make sure to consult the latest docs here:
http://docs-next.angularjs....
"Angular was the best fit for me." - And isn't that the most important thing? I know for me in ColdFusion, there were multiple frameworks I tried until I found the one that made me effective.
I tried to compile a angular js application into phonegap....But the angular.js directives {{ng:repeat}} does not seems to be picked up when I compile with ant debug..... But the rest of the html stuff seems to be ok...Is there any special rules that needs to be followed for compiling it with the the angular.js ? Any complete example of an android app which contains the index.html in assets/www ? Thanks for any pointer...
I'd try checking DDMS and the console output. Check my post here:
http://www.raymondcamden.co...
What icon set are you using in your test app Ray? For the edit and delete buttons?
famfamfam Silk Icons - http://www.famfamfam.com/la...
Thanks for that wonderfull tuto.
What is the licence for your code ? Mit ? LGPL ?
Can we use your code ?
In general, my code is Apache licensed, and practically, it is: Free to use, give me credit in the source, and visit the Amazon Wish List if you can.
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.)
What bothers you in the current version of angular ?
Have you moved to another framework ? If so which one ?
Thanks again
It's hard to really say. I feel like how Angular does stuff now in 1.0 is just... overly complex and weird. I accept that it is a completely "gut feel" type reaction, but when I tried Angular 1.0, I had none of the feelings I did when I tried the earlier editions.
The framework I'm most interested in now (but I've yet to build a POC in it it) is Knockout.
Hi Ray,
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/r...
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.
"Are you currently still working with Knockout or Angular." As I said above - I'm not using Angular, I want to use Knockout, but i haven't had a chance to yet.
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".
When I try to run this app, I get an error:
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?
Sorry, I don't know. I haven't run this in a long time. Does the online demo do the same?
Figures, after I posted this, I replaced the Partial files and it worked, must of been a type-o somewhere
This is a great app for your first application in AngularJS.
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!
"This is a great app for your first application in AngularJS."
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.o...
What didn't you like about how Angular evolved?
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.
It isn't a very scientific thing - just a gut feeling. It seems like it's gotten overly complex and it isn't as friendly as when I first saw it. This is in no way meant to be an attack on them. I'm sure it is still a good framework. It just doesn't feel right for me.
Knockout is the one I'm most interested in now, but I haven't had a chance to build a demo with it yet.
I downloaded the files of your project & tried to run this application on Eclipse(helios) but it didn't work.
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??
Your comment about running it in Eclipse doesn't make sense. Eclipse is your editor. You should be running it in the browser. If you get a blank page, then open your browser's dev tools to see if an error has shown up.
Interested to see you are going from angular to knockout.In my case i started with knockout and endup with angularjs. knockout support only binding. AngularJS is more than that it is framework whereas knockout is api. knockout has to work with other JS framework but Angular has everthing built in.
Knockout may support less, but that doesn't necessarily make it better for me. :) Frameworks are - imo - a very personal thing. They either work well for you and your team or they don't. To me, Angular just isn't working for me. As I've said now multiple times, this is no reflection on the product itself, but just my personal feelings about it. :)
I did get a chance to play with Knockout finally and - I dig it. :)
I have been working with this project to learn angular :
https://github.com/kevinpet...
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.
Augustin, I really can't help anymore with this. I've not used Angular in so long that I simply do not remember how my old app here works. I wouldn't try to make it work with modern Angular either. As you said, it may work for backwards compat reasons, but it would seem (imo) to be a bad idea to try to use 'old' style Angular on a new project.
KnockoutJS is good but doesn't contain what you need to build any large SPA. (You could look at Durandal) - this is where AngularJS shines - it's routing, view composition, etc... is baked right in from the start.
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
I have a simple angulajs application and I can run it on web browser.
How can I create apk file for this aaplication so that I can install it on an android device.
Is there any changes needed in my web application for this?
Please see the PhoneGap or Cordova docs for creating APKs.
The link "Download attached file." doesn't work? Could you please provide it?
I fixed the link, but I'd urge you away from using my code as an example. This post is almost four years old and doesn't represent a modern Angular app.
i went through you code, i am busy with angular now...yes it might be four years old but trust me..it helped me a lot.
thanks