AngularJS 1.x Fundamentals (Part 2)

Nyambati Thomas

In the previous article in this series, we looked into AngularJs concepts and features. We looked into what AngularJs is and its features that make it standout. In this article, we will take a deep look into some of these features and how they can be used to build awesome and robust web applications.

Before we proceed I would like to clarify that any reference to Angular in this article will be for Angular version 1.x. So let's not get it confused with Angular2, even though some of the concepts are similar they do differ in many ways.

The features we will be looking into in this article include;

  • Modules
  • Controllers and,
  • Data binding

Modules

Structuring code is key in building maintainable software. Good news for us is that with Angular we can easily divide front end code into reusable components called modules. A module is basically a container that holds different components of your application under one name.

Most applications have a main method that instantiates and wires all the different parts of the application. However, this is not the case with AngularJs. Instead, modules declaratively specify how the application will be bootstrapped and executed.

Using this approach comes with several benefits:

  • The declarative process is easier to understand.
  • The codebase can be packaged into reusable components.
  • The modules can be loaded in any order (or even in parallel) because modules delay the execution.
  • Unit tests only have to load relevant modules, which keeps them fast.
  • End-to-end tests can use modules to override configuration.

Declaring a Module

A module is declared using the angular.module() function. This function requires three arguments to fully declare a valid module, these are module name, module dependencies and the configuration function respectively. In some cases, a module may not have any dependencies, therefore, you place and empty array and do away with the configuration function if it is not required. The code snippet below shows the syntax and example of module declaration.

// Angular module syntax
angular.module(name, [requires], confgFn);

// Declare a module
angular.module('myApp', []);

Once you have declared your module, the method will return a reference to a newly created module which can be used to attach other components like controllers, directives, services and so forth. Look at angular.module() as a global API for creating, retrieving modules and registering components.

Retrieving a module.

Ok, now that we have declared our module, how do we retrieve it?Retrieving modules can be very tricky sometimes and accounts to some of the bugs you may encounter while working with AngularJs. To avoid such, it is important to always remember that using angular.module('myModule', []) will create the module myModule and overwrite any existing module named myModule and therefore we should use angular.module('myModule') to retrieve an existing module. Note that it does not have the dependency array.

// Declaring a module
angular.module('sampleModule',[])

// Retrieving a module
function GreetController() {
    this.greetings = " Hey there i am a controller";
}

angular.module('sampleModule')
    .controller('GreetController', [GreetController]);

You can take a look at jsfiddle implementation below.

Recommended Setup

In the above example, we managed to declare a simple module and attached a simple controller to it. However, this approach will not scale when it comes to big and complex applications. Instead, It is recommended to break your application into multiple modules. This might look something like:

  • Application level module to attach other modules and initialization code.
  • A module for each feature.
  • A module for reusable components.

For more details on how to structure modules for bigger applications refer to the community style guide for reference. It is important to note that what I have mentioned above is a mere suggestion and therefore you are free to modify or come up with a suitable workflow that works well for you and your team.

// Modules for controller
angular.module('myApp.controller', [])
    .controller('GreetController', function(ResponseService) {
        this.greetings = ResponseService.greetings
    });

// Module for services
angular.module('myApp.service', [])
    .service('ResponseService', function() {
        this.greetings = "Nice to meet you controller, I am service";
    });

// Application level module to attach services, directives,controllers or filters modules
angular.module('myApp', ['myApp.service', 'myApp.controller'])

Below is jsfiddle on the same.


Module loading & Dependencies

A module requires a collection of configuration and run blocks that get applied to the application during the bootstrap process. In its simplest form, a module consist of two kinds of blocks

  • Configuration Blocks
  • Run Blocks

Configuration blocks

These are functions that get executed during the provide registration and configuration phase. It is important to note that only providers and constants can be injected into the configuration blocks. The reasoning behind this is to prevent services from being accidentally instantiated before they are fully configured.

Configuration Blocks are denoted by the .config() function.

// Configuration Blocks syntax
angular.module('myModule', [])
    .config(function(injectables) {
        // provider-injector
        // You can only inject Providers (not instances) into config blocks.
    });

