Controllers, Directives, and Services. AngularJS 101

So you want to get into AngularJS. You’ve probably heard about it being a great framework or platform and that sounds like something you should be using. You might have seen a few great demos of data binding and wanted to incorporate that into your project. Then you take a look at the docs… you get lost. You read some things online and now your learning about dependency injection, model view controller, declarative programming…  maybe even lambda calculus. All you wanted to do was bind a variable into your page. Let me run you through the core concepts you should understand – you’ll be able to run with them and start developing with AngularJS right away.

So where do we start? Well we need some HTML. So let’s open up your favorite code editor and markup some HTML. I use Sublime Text. Here’s what you should type up.

<html>
    <head>
        <title>My First Angular App!</title>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
    </head>
    <body>
    </body>
</html>

So what did we do here? Well for the most part its just some standard HTML markup. I also added a script tag that includes the angularjs file directly from a Google CDN. Why download it? If you save this file and load it in a browser, you won’t see anything, but you should be able to open up the developer console and type angular. This will return an object and you shouldn’t see any errors.

type-angular-no-errors

Ok great, we’ve setup AngularJS on our webpage and we’re ready to start writing some code! Let’s start with a controller.

Controllers

So what is a Controller? A Controller is an object that’s responsible for managing other objects. What that means is, the Controller doesn’t actually know the specifics about an object, like a Book for example. The Controller doesn’t know how many pages a Book has, who its author is or what its called, but it knows how to get a Book, how to ask it for its name or how to pass it to a View so it can be read. If you’re just getting started with AngularJS and Model-View-Controller architecture in general just remember that if your application has an object like a Book, chances are it will have a BooksController.

So for this simple application I want to ultimately display some weather for a specific city. So right away I know I am going to need to create a WeatherController to manage the Weather we request! Let’s do that now.

Go ahead and add another file to your application. Name this file app.js and update the markup in the html file you created earlier to include it:

<html>
    <head>
        <title>My First Angular App!</title>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
        <script type="text/javascript" src="app.js"></script>
    </head>
    <body>
    </body>
</html>

Great! Before we make our Controller we are going to have to make a top level module for it to live in. AngularJS projects are built around modules, so we will make one next. Open app.js and add the following code:

angular.module('weatherApp', []);

Save and refresh your html page. Take a look at the developer console. You shouldn’t have any errors and you should be able to retrieve your angular app by typing the following:

get-angular-module

Did you see the subtle difference there in Syntax? When you create a module, you add the square brackets [ ] but when you retrieve one, you don’t. This can trip you up when you’re getting started with AngularJS. So be careful. What are the brackets for? For including other modules! For the Ruby developers, you can think of them as a require.

Ok, so now we are going to add our Controller (finally!). I’m going to just add it to app.js. In a production application you would likely use the return value from this module statement and have other files that had the code for your controllers, services, and directives, but I’m gonna keep it simple for now.

Here’s how you go ahead and add your first AngularJS Controller!

angular.module('weatherApp', [])
  .controller("weatherController", function() {
    console.log('weather controller created!');
  });

There’s another small thing to notice here – I took the semicolon off the first line and chained the controller declaration after the module declaration. I added a simple console.log statement to let us know that the controller has been loaded on the page. So let’s check that now.

Save and refresh your page. Nothing. That’s because we haven’t actually included that weatherApp module we created onto the page, to do that we need a directive!

Directives

Directives are simply custom html attributes that AngularJS knows what to do with. The browser will ignore them and Angular will use them. They are that simple. I know they are SO much more, but we are learning, and for the sake of getting started with Angular. That’s all you need to know for now. So let’s tell Angular to load our app. We do that by using a built-in directive named ng-app. Its common to put that on the HTML tag at the top of you page. Let’s do that now:

<html ng-app="weatherApp">
    <head>
        <title>My First Angular App!</title>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
        <script type="text/javascript" src="app.js"></script>
    </head>
    <body>
    </body>
</html>

So again, if you save and refresh your page. Nothing. Ok, here’s the deal. We have told Angular about our app and Angular has in fact loaded it. If you don’t believe me go ahead and change the ng-app declaration from: ng-app=”weatherApp” to ng-app=”weatherApp1″. When you save and refresh the page you will see a horrible error message (get used to it – that’s one of the things that drives me nuts about Angular is the horrible error messages). Change the ng-app back and make sure you aren’t getting any errors. Now you believe me that the app was loaded, but where was that console statement? Well, we didn’t create the controller. Let’s go ahead and do that now. With another built-in directive named ng-controller!

