AngularJS Part 2 - Getting started with Controllers
This post is part of a series of posts on AngularJS, and here are the parts so far:
- AngularJS Part 1 - Getting started with the basics
- AngularJS Part 2 - Getting started with Controllers
- AngularJS Part 3 - Routes and Views
- AngularJS Part 4 - The Factory
In AngularJS - Getting started with the basics we simply bootstrapped an AngularJS app with a simple module, and a TextBox bound to a scope-value. In this post we'll continue developing on our application and get some routing and controllers up and running
Modules revisited
Before we begin I want to make a note on modules. There are an abundance of examples on AngularJS out there, and alot of them have decided to define modules to stick controllers, directives, services etc in. The code ends up looking like this:
angular.module('controllers', []);
angular.module('app', ['controllers']);
// and in some controller-file:
angular.module('controllers').controller ...
While this looks like a good idea and everyone loves modules, I have a hard time seeing the benefit of this, other than some sort of readability. I've done this myself in a few projects, but have still to see what purpose this approach serve.
AngularJS is already pretty descriptive in its syntax, so angular.module('controllers').controller
really just tells us the same thing twice. The way I see it is that a module should define a boundary for something that is totally isolated and the entire module can be pulled out, and put into a different AngularJS-project. One could argue that this makes the requirement for modules very limited, but we'll explore this more later on. For now, I'll stick stuff on my 'app'-module until that seems messy.
Controllers
Where modules are what defines our application, controllers define the functionality of our application. Controllers are responsible for turning data into something that our views can use.
Let's just jump right in and learn by example. We'll add a new subfolder to our app, and add a file within it called Home.js. Our structure should look like this now:
/
/app/app.js
/app/Home/Home.js
/scripts/angular.js
/views/main/index.cshtml
Home.js will be where our Home-controller will recide, and the syntax for defining a controller looks like this:
angular.module('app').controller('Home', ['$scope', function($scope){
}]);
There are a few things going on here. First we get a reference to our app-module (remember in the previous post, I mentioned the importance of the square-brackets in the module-definition?). The module exposes a function called controller, which creates our controller. The first argument is the name of our controller which we'll reference in a minute, and the next is a bit of AngularJS syntax-weirdness. The array we provide as the second argument is actually not required. We can skip the array, and just pass in function($scope) {}
without the angular-brackets and the '$scope'
-bit. This is also used frequently in examples all over the place, but it's really a bad practice, and to be honest, a bit weird that it's at all possible. Why?
Dependency injection - that's why
Without the brackets, AngularJS will try to resolve all the arguments passed in to the function, and at first glance this will work just fine - $scope is familiar to AngularJS, so it will know how to do this. The problem arises when you run some sort of minification over your Javascript-files. The result will be something like angular.module('app').controller('Home', function(a){})
. Now AngularJS will have no idea how to resolve a
, and your app will crash. With the square-bracket syntax we explicitly tell AngularJS how to resolve the arguments in the function. The number of arguments must match, and the order is critical. Don't worry, we'll cover lots more DI in AngularJS later on. For now, let's stop here.
Binding our controller to our view
In order for our view to utilize our controller we can simply use the ng-controller
directive provided by AngularJS. Let's try that, and see if we can display some data in our scope. Your <body>
should already contain the following code (if you followed the first post):
Make sure to add a reference to angular.js and app.js (in that order) in your HTML if you're not using some bundling-mechanism. All the other .js-files we create should be referenced after app.js, but the order of those are not important.
<input type='text' ng-model='text'>
{{text}}
Let's expand it by wrapping it in a <div>
, and add the ng-controller
directive:
<div ng-controller='Home'>
<input type='text' ng-model='text'>
{{text}}
</div>
As you see, the value of the ng-controller='Home'
directive reflects the name we provided in our Home.js file.
If you try to browse the page now, the behaviour will be identical to how it was. Let's try to change that.
$scope
When you work with AngularJS, you'll quickly get familiar with the $scope
keyword (AngularJS prefixes all their internal keywords with a $-sign to avoid collisions with our custom names). The $scope
is what connects our markup and the data in our controller. Everything we hook on the $scope-variable will be available for binding in our markup. What actually happened in our 'controller-less' scenario before we added Home.js is that AngularJS will create a $scope for us behind the scenes, and the ng-model='text'
actually creates the property text
on that scope. Since we've wrapped our code with the ng-controller
that property will now be created on the $scope
in our controller. Let's add a button we can click just to verify that this is true:
index.cshtml:
<div ng-controller="Home">
<input type='text' ng-model='text'>
{{text}}
<button ng-click="showTextValue()">Show the value of $scope.text</button>
</div>
Home.js:
angular.module('app').controller('Home',['$scope', function($scope) {
$scope.showTextValue = function() {
alert('The value of $scope.text is: ' + $scope.text);
};
}]
);
As you can see we've added a ng-click='showTextValue()'
on our <button>
. This will attempt to execute the code on our $scope
in the controller. Also worth noting is that we never actualy define $scope.text
, but simply expect it to exist. Try to run the page, and you'll see the alert showing whatever you've typed into the TextBox when you click the button.
This is excellent Demo-code, but I'm still not happy about Demo-code. To make it slightly less demo-code, I'd define the $scope.text
property within my controller so that it's explicit for whomever reads my code that text
is something that will be used by my view:
angular.module('app').controller('Home',['$scope', function($scope) {
$scope.text = 'Some initial value';
$scope.showTextValue = function() {
alert('The value of $scope.text is: ' + $scope.text);
};
}]
);
Conclusion
We haven't created the next big thing quite yet, but we're moving slowly along to get a hold of the basic concepts of AngularJS. As the series moves forward we'll expand on the stuff we talked about so far.
[Complete code for this post on Github.com](https://github.com/yngvebn/blog-angular/tree/Part2)