// Example Configuration Blocks
angular.module('myModule', [])
    .config(function($provide, $compileProvider, $filterProvider) {
        $compileProvider.directive('directiveName', ...);
        $filterProvider.register('filterName', ...);
    });

In the example, above we have injected the $filterProvider, $compileProvider into the configuration block and used the providers to register and compile the filter and directive before they can be used in our application. Not that when bootstrapping Angular applies all constant definitions first and the applies the configuration blocks in the same order they are registered.

Run Blocks

Run blocks are the closest thing to the main method in AngularJs. Run blocks are executed to kickstart the application. They are executed after all the services have been configured and the injector has been created.

The code wrapped by the run blocks is typically hard to unit test and for this reason, they should be declared in isolated modules so that the can be ignored in the unit test.

Same as the configuration blocks only instances and constants should be injected into run blocks, this is to prevent further configuration during application run time.


angular.module('myModule')
    .run(function(injectables) {
        // instance-injector
        // You can only inject instances (not Providers) into run blocks
    });

Dependencies

As we have seen in previous examples above, Modules can list other modules as their dependencies. When a module specifies that it depends on in another module, the required module needs to be loaded before the requiring module is loaded.

In other words, the configuration blocks of the required modules execute before the configuration blocks of the requiring module. The same is true for the run blocks. Each module can only be loaded once, even if multiple other modules require it.


Controllers

Controllers are constructor functions that are used to augment the Angular scope. It is responsible for responding to user inputs and performs interactions on the data model objects. It receives input, validates it and then performs the business operation that modifies the state of the model.

When a controller is attached to the DOM, Angular will instantiate a new Controller object, using the specified controller's constructor function. It then pulls together the model used by the view and sets up any corresponding dependencies needed to render the view or handle input from the consumer of the view. In other words, the controller can be used to set up initial state and add behaviours to the scope object.

Let's look at an example below

...
function MyController(UserService) {
   // The controller business logic goes in here
   this.users = UserService.all();
}
...

In our first article, we said controllers act as the middleman between the model and the view(template). In the above example, when the controller is instantiated it will fetch all users from the UserService -our model, and make it available as scoped variable users which can be accessed by referencing the controller once attached to the view.

Declaring a Controller

In Angular, we can declare controllers before we use them in our templates (View). This is done through use of the .controller() method. This keeps the controller's constructor function out of the global scope.

This method registers the controller with a name that can be used to associate it with a view.The example below illustrates how to declare a controller.

// module declation syntax
angular.module('Modulename',[])
    .controller('ControllerName', [controllerFn])

Using the example controller above, we can declare it as shown below


// The controller
function MyController(UserService) {
   // The controller business logic goes in here
   this.users = UserService.all();
}

// Declaring a controller
angular.module('MyApp', [])
    .controller('MyController',[MyController]);

Once declared MyController can be associated with the relevant view in the angular application.

Attaching Properties and Functions to Scope

As we have mentioned above, controllers are JavaScript constructor functions that act as the middleman between the model and the view. However, for it to be used it has to be associated with either a module, directive or components.

Using the controller example above, let's see on how we can associate controllers to various services in Angular.

Association via ngController Directive

The ngController directive attaches a controller class to the view. When Angular compiles the view it will associate the controller specified and use it to bind data and functions to the scope. It is recommended to use the controller as syntax. This approach binds the properties directly to the controller thus making it easier to access data and methods especially when multiple controllers apply.

Let's look at an example that demonstrates controller as syntax.

...
// Greet controller
function GreetController() {
    this.name = 'Mister Awesome'
    this.greet = function() {
        alert(this.name)
    };
}
....

Associating the controller to the view

...
<!--attach the controller to the view-->
<div ng-controller="GreetController as $ctrl">
    <!--Display the name -->
    <p> {{ $ctrl.name }}<p/>
<div/>
...

In the example above when ngController initializes the controller it assigns it an alias of $ctrl which we have used to access the method greet and the name from our view.

Below is an implementation of the same in jsfiddle.

Setting up the Initial State