Add ng-controller to the body tag:

<html ng-app="weatherApp">
    <head>
        <title>My First Angular App!</title>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
        <script type="text/javascript" src="app.js"></script>
    </head>
    <body ng-controller="weatherController">
    </body>
</html>

Now if you save and refresh the page… you see the console message! Now that we have a controller there is a ton of cool stuff we can do. The first is to start using the scope and binding data to our html elements.

Data binding and scope

If you’ve done any jQuery before, you’ve probably written something like this:

$("p.description").text("Here is some text!");

This is simply finding a p tag with a class of description and binding the text Here is some text! inside it. It works, and its served us well. However, what can happen is this kind of code can be nested deep in a very large javascript application and the p tag, well it just has a class of description. We don’t know that this code is going to drive by and throw a value in it. It can be hard to trace back why some elements behave the way they do on a page. Imagine if this p tag didn’t even have a class of description. It was just a p tag! This is where declarative programming and data binding comes in.

In Angular we actually declare what we are binding to an element, it makes it much easier to understand what markup is going to be affected by our code and what the intention of the original developer was. So let’s go ahead and add a p tag to our webpage and write out a brief description of our app using Angular declarative programming:

<html ng-app="weatherApp">
    <head>
        <title>My First Angular App!</title>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
        <script type="text/javascript" src="app.js"></script>
    </head>
    <body ng-controller="weatherController">
        <p ng-bind="description"></p>
    </body>
</html>

You know the drill. Save and refresh. Nothing. Don’t hate me. The reason is because Angular looked in the current scope of the directive (in this case the ng-controller named weatherController) and found nothing. So, you got nothing. Let’s go into our controller and add the description variable to the scope now:

angular.module('weatherApp', [])
  .controller("weatherController", function($scope) {
    $scope.description = "a simple weather app";
  });

There are two changes to notice here. First, we added a parameter to the constructor function of our controller named $scope. Second, we used that parameter as an object and added our description variable onto it. We gave it a simple value of: a simple weather app. What was that $scope parameter we passed to our constructor function? Well it was basically a service that gave us access to the current scope. What’s a scope? Well for right now, a scope is basically anything inside the element our directive is declared on. So, we declared our controller on the body tag. That means whatever we put inside the body tag is in the current scope when we are working inside our weatherController. Don’t believe me? Try this: add a div tag inside the body. Move the ng-controller declaration onto that div tag. Finally, move the p tag with our description outside the div, like this:

<html ng-app="weatherApp">
    <head>
        <title>My First Angular App!</title>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
        <script type="text/javascript" src="app.js"></script>
    </head>
    <body>
        <p ng-bind="description"></p>
        <div ng-controller="weatherController"></div>
    </body>
</html>

Here we go again… save and refresh. Nothing. That’s because our p tag is outside the scope of the controller now. I hope that’s becoming more clear. Basically if you want to bind something to the page then it needs to be on the $scope object. The $scope object is relevant to everything inside the element that you declared the directive on. In case you are wondering if you can have nested scopes. Yes. You can. So now that we have used a couple built-in directives, lets create our own. A custom directive!

Custom Directives

Why would we want to build our own Directive? Well, suppose we have a lot more functionality inside our weatherController. Suppose it has a few Controllers and a Service or two. We might find it useful to re-use that weather on multiple pages. We don’t want to markup all the necessary elements every time on every page, so we use a directive. Adding a directive let’s us use templates so we can define the markup we need for weather once in its own file. Adding a directive also allows us to make changes to our code in one place and everywhere the directive is used is updated with the new functionality. Directives are really powerful and one of the best parts of Angular.

Adding a Directive to our app.js is very similar to adding a Controller. Here’s what app.js looks like now:

angular.module('weatherApp', [])
  .controller("weatherController", function($scope) {
    $scope.description = "a simple weather app";
  })
  .directive("weather", function() {
    return {
      restrict: "A",
      templateUrl: "weather.html"
    }
  });

This one is a little bit different than the controller in that it returns an object. That object has a bunch of parameters necessary for the directive to function. I’ve kept it simple and only added the bare minimum. First, I set the restrict property to “A”. This means the directive will function as an attribute of an element. This means I will put it on HTML elements just like ng-app or ng-controller. Second, I set templateUrl to a file named weather.html. This is the file that will hold the markup we used to have sitting in our body tag. Lets create that file next. Add the following code to weather.html:

<div ng-controller="weatherController">
 <p ng-bind="description"></p>
</div>

I’ve created a div tag here to declare the controller on. You could also have added the controller property to our directive and omitted this all together. Other than that, this file is pretty simple. Finally, let’s update our original HTML file to use our new directive:

<html ng-app="weatherApp">
    <head>
        <title>My First Angular App!</title>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
        <script type="text/javascript" src="app.js"></script>
    </head>
    <body>
        <div weather></div>
    </body>
</html>

I love how simple that is! Basically anywhere I want to add my weather app, I just add that directive now.

Now before you run this, let me tell you what sucks. If you try to refresh this in Chrome you will get a huge ugly Cross Origin error in your console, like this:

cross-origin-error

Unfortunately, Chrome won’t let you reference local files. You can go ahead and work from Safari or you can run the following command inside your terminal window to open Chrome with this security disabled:

open -a Google\ Chrome --args --disable-web-security -–allow-file-access-from-files

Once you open you app again in Safari, or using the above command, you should see the same message as before, but now using your own custom directive! The last thing we need to do is add a Service.

Services

So we created a controller to manage our application and give us access to the scope. We placed all that functionality inside a Directive to allow us to re-use it and template out our markup. Where do we make a call to get our actual Weather? If you are thinking you would simply add a function to our controller and place the result on the scope… you’re not being very Angular! What I mean is that would work. You could simply call a web service right inside your controller and get your weather data, but Angular gives us Services to extract the functionality of getting our objects, calling web services etc. Its the preferred place to put that sort of logic.

Why else should you use a service? First, Reuse! You will likely create a service that is helpful to not only this controller, but to other controllers in your application. Or even a Directive (yes directives can be passed services). Second, maintenance. This controller example is trivial, but your actual projects will create very large controllers without cluttering them with service calls and the logic required to get and set objects.

So how do we add a Service? Well, again its very similar to the other pieces we’ve added to our application:

angular.module('weatherApp', [])
  .service("weatherService", function() {
    this.getWeather = function(cityName) {
      return "30";
    };
  })
  .controller("weatherController", function($scope, weatherService) {
    $scope.description = "a simple weather app";
    $scope.temp = weatherService.getWeather("Vancouver");
  })
  .directive("weather", function() {
    return {
      restrict: "A",
      templateUrl: "weather.html"
    }
  });

There’s a few items to take note of here. One, our service declares its function on this. Because of the way constructor functions work, you need to declare your functions on this if you want them to be accessible. If you didn’t declare it on this you wouldn’t be able to call it in the Controller. Second, we added our service to the list of parameters in our controllers’ constructor function. Finally, we created a scope variable named temp and placed the value returned from our Service call. Now I know I didn’t actually make a Service call. That’s the subject for another blog post, or a challenge for the reader.

The final piece is to update our template file with the new scope variable we created:

<div ng-controller="weatherController">
  <p ng-bind="description"></p>
  <p ng-bind="temp"></p>
</div>

If you save and refresh you will see your simple description and you’re hard-coded temperature. This was a simple and not very functional example, but the point was to help you understand the building blocks of an AngularJS application. With Controllers, Services and Directives and a basic understanding of scopes and declarative programming, you are should now be able to start to put together your first real AngularJS application!

Comments

  1. Asaf says

    That was seriously lucid explanation of the very basics of AngularJS. Although I have successfully used Angular before, I can certainly say that the concepts didn’t quite fully pass through my throat, but your text here helped the final downing.
    To show how well your text is working, I will just say that I read it after 2 glasses of wine and 2 whiskies – and it still worked it’s introductory magic!
    Thanks.

  2. Mario says

    Excellent basic tutorial! Really cleared some things up for me.

    By the way, as a note for those trying to activate the command from Terminal, you need to make sure Chrome is shut down, run the command and then Chrome will open up and reference local files. I ran it and nothing appeared to happen, as I guess it’s trying to open a new instance of Chrome and failing.

    Put chrome://version/ into the address bar to view what flags have taken effect. The above seems to apply to all open tabs in that sessions but closing Chrome down and reopening it seems to remove the added flags.

Leave a Reply

Your email address will not be published. Required fields are marked *