What is AngularJS?
Angular is a client-side MVC/MVVM framework built in JavaScript, essential for modern single page web applications (and even websites). This post is a full end to end crash course from my experiences, advice and best practices I’ve picked up from using it.Angular has an initial short learning curve, you’ll find it’s up and down after mastering the basics. It’s mainly getting to grips with the terminology and “thinking MVC”. MVC stands for Model-View-Controller. Here are the higher level and essential APIs that Angular comes with, and some terminology. Terminology
You’ve probably heard of MVC, used in many programming languages as a means of structuring/architecting applications/software. Here’s a quick breakdown of meanings: MVC
- Model: the data structure behind a specific piece of the application, usually ported in JSON. Read up on JSON before getting started with Angular as it’s essential for communicating with your server and view. For instance a group of User IDs could have the following model:
{
"users" : [{
"name": "Joe Bloggs",
"id": "82047392"
},{
"name": "John Doe",
"id": "65198013"
}]
}
- View: The view is simple, it’s your HTML and/or rendered output. Using an MVC framework, you’ll pull down Model data which updates your View and displays the relevant data in your HTML.
- Controller: Do what they say on the tin, they control things. But what things? Data. Controllers are your direct access from the server to the view, the middle man, so you can update data on the fly via comms with the server and the client.
First, we need to actually setup the essentials to an Angular project. There are certain things to note before we begin, which generally consist of an ng-app declaration to define your app, a Controller to talk to your view, and some DOM binding and inclusion of Angular. Here are the bare essentials: Setting up an AngularJS project (bare essentials)
Some HTML with ng-* declarations:
<div ng-app="myApp">
<div ng-controller="MainCtrl">
<!-- controller logic -->
</div>
</div>
var myApp = angular.module('myApp', []);
myApp.controller('MainCtrl', ['$scope', function ($scope) {
// Controller magic
}]);
angular.module('myApp', [])
.controller('MainCtrl', ['$scope', function ($scope) {...}])
.controller('NavCtrl', ['$scope', function ($scope) {...}])
.controller('UserCtrl', ['$scope', function ($scope) {...}]);
var myApp = angular.module('myApp', []);
myApp.controller('MainCtrl', ['$scope', function ($scope) {...}]);
myApp.controller('NavCtrl', ['$scope', function ($scope) {...}]);
myApp.controller('UserCtrl', ['$scope', function ($scope) {...}]);
Now you’ve grasped the concept of MVC and a basic setup, let’s check out Angular’s implementation on how you can get going with Controllers. Controllers
Taking the example from above, we can take a baby step into pushing some data into the DOM from a controller. Angular uses a templating-style {{ handlebars }} syntax for talking to your HTML. Your HTML should (ideally) contain no physical text or hard coded values to make the most of Angular. Here’s an example of pushing a simple String into the DOM:
<div ng-app="myApp">
<div ng-controller="MainCtrl">
{{ text }}
</div>
</div>
var myApp = angular.module('myApp', []);
myApp.controller('MainCtrl', ['$scope', function ($scope) {
$scope.text = 'Hello, Angular fanatic.';
}]);
The key rule concept here is the $scope concept, which you’ll tie to all your functions inside a specific controller. The $scope refers to the current element/area in the DOM (no, not the same as this), and encapsulates a very clever scoping capability that keeps data and logic completely scoped inside elements. It brings JavaScript public/private scoping to the DOM, which is fantastic.
The $scope concept may seem scary at first, but it’s your way into the DOM from the server (and static data if you have that too)! The demo gives you a basic idea of how you can ‘push’ data to the DOM.
Let’s look at a more representative data structure that we’ve hypothetically retrieved from the server to display a user’s login details. For now I’ll use static data; I’ll show you how to fetch dynamic JSON data later.
First we’ll setup the JavaScript:
var myApp = angular.module('myApp', []);
myApp.controller('UserCtrl', ['$scope', function ($scope) {
// Let's namespace the user details
// Also great for DOM visual aids too
$scope.user = {};
$scope.user.details = {
"username": "Todd Motto",
"id": "89101112"
};
}]);
<div ng-app="myApp">
<div ng-controller="UserCtrl">
<p class="username">Welcome, {{ user.details.username }}</p>
<p class="id">User ID: {{ user.details.id }}</p>
</div>
</div>
It’s important to remember that Controllers are for data only, and creating functions (event functions too!) that talk to the server and push/pull JSON data. No DOM manipulation should be done here, so put your jQuery toolkit away. Directives are for DOM manipulation, and that’s up next.
Protip: throughout the Angular documentation (at the time of writing this) their examples show this usage to create Controllers:
var myApp = angular.module('myApp', []);
function MainCtrl ($scope) {
//...
};
A directive (checkout my post on Directives from existing scripts/plugins) in its simplest form is a small piece of templated HTML, preferably used multiple times throughout an application where needed. It’s an easy way to inject DOM into your application with no effort at all, or perform custom DOM interactions. Directives are not simple at all; there is an incredible learning curve to fully conquering them, but this next phase will let you hit the ground running. Directives
So what are directives useful for? A lot of things, including DOM components, for example tabs or navigation elements - really depends on what your app makes use of in the UI. Let me put it this way, if you’ve toyed with ng-show or ng-hide, those are directives (though they don’t inject DOM).
For this exercise, I’m going to keep it really simple and create a custom type of button (called customButton) that injects some markup that I hate to keep typing out. There are various ways to define Directives in the DOM, these could look like so:
<!-- 1: as an attribute declaration -->
<a custom-button>Click me</a>
<!-- 2: as a custom element -->
<custom-button>Click me</custom-button>
<!-- 3: as a class (used for old IE compat) -->
<a class="custom-button">Click me</a>
<!-- 4: as a comment (not good for this demo, however) -->
<!-- directive: custom-button -->
Now you know how to declare where Directives are used/injected, let’s create this custom button. Again, I’ll hook into my global namespace myApp for the application, this is a directive in its simplest form:
myApp.directive('customButton', function () {
return {
link: function (scope, element, attrs) {
// DOM manipulation/events here!
}
};
});
A directive simply returns itself via an Object and takes a number of parameters. The most important for me to master first are, restrict, replace, transclude, template and templateUrl, and of course the link property. Let’s add those others in:
myApp.directive('customButton', function () {
return {
restrict: 'A',
replace: true,
transclude: true,
template: '<a href="" class="myawesomebutton" ng-transclude>' +
'<i class="icon-ok-sign"></i>' +
'</a>',
link: function (scope, element, attrs) {
// DOM manipulation/events here!
}
};
});
Make sure you Inspect Element to see the additional markup injected. Yes, I know, there is no icon included as I never included Font Awesome, but you see how it works. Now for the Directive properties explanations:
- restrict: This goes back to usage, how do we restrict the element’s usage? If you’re using a project that needs legacy IE support, you’ll probably need attribute/class declarations. Restricting as ‘A’ means you restrict it as an Attribute. ‘E’ for Element, ‘C’ for Class and ‘M’ for Comment. These have a default as ‘EA’. Yes, you can restrict to multiple use cases.
- replace: This replaces the markup in the DOM that defines the directive, used in the example, you’ll notice how initial DOM is replaced with the Directive’s template.
- transclude: Put simply, using transclude allows for existing DOM content to be copied into the directive. You’ll see the words ‘Click me’ have ‘moved’ into the Directive once rendered.
- template: A template (as above) allows you to declare markup to be injected. It’s a good idea to use this for tiny pieces of HTML only. Injected templates are all compiled through Angular, which means you can declare the handlebar template tags in them too for binding.
- templateUrl: Similar to a template, but kept in its own file or <script> tag. You can do this to specify a template URL, which you’ll want to use for manageable chunks of HTML that require being kept in their own file, just specify the path and filename, preferably kept inside their own templates directory:
myApp.directive('customButton', function () {
return {
templateUrl: 'templates/customButton.html'
// directive stuff...
};
});
<!-- inside customButton.html -->
<a href="" class="myawesomebutton" ng-transclude>
<i class="icon-ok-sign"></i>
</a>
<script type="text/ng-template" id="customButton.html">
<a href="" class="myawesomebutton" ng-transclude>
<i class="icon-ok-sign"></i>
</a>
</script>
Services are often a confusing point. From experience and research, they’re more a stylistic design pattern rather than providing much functional difference. After digging into the Angular source, they look to run through the same compiler and they share a lot of functionality. From my research, you should use Services for singletons, and Factories for more complex functions such as Object Literals and more complicated use cases. Services
Here’s an example Service that multiples two numbers:
myApp.service('Math', function () {
this.multiply = function (x, y) {
return x * y;
};
});
myApp.controller('MainCtrl', ['$scope', function ($scope) {
var a = 12;
var b = 24;
// outputs 288
var result = Math.multiply(a, b);
}]);
When you create a Service (or Factory) you’ll need to use dependency injection to tell Angular it needs to grab hold of your new Service - otherwise you’ll get a compile error and your Controller will break. You may have noticed the function ($scope) part inside the Controller declaration by now, and this is simple dependency injection. Feed it the code! You’ll also notice [‘$scope’] before the function ($scope) too, I’ll come onto this later. Here’s how to use dependency injection to tell Angular you need your service:
// Pass in Math
myApp.controller('MainCtrl', ['$scope', 'Math', function ($scope, Math) {
var a = 12;
var b = 24;
// outputs 288
var result = Math.multiply(a, b);
}]);
Coming from Services to Factories should be simple now, we could create an Object Literal inside a Factory or simply provide some more in-depth methods: Factories
myApp.factory('Server', ['$http', function ($http) {
return {
get: function(url) {
return $http.get(url);
},
post: function(url) {
return $http.post(url);
},
};
}]);
myApp.controller('MainCtrl', ['$scope', 'Server', function ($scope, Server) {
var jsonGet = '//myserver/getURL';
var jsonPost = '//myserver/postURL';
Server.get(jsonGet);
Server.post(jsonPost);
}]);
Filters are used in conjunction with arrays of data and also outside of loops. If you’re looping through data and want to filter out specific things, you’re in the right place, you can also use Filters for filtering what a user types inside an <input> field for example. There are a few ways to use Filters, inside Controllers or as a defined method. Here’s the method usage, which you can use globally: Filters
myApp.filter('reverse', function () {
return function (input, uppercase) {
var out = '';
for (var i = 0; i < input.length; i++) {
out = input.charAt(i) + out;
}
if (uppercase) {
out = out.toUpperCase();
}
return out;
}
});
// Controller included to supply data
myApp.controller('MainCtrl', ['$scope', function ($scope) {
$scope.greeting = 'Todd Motto';
}]);
<div ng-app="myApp">
<div ng-controller="MainCtrl">
<p>No filter: {{ greeting }}</p>
<p>Reverse: {{ greeting | reverse }}</p>
</div>
</div>
You can see that we passed the greeting data to a filter via the pipe ( | ) character, applying the reverse filter to the greeting data. |
<ul>
<li ng-repeat="number in myNumbers |filter:oddNumbers">{{ number }}</li>
</ul>
myApp.controller('MainCtrl', ['$scope', function ($scope) {
$scope.numbers = [10, 25, 35, 45, 60, 80, 100];
$scope.lowerBound = 42;
// Does the Filters
$scope.greaterThanNum = function (item) {
return item > $scope.lowerBound;
};
}]);
<li ng-repeat="number in numbers | filter:greaterThanNum">
{{ number }}
</li>
That’s the main bulk behind AngularJS and its APIs, this is only diving into the shallow end of the waters, but is more than enough to get you building your own Angular application.
When I first heard about two-way data-binding, I wasn’t really sure what it was. Two-way data-binding is best described as a full-circle of synchronised data: update the Model and it updates the View, update the View and it updates the Model. This means that data is kept in sync without making any fuss. If I bind an ng-model to an <input> and start typing, this creates (or updates an existing) model at the same time. Two-way data-binding
Here I create the <input> and bind a Model called ‘myModel’, I can then use the curly handlebars syntax to reflect this model and its updates in the view all at once:
<div ng-app="myApp">
<div ng-controller="MainCtrl">
<input type="text" ng-model="myModel" placeholder="Start typing..." />
<p>My model data: {{ myModel }}</p>
</div>
</div>
myApp.controller('MainCtrl', ['$scope', function ($scope) {
// Capture the model data
// and/or initialise it with an existing string
$scope.myModel = '';
}]);
You’ve got the idea when it comes to pushing basic data to the $scope now, and a rough idea of how the models and two-way data-binding works, so now it’s time to emulate some real XHR calls to a server. For websites, this isn’t essential unless you have specific Ajax requirements, this is mainly focused on grabbing data for a web application. XHR/Ajax/$http calls and binding JSON
When you’re developing locally, you’re possibly using something like Java, ASP .NET, PHP or something else to run a local server. Whether you’re contacting a local database or actually using the server as an API to communicate to another resource, this is much the same setup.
Enter ‘dollar http’. Your best friend from now on. The $http method is a nice Angular wrapper for accessing data from the server, and so easy to use you could do it blindfolded. Here’s a simple example of a ‘GET’ request, which (you guessed it) gets data from the server. Its syntax is very jQuery-like so transitioning is a breeze:
myApp.controller('MainCtrl', ['$scope', '$http', function ($scope, $http) {
$http({
method: 'GET',
url: '//localhost:9000/someUrl'
});
}]);
myApp.controller('MainCtrl', ['$scope', function ($scope) {
$http({
method: 'GET',
url: '//localhost:9000/someUrl'
})
.success(function (data, status, headers, config) {
// successful data retrieval
})
.error(function (data, status, headers, config) {
// something went wrong :(
});
}]);
Ideally, we should setup and design our JSON first, which will affect how we bind our data. Let’s keep it simple, this is what a backend guy will setup as an API feed down to your application, you’ll be expecting the following:
{
"user": {
"name": "Todd Motto",
"id": "80138731"
}
}
The JavaScript (check inline annotations for what’s going on here):
myApp.controller('UserCtrl', ['$scope', '$http', function ($scope, $http) {
// create a user Object
$scope.user = {};
// Initiate a model as an empty string
$scope.user.username = '';
// We want to make a call and get
// the person's username
$http({
method: 'GET',
url: '//localhost:9000/someUrlForGettingUsername'
})
.success(function (data, status, headers, config) {
// See here, we are now assigning this username
// to our existing model!
$scope.user.username = data.user.name;
})
.error(function (data, status, headers, config) {
// something went wrong :(
});
}]);
<div ng-controller="UserCtrl">
<p>{{ user.username }}</p>
</div>
Angular’s philosophy is creating dynamic HTML that’s rich in functionality and does a hell of a lot of work seamlessly that you would never expect on the client-side of the web. This is exactly what they’ve delivered. Declarative data-binding
Let’s imagine we’ve just made an Ajax request to get a list of emails and their Subject line, date they were sent and want to render them in the DOM. This is where jaws drop at the power of Angular. First I’ll need to setup a Controller for Emails:
myApp.controller('EmailsCtrl', ['$scope', function ($scope) {
// create a emails Object
$scope.emails = {};
// pretend data we just got back from the server
// this is an ARRAY of OBJECTS
$scope.emails.messages = [{
"from": "Steve Jobs",
"subject": "I think I'm holding my phone wrong :/",
"sent": "2013-10-01T08:05:59Z"
},{
"from": "Ellie Goulding",
"subject": "I've got Starry Eyes, lulz",
"sent": "2013-09-21T19:45:00Z"
},{
"from": "Michael Stipe",
"subject": "Everybody hurts, sometimes.",
"sent": "2013-09-12T11:38:30Z"
},{
"from": "Jeremy Clarkson",
"subject": "Think I've found the best car... In the world",
"sent": "2013-09-03T13:15:11Z"
}];
}]);
<ul>
<li ng-repeat="message in emails.messages">
<p>From: {{ message.from }}</p>
<p>Subject: {{ message.subject }}</p>
<p>{{ message.sent | date:'MMM d, y h:mm:ss a' }}</p>
</li>
</ul>
I’ve also snuck in a date filter in there too so you can see how to render UTC dates.
Dig into Angular’s suite of ng-* directives to unleash the full power of declarative bindings, this shows you how to join the dots from server to Model to View and render the data.
As a continuation from declarative-binding, scope functions are the next level up in creating an application with some functionality. Here’s a basic function to delete one of our emails in our data: Scope functions
myApp.controller('MainCtrl', ['$scope', function ($scope) {
$scope.deleteEmail = function (index) {
$scope.emails.messages.splice(index, 1)
};
}]);
Binding functions to the scope are also run through ng-* Directives, this time it’s an ng-click Directive:
<a ng-click="deleteEmail($index)">Delete email</a>
Output (delete some emails!):
Now we’ll move onto DOM Methods, these are also Directives and simulate functionality in the DOM which you’d normally end up writing even more script logic for. A great example of this would be a simple toggling navigation. Using ng-show and a simple ng-click setup, we can create a flawless toggling nav: Declarative DOM methods
<a href="" ng-click="toggle = !toggle">Toggle nav</a>
<ul ng-show="toggle">
<li>Link 1</li>
<li>Link 2</li>
<li>Link 3</li>
</ul>
Output (get toggling!):
One of my favourite parts of Angular, what you’d usually use JavaScript for and write a lot of repetitive code. Expressions
Have you ever done this?
elem.onclick = function (data) {
if (data.length === 0) {
otherElem.innerHTML = 'No data';
} else {
otherElem.innerHTML = 'My data';
}
};
<p>{{ data.length > 0 && 'My data' || 'No data' }}</p>
Output:
The philosophy behind single-page web applications (and also websites!). You have a header, footer, sidebar, and the content in the middle magically injects new content based on your URL. Dynamic views and routing
Angular makes this setup a breeze to configure what we’d call dynamic views. Dynamic views inject specific Views based on the URL, through the $routeProvider. A simple setup:
myApp.config(['$routeProvider', function ($routeProvider) {
/**
* $routeProvider
*/
$routeProvider
.when('/', {
templateUrl: 'views/main.html'
})
.otherwise({
redirectTo: '/'
});
}]);
myApp.config(['$routeProvider', function ($routeProvider) {
/**
* $routeProvider
*/
$routeProvider
.when('/', {
templateUrl: 'views/main.html'
})
.when('/emails', {
templateUrl: 'views/emails.html'
})
.otherwise({
redirectTo: '/'
});
}]);
There is a lot more to the $routeProvider service which is well worth reading up on, but this’ll get the ball rolling for you. There are things such as $http interceptors that’ll fire events when an Ajax call is in progress, things we could show some spinners for whilst we load in fresh data.
Gmail handles a lot of its initial data by writing the JSON into the page (right-click View Source). If you want to instantly set data in your page, it’ll quicken up rendering time and Angular will hit the ground running. Global static data
When I develop our apps, Java tags are placed in the DOM and when rendered, the data is sent down from the backend. [I’ve zero experience with Java so you’ll get a made up declaration from me below, you could use any language though on the server.] Here’s how to write JSON into your page and then pass it into a Controller for immediate binding use:
<!-- inside index.html (bottom of page ofc) -->
<script>
window.globalData = {};
globalData.emails = <javaTagHereToGenerateMessages>;
</script>
myApp.controller('EmailsCtrl', ['$scope', function ($scope) {
$scope.emails = {};
// Assign the initial data!
$scope.emails.messages = globalData.emails;
}]);
I’ll talk a little about minifying your Angular code. You’ll probably have experimented a little at this point and perhaps ran your code through a minifier - and perhaps encountered an error! Minification
Minifying your AngularJS code is simple, you need to specify your dependency injected content in an array before the function:
myApp.controller('MainCtrl',
['$scope', 'Dependency', 'Service', 'Factory',
function ($scope, Dependency, Service, Factory) {
// code
}]);
myApp.controller('MainCtrl',
['$scope', 'Dependency', 'Service', 'Factory',
function (a,b,c,d) {
// a = $scope
// b = Dependency
// c = Service
// d = Factory
// $scope alias usage
a.someFunction = function () {...};
}]);
Scope comments I think are a really nice addition to my workflow, instead of declaring chunks of my HTML with comments like this: Scope comments
<!-- header -->
<header>
Stuff.
</header>
<!-- /header -->
<!-- scope: MainCtrl -->
<div class="content" ng-controller="MainCtrl">
</div>
<!-- /scope: MainCtrl -->
No comments:
Post a Comment