When controllers are initialized, they should have the initial state that will be displayed to the users. This might be default information such as user details on the profile page. Controller's initial state can be done by attaching properties to the controller scope, these properties will be available to the template when the controller is registered.

Initial state can be constant values or HTTP requests made to your server. The example below shows how to set the initial state of a controller.

....
// Controller with initial state
function ContactsController() {
    this.contacts = {
        name: "Thomas Awesome",
        address:"249 Union Avenue, Brooklyn"
    }
}
....

With the example above when the controller is initialized the scope variable contacts will be initialized to the value of the contacts object.

Adding Behaviour to the Scope Object.

Web application entails having views that react to events and data passed from the user to the server or vice Versa. This may also include user initiated events such as a form submit, login, logout etc.

In order to react to these events or execute computations, we must provide behaviour to the controller scope. This is achieved by adding methods to the controller, which are later made available to the view. If you look at the example above, we have only displayed the initialized values.

Let us add a function that can change the address to somewhere in 28 W 245 New York.

...
// Controller with function that changes its properties value.
function ContactsController() {
    var self = this;
     self.contacts = {
         name: "Thomas Awesome",
         address:"249 Union Avenue, Brooklyn"
    }
    // Function that changes the address 
    self.changeAddress = function() {
        self.contacts.address = "28 W 245 New York";
    }
}
...

Using Controllers Correctly

In general, a controller shouldn't try to do too much. It should contain only the business logic needed for a single view. The most common way to keep controllers slim is by encapsulating work that doesn't belong to controllers into services and then using these services in controllers via dependency injection. The controller should only be used to set initial site and add behaviour to the view, anything beyond this should be delegated to services or directives.

Here are some of the scenarios that a controller shouldn't be used for:

Manipulate DOM

Sometimes we get carried away with our awesomeness and want to some serious DOM manipulation in the controller. It will work no doubt but introducing any presentation logic into Controllers significantly affects its testability. Controllers should contain only business logic and nothing else, Angular has data binding for most cases and directives to encapsulate manual DOM manipulation.

Format input

Thou shall not format inputs in your controller, use angular form controls instead. Angular come shipped with awesome form controls which you can leverage to make your inputs look awesome and give your users amazing user experience.

Filter output

Never ever filter you outputs in controllers, use angular filters instead. Angular has inbuilt filters you can use to format outputs like date, JSON etc. It also provides a way to build your own making it easier to give your app unique data filters.

Share code or state across controllers.

Controllers only act as a middleman between the model and the view. If you are looking to share functionality across controllers, use angular services instead. With angular services, you can share common functionality across your application.

Many of the don't above have recommended other angular features we have not yet covered, worry not we will cover the in details in the subsequent articles.

Data binding

Data binding is the automatic synchronisation of data between the model and the view. If changes happen in the view or the model AngularJs will propagate those changes to the view and the model respectively.

The concept of data binding enables us to provide real-time changes to our view whenever data in the model changes. This gives users the desktop app experience and they don't have to reload the browser to access update changes.

Data binding in Angular is classified into three parts;

  1. one-time data binding.
  2. one-way data binding and
  3. two-way data binding.

We are going to look at them one by one get a deeper understanding of how they work.

One-Time Data Binding.

The Angular $digest cycle essentially loops through all the bindings and checks for any changes then re-renders the values. This has performance implication to our apps especially when the application scales. To solve this problem Angular has introduced the concept of one-time data binding.

The main purpose of one-time binding expression is to provide a way to create a binding that gets deregistered and frees up resources once the binding is stabilised. Reducing the number of expressions being watched makes the digest loop faster and allows more information to be displayed at the same time.

One time data binding is achieved by using one-time expression ::. One-time expressions will stop recalculating once they are stable. This happens after the first digest if the expression result is a non-undefined value. What this means is that once we have our value the binding will be released from the $digest watchers thus less binding to worry about.

Let us look at an example

...
// Controller demonstrating One -Time Data binding.
function EventController() {
  var counter = 0;
  var self = this;
  var names = ['Igor', 'Misko', 'Chirayu', 'Lucas'];
  // exposing the click event to the scope
  self.clickMe = function(clickEvent) {
    self.name = names[counter % names.length];
    counter++;
  };
}
...

angular.module('oneTimeBinding', [])
  .controller('EventController', [EventController]);

Associate the controller to the view.

...
<div ng-controller="EventController as $ctrl">
  <button ng-click="$ctrl.clickMe($event)">Click Me</button>
  <p>One time binding: {{ ::$ctrl.name }}</p>
  <p>Normal binding: {{ $ctrl.name }}</p>
</div>
...

In the above example, we have bound the name variable using one-time expression and the other one using normal expressions. When the button is clicked, the value of Normal binding will change while the one with one-time binding will remain with the first value bound which is is Marko in this instance.

You can reference the example below to see how it works.

One-Way data binding

One-way data binding is a unidirectional data propagation from the Scope to the view or parent component to the child component. In AngularJs there are instances that we only require data changes from the scope to be reflected in the view, not the other way round.

A good example will be when displaying student scores assuming we are building a student management system. In this instance you only want to display the score, and if the score changes you need same reflected on the view. Any changes that happen in the view should not affect the model or the scope.

This can be achieved by using ng-bind or the expression directive {{ }}. By now you should be familiar with this way of binding having used it in several examples above. Let's look at the code sample below.

// Controller demonstrating One-way
function StudentScoresController() {
  this.class = 'Class One';
  this.scores = [{
    name: 'Alex Magana',
    score: 40
  }, {
    name: 'Nduta Opksey',
    score: 70
  }, {
    name: 'Lawrence Mocha',
    score: 90
  }];
}
...

In the view.

...
<div ng-controller="StudentScores as $ctrl" class="container jumbotron">
  <table class="table table-striped table-bordered">
    <tr>
      <td>Name</td>
      <td>Score</td>
    </tr>
    <tr ng-repeat="student in $ctrl.students">
      <td>{{ student.name }}</td>
      <td>{{ student.score }}</td>
    </tr>
  </table>
</div>
  ...

One-way data binding has also been introduced in the .component() and directive () methods allowing us to pass data to child components without affecting the parent. More details on data binding will be covered in the directives section of this article series.

Two-Way Data Binding

Two-way data binding is what we love most about Angular. What this means is that changes in the view and model are automatically synchronised and reflected in view and model respectively. What this means for us is that we do not have to manage or manually update the view or model on changes that happen on either side, Angular will handle that for us.

The best scenario to use two-way data binding is when using forms. In this instance, you might what to ensure that the data you are collecting from the users is the correct one. With the use of ng-model, we can instantly display the user inputs while they type. This also makes it easier to do instant validation as user input changes.

...
// Controller for the form
function FormController(argument) {
  this.user = {
    name: "Moses Koena",
    age: 24
  };
}
...

Let's associate the above controller to the view.

<div ng-controller="FormController as $ctrl">
  <p>Name: {{ $ctrl.user.name }}</p>
  <p>Age : {{ $ctrl.user.age }}</p>

  <input type="text" ng-model="$ctrl.user.name">
  <input type="number" ng-model="$ctrl.user.age">
</div>

When the user makes any changes on the forms, the value of our user object will changes automatically without us clicking any buttons. isn't this beautiful!

Here is a live example to you can examine and see first hand how two-way data binding magic works.

Conclusion

AngularJs has been built to make building web application easy and fun. We have seen how you can use modules to structure your codebase, how controllers augment the view by attaching properties and functions to the Scope. We have also looked into classifications of data binding - (One-Way, One-Time, Two-Way) and scenarios under which to use each.

In most cases, you will use all that these features together to deliver the seamless and elegant desktop experience to your application. Therefore it is important for us to know how to integrate all to this awesome powerful features. I will leave this to you to go and explore and find you footing.

What we have discussed here is just a by the way, I would recommend you research further to understand how you can use this features to your advantage.

Nyambati Thomas

I am a full stack developer with extensive experience working with web technologies. I am competent in MEAN stack, databases such as PostgreSQL, Firebase, MongoDB, and MySQl. I am currently exploring Golang and python.

Software Developer @Andela