Angular JS Tutorial

Introduction to AngularJS

What is AngularJS?

AngularJS is a structural framework for building dynamic web applications. Developed by Google, it enables developers to create single-page applications (SPAs) by extending HTML with custom attributes and providing a client-side model-view-controller (MVC) architecture. AngularJS allows for two-way data binding, meaning changes in the user interface automatically update the underlying model and vice versa. This makes it easier to build interactive and real-time applications.

Key Features of AngularJS

  • Two-Way Data Binding: AngularJS automatically synchronizes data between the model and the view, reducing the need for complex DOM manipulation.
  • Dependency Injection (DI): Allows for the injection of dependencies into components, making the application modular and easier to manage.
  • Directives: Special markers on DOM elements (e.g., attributes, elements, classes) that extend HTML capabilities and instruct Angular to perform specific tasks.
  • MVC Architecture: Follows the MVC pattern to separate concerns (model, view, and controller) and make the application easier to maintain.

Advantages of Using AngularJS in Web Development

  • Two-Way Data Binding: Ensures changes to the view propagate to the model and vice versa, reducing manual DOM manipulation and enhancing user experience.
  • Modular Development: Uses dependency injection (DI) to create modular applications, making them easier to maintain and test.
  • Simplified Testing: Provides features and tools like Jasmine, Karma, and Protractor for easier unit testing.
  • Extensive Tooling and Libraries: Offers built-in features like routing, form validation, and animations, along with a strong ecosystem of community-developed libraries.
  • Cross-Platform Support: Enables web applications to be extended to mobile platforms, including hybrid mobile development via frameworks like Ionic.
  • Declarative Syntax: Directives and templates promote clean, readable, and maintainable code.
  • Community Support: Backed by a large community offering extensive documentation, tutorials, and forums.

Setting up AngularJS: Installation and Environment Setup

  1. Install Node.js and npm: Download and install Node.js from https://nodejs.org/. Verify the installation:
    node -v
    npm -v
  2. Install Angular CLI: Use npm to install Angular CLI globally:
    npm install -g @angular/cli
  3. Create a New Angular Project: Run the following command to create a new project:
    ng new my-angular-app
    Follow the prompts to configure routing and CSS preferences.
  4. Navigate to the Project Directory:
    cd my-angular-app
  5. Serve the Application: Start the development server:
    ng serve
    Access the app at http://localhost:4200.

Basic Structure of an AngularJS Application

1. index.html

The entry point of the application, which includes AngularJS framework references and app scripts.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>AngularJS Application</title>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
</head>
<body>
  <div ng-app="myApp">
    <div ng-controller="myController">
      {{message}}
    </div>
  </div>

  <script src="app.js"></script>
</body>
</html>

2. app.js

Defines the main module and controllers for the application.

var app = angular.module('myApp', []);

app.controller('myController', function($scope) {
  $scope.message = 'Hello, AngularJS!';
});

3. Controllers

Control the data and behavior of specific parts of the application.

app.controller('myController', function($scope) {
  $scope.message = "Welcome to AngularJS!";
});

4. Directives

Extend HTML functionality using AngularJS custom attributes.

<div ng-repeat="item in items">{{item.name}}</div>

5. Services

Provide reusable business logic and data management across the application.

app.service('myService', function() {
  this.getData = function() {
    return 'Some data';
  };
});

6. Views

HTML pages where data from the controller is bound to the view using expressions and directives.

AngularJS Application Architecture

1. Understanding MVC Architecture in AngularJS

AngularJS follows a Model-View-Controller (MVC) architecture to organize an application into manageable and modular components. This architecture helps separate concerns and makes applications more maintainable, scalable, and testable.

The MVC architecture divides an application into three interconnected components:

  • Model: Represents the data and business logic of the application. In AngularJS, the model is typically represented by the $scope object, which holds the data displayed in the view.
  • View: The user interface (UI) part of the application, represented by HTML combined with AngularJS directives and expressions. It interacts with the model through data binding to dynamically display data.
  • Controller: Acts as the intermediary between the model and view. It retrieves data from the model and passes it to the view for display. Controllers are defined as JavaScript functions associated with specific views.

2. Modules, Controllers, Views, and Directives

Modules

An AngularJS module is a container for different parts of an application, such as controllers, services, and directives. Every AngularJS application must define at least one module using the angular.module() function.


var app = angular.module('myApp', []);

In this example, myApp is the module name, and the empty array represents its dependencies.

Controllers

Controllers in AngularJS are JavaScript functions that manage the data and logic of a specific part of the application. They can be associated with HTML elements using the ng-controller directive.


app.controller('myController', function($scope) {
  $scope.message = "Hello, AngularJS!";
});

The myController manages the message variable and binds it to the view.

Views

Views are HTML templates that users interact with. In AngularJS, views dynamically bind to the model via data binding.


{{ message }}

The {{ message }} expression binds data from the model to the view.

Directives

Directives extend HTML by creating reusable components or adding behavior. Common AngularJS directives include:

  • ng-model: Binds form input elements to data models.
  • ng-repeat: Loops over an array and repeats elements for each item.
  • ng-if: Conditionally includes or removes elements from the DOM.

{{ item.name }}

The ng-repeat directive repeats the div element for each item in the items array.

3. Data Binding in AngularJS: One-Way and Two-Way Binding

Data binding synchronizes data between the model (JavaScript) and the view (HTML). AngularJS supports two types:

One-Way Data Binding

Data flows from the model to the view. Changes in the model automatically reflect in the view, but not vice versa.


{{ message }}

Two-Way Data Binding

Data synchronization occurs in both directions. This is achieved using the ng-model directive.



Hello, {{ userName }}

Changes in the input field immediately update the userName model and vice versa.

4. Dependency Injection: Concept and Use

Dependency Injection (DI) is a design pattern in AngularJS that manages the provision and consumption of services and components.

Why Use Dependency Injection?

  • Modularity: Better organization and easier replacement of dependencies.
  • Testability: Easier to mock dependencies for unit testing.
  • Reusability: Components and services can be reused across the application.

Example of Dependency Injection:


app.controller('myController', function($scope, $http) {
  $http.get('api/data').then(function(response) {
    $scope.data = response.data;
  });
});

Here, the $http service is injected into myController to fetch data from an API.

AngularJS Directives

Directives are a core feature of AngularJS that extend HTML functionality by allowing you to create custom behaviors or UI components. Directives enable you to attach specific behavior to elements in your HTML, providing powerful ways to build dynamic and reusable components. AngularJS includes a variety of built-in directives, and you can also create custom directives to extend the application's functionality.

1. Using Built-In Directives

AngularJS provides several built-in directives to simplify the development process. Below are some commonly used directives:

ng-app

Purpose: Initializes an AngularJS application. It tells AngularJS that this section of the HTML page is the entry point of an AngularJS app.

Usage: The ng-app directive is typically added to the root HTML element to define the scope of the AngularJS application.



  
    

ng-controller

Purpose: Defines a controller in AngularJS that is responsible for handling the logic and data of a specific part of the view.

Usage: The ng-controller directive binds the controller to an HTML element, allowing it to control the scope for that element.


{{ message }}

ng-repeat

Purpose: Repeats an HTML element or section for each item in a collection (array or object).

Usage: Dynamically generate a list or table from data using the ng-repeat directive.


  • {{ item.name }}

ng-model

Purpose: Binds the value of HTML form elements to a property in the AngularJS model, providing two-way data binding.

Usage: Use the ng-model directive to bind form controls to the AngularJS scope.



Hello, {{ userName }}

ng-if

Purpose: Conditionally includes or removes an element from the DOM based on a given expression.

Usage: Ideal for conditionally displaying content in your application.


Welcome, User!

ng-show and ng-hide

Purpose: Control the visibility of an element based on a boolean expression. ng-show displays the element when the expression is true, while ng-hide hides the element when the expression is true.

Usage with ng-show:


This content is visible!

Usage with ng-hide:


This content is hidden!

2. Creating Custom Directives

AngularJS allows developers to create custom directives to encapsulate reusable behavior or logic. Custom directives are useful for building complex UI components or adding specialized functionality to an HTML element.

Steps to Create a Custom Directive

  • Define the Directive: Use the angular.module().directive() method.
  • Set Up Directive Options: Configure how the directive behaves, what elements it applies to, and how it interacts with the DOM.

app.directive('myDirective', function() {
  return {
    restrict: 'E',  // Defines the directive as an element
    template: '
Custom Directive Content
', link: function(scope, element, attrs) { // Custom behavior can be added here } }; });

Directive Options:

  • restrict: Specifies how the directive is used (e.g., as an element, attribute, class, or comment).
  • template: Defines the HTML that the directive will render.
  • link: A function to add behavior to the directive, such as manipulating the DOM or adding event listeners.
  • scope: Determines whether the directive should have its own isolated scope or use the parent scope.

3. Practical Examples

Example 1: Building a Modal Dialog


app.directive('modal', function() {
  return {
    restrict: 'E',
    template: `
      
    `,
    scope: {
      isVisible: '=',
      closeModal: '&'
    },
    transclude: true
  };
});

Usage:



  

This is a modal dialog!

Example 2: A Custom Tooltip


app.directive('tooltip', function() {
  return {
    restrict: 'A',
    link: function(scope, element, attrs) {
      var tooltipText = attrs.tooltip;
      var tooltip = angular.element('
' + tooltipText + '
'); element.on('mouseenter', function() { element.append(tooltip); tooltip.css({ position: 'absolute', top: element[0].offsetTop + 20 + 'px', left: element[0].offsetLeft + 'px' }); }); element.on('mouseleave', function() { tooltip.remove(); }); } }; });

Usage:



AngularJS Controllers and Scopes

1. What is a Controller in AngularJS?

A controller in AngularJS is a JavaScript function used to define the business logic of an application. It manages the data and behavior of a part of the UI (view) and acts as a bridge between the view (HTML) and the model (JavaScript data).

Key Points:

  • A controller is defined with the ng-controller directive in HTML.
  • It bridges the view and the model.
  • Manages user interactions, such as form submissions, data retrieval, and updates.
Example:

// JavaScript
angular.module('myApp', [])
  .controller('MyController', function($scope) {
    $scope.message = "Hello, AngularJS!";
  });

// HTML

{{ message }}

Explanation:

  • The MyController manages the message property.
  • The view binds message, displaying "Hello, AngularJS!"

2. Using $scope to Bind Data Between Controller and View

The $scope object binds data between the controller and the view. It allows for AngularJS's two-way data binding, ensuring updates in the model are reflected in the view and vice versa.

Two-Way Data Binding:

If the model (data in $scope) updates, the view updates automatically, and vice versa.

Example:

// JavaScript
angular.module('myApp', [])
  .controller('MyController', function($scope) {
    $scope.username = "John Doe";
  });

// HTML

Hello, {{ username }}!

Explanation:

  • The ng-model directive binds the input field to username.
  • Typing in the input updates the username dynamically in the greeting.

3. Passing Data Between Controllers

Controllers are usually independent, but data can be shared between them through parent-child relationships or services.

Parent-Child Controller Relationship:

Example:

// JavaScript
angular.module('myApp', [])
  .controller('ParentController', function($scope) {
    $scope.parentMessage = "Message from Parent Controller";
  })
  .controller('ChildController', function($scope) {
    $scope.childMessage = "Message from Child Controller";
  });

// HTML

{{ parentMessage }}

{{ childMessage }}

Using Services to Share Data:

Example:

// JavaScript
angular.module('myApp', [])
  .service('messageService', function() {
    this.message = "Shared message between controllers";
  })
  .controller('FirstController', function($scope, messageService) {
    $scope.message = messageService.message;
  })
  .controller('SecondController', function($scope, messageService) {
    $scope.message = messageService.message;
  });

Explanation:

  • Services allow data sharing across controllers.
  • The messageService holds a shared message accessible to both controllers.

4. Practical Use Case: Managing User Inputs in Forms

Controllers and $scope are frequently used to manage user inputs and form data with AngularJS's built-in directives like ng-model, ng-submit, and ng-valid.

User Registration Form Example:


// JavaScript
angular.module('myApp', [])
  .controller('FormController', function($scope) {
    $scope.user = {};
    $scope.isSubmitted = false;

    $scope.submitForm = function() {
      if ($scope.registrationForm.$valid) {
        $scope.isSubmitted = true;
        console.log("User Data:", $scope.user);
      }
    };
  });

// HTML

Registration Successful!

Explanation:

  • The form uses ng-submit for submission and ng-model for binding input data.
  • Validation directives like required ensure proper input.
  • On valid submission, isSubmitted becomes true, displaying a success message.

AngularJS Services and Factories

In AngularJS, services and factories are used to define reusable business logic and share data or functionality across different parts of an application. They are singletons, meaning they maintain a single instance that can be shared across controllers and other services.

1. What are Services and Factories in AngularJS?

Services:

Services are used to define reusable logic and data that can be injected into controllers, directives, filters, or other services. A service in AngularJS is typically a constructor function that is used to define its methods or properties. When you define a service, AngularJS instantiates it using the new keyword.

Factories:

A factory is similar to a service, but instead of a constructor function, a factory is a function that returns an object (could be a service or other data). Factories allow more flexibility and are commonly used when you need to return a function or an object with methods that maintain state or hold complex logic.

Key Differences:

  • Service uses the new keyword and is instantiated as a constructor.
  • Factory can return anything (functions, objects, etc.), giving more flexibility.

2. Creating Custom Services to Share Data Across Controllers

You can create custom services in AngularJS to encapsulate business logic or data and share it across multiple controllers. This is useful when you need to maintain application state or data consistency between different parts of your app.

Example: Creating a Simple Service


angular.module('myApp', [])
  .service('UserService', function() {
    this.user = { name: 'John Doe', age: 30 };
    
    this.getUser = function() {
      return this.user;
    };

    this.setUser = function(user) {
      this.user = user;
    };
  })
  .controller('MainController', function($scope, UserService) {
    $scope.user = UserService.getUser();
  })
  .controller('EditController', function($scope, UserService) {
    $scope.user = UserService.getUser();
    $scope.saveUser = function() {
      UserService.setUser($scope.user);
    };
  });

Explanation:

  • UserService is a custom service with getUser and setUser methods to share user data between controllers.
  • MainController retrieves the user data from UserService to display it.
  • EditController allows editing the user data and saves it back to the service.

3. Using Built-In AngularJS Services

AngularJS comes with several built-in services that are commonly used for various tasks. Some of the most frequently used services include:

  • $http: For making AJAX requests (e.g., fetching data from APIs).
  • $route: For routing and managing the views in single-page applications.
  • $location: For interacting with the URL and browser location.

Using $http Service for API Calls

The $http service is used to make asynchronous HTTP requests and retrieve data from external sources like APIs.

Example: Fetching Data Using $http Service


angular.module('myApp', [])
  .controller('DataController', function($scope, $http) {
    $http.get('https://jsonplaceholder.typicode.com/posts')
      .then(function(response) {
        $scope.posts = response.data;
      })
      .catch(function(error) {
        console.error('Error fetching data:', error);
      });
  });

Explanation:

  • The $http.get method is used to send a GET request to fetch data from a placeholder API (https://jsonplaceholder.typicode.com/posts).
  • On success, the data is stored in the $scope.posts variable and displayed in the view.
  • The .catch method is used to handle any errors that occur during the HTTP request.

$http Methods:

  • $http.get(url, config): Sends a GET request to the specified URL.
  • $http.post(url, data, config): Sends a POST request with the specified data to the URL.
  • $http.put(url, data, config): Sends a PUT request to update the data on the server.
  • $http.delete(url, config): Sends a DELETE request to remove data.

4. Practical Example: Fetching Data from APIs Using $http Service

Let's extend the previous example with a more comprehensive application where we retrieve, display, and update data from an API using AngularJS services.

Full Example: Displaying and Editing Posts from an API


angular.module('myApp', [])
  .service('PostService', function($http) {
    this.getPosts = function() {
      return $http.get('https://jsonplaceholder.typicode.com/posts');
    };
    
    this.savePost = function(post) {
      return $http.put('https://jsonplaceholder.typicode.com/posts/' + post.id, post);
    };
  })
  .controller('PostsController', function($scope, PostService) {
    $scope.posts = [];
    
    // Fetch posts from API
    PostService.getPosts().then(function(response) {
      $scope.posts = response.data;
    }).catch(function(error) {
      console.error('Error fetching posts:', error);
    });
    
    // Save updated post
    $scope.updatePost = function(post) {
      PostService.savePost(post).then(function(response) {
        console.log('Post updated:', response.data);
      }).catch(function(error) {
        console.error('Error updating post:', error);
      });
    };
  });

HTML:


Posts

  • {{ post.title }}

Explanation:

  • The PostService handles API requests for fetching (getPosts) and updating (savePost) posts.
  • PostsController retrieves the posts from the API when the controller is initialized and stores them in $scope.posts.
  • Users can edit a post's body and click the "Update Post" button to send a PUT request to update the post on the API.

AngularJS Forms and Validation

AngularJS provides robust support for building forms, including two-way data binding, form validation, and the ability to create custom validators. Below is an overview of how to work with forms and validation in AngularJS.

1. Binding Form Data with AngularJS

AngularJS provides two-way data binding, which means that any change in the form input is automatically reflected in the controller's scope, and vice versa. This is achieved using the ng-model directive.

Example of Binding Form Data:





In this example:

  • ng-model binds form inputs (like name, email, and password) to properties in the $scope.user object in the controller.
  • As the user types in the form, these values automatically update the user model in the controller.

2. Using ng-model for Two-Way Data Binding in Forms

The ng-model directive is key to AngularJS's two-way data binding. When used on a form input, it allows you to synchronize the input value with the scope object.

Two-Way Data Binding Example:


You entered: {{ user.username }}

In this example:

  • ng-model="user.username" binds the input field to the user.username property.
  • When the user types into the input field, the value is automatically reflected in the user.username model and vice versa.

3. Built-in Form Validation (Required, Email, MinLength, MaxLength)

AngularJS offers several built-in validation features for common form input types like required, email, minlength, maxlength, and more.

Common Form Validation Directives:

  • required: Ensures the field is not empty.
  • email: Validates that the input is in the correct email format.
  • minlength/maxlength: Ensures the input length is within a specified range.

Example of Built-in Validation:


Email is required.
Please enter a valid email.

Password is required.
Password must be at least 6 characters long.

In this example:

  • The ng-model="user.email" binds the email input to the user.email model.
  • The ng-show directives display error messages based on whether the input field is invalid and has been touched.

4. Creating Custom Validators in AngularJS

Sometimes, built-in validation isn't enough. In AngularJS, you can create custom validators using the ng-model's $validators property. Custom validators allow you to define complex validation rules.

Example of Creating a Custom Validator:


angular.module('myApp', [])
  .controller('FormController', function($scope) {
    $scope.user = {};

    $scope.passwordStrength = function(password) {
      // Custom validator to check if password is strong
      return /[A-Z]/.test(password) && /[0-9]/.test(password);
    };
  })
  .directive('strongPassword', function() {
    return {
      require: 'ngModel',
      link: function(scope, element, attrs, ngModel) {
        ngModel.$validators.strongPassword = function(modelValue) {
          return /[A-Z]/.test(modelValue) && /[0-9]/.test(modelValue);
        };
      }
    };
  });

In the above example:

  • We define a custom validator for a password using ngModel.$validators.strongPassword.
  • The custom validator checks that the password contains at least one uppercase letter and one number.
  • The strongPassword directive is applied to an input field and is used to validate the password.

5. Practical Example: Form Validation for User Registration

This example demonstrates how to implement a user registration form with built-in and custom validation rules.

HTML: User Registration Form


Username is required.

Email is required.
Please enter a valid email.

Password is required.
Password must be at least 6 characters long.
Password must contain at least one uppercase letter and one number.

JavaScript Controller:


angular.module('myApp', [])
  .controller('FormController', function($scope) {
    $scope.user = {};

    // Custom validator function for strong password
    $scope.passwordStrength = function(password) {
      return /[A-Z]/.test(password) && /[0-9]/.test(password);
    };
  })
  .directive('strongPassword', function() {
    return {
      require: 'ngModel',
      link: function(scope, element, attrs, ngModel) {
        ngModel.$validators.strongPassword = function(modelValue) {
          return /[A-Z]/.test(modelValue) && /[0-9]/.test(modelValue);
        };
      }
    };
  });

AngularJS Routing and SPA (Single Page Application)

1. Setting up Routing with ngRoute

To use routing in AngularJS, you first need to include the ngRoute module and configure routes using the $routeProvider service. This will help define the different routes (URLs) and the templates associated with them.

Steps to Set up Routing:

  • Include AngularJS and ngRoute:




  • Create an AngularJS App and Configure Routes: In your AngularJS app, you will use $routeProvider to configure different routes and map them to views (HTML templates).

angular.module('myApp', ['ngRoute'])
  .config(function($routeProvider) {
    $routeProvider
      .when('/home', {
        templateUrl: 'home.html',
        controller: 'HomeController'
      })
      .when('/about', {
        templateUrl: 'about.html',
        controller: 'AboutController'
      })
      .otherwise({
        redirectTo: '/home'
      });
  })
  .controller('HomeController', function($scope) {
    $scope.message = "Welcome to the Home Page!";
  })
  .controller('AboutController', function($scope) {
    $scope.message = "Welcome to the About Page!";
  });

$routeProvider.when(): This defines a route and associates it with a template and controller.

$routeProvider.otherwise(): This defines a fallback route, in case the user enters an undefined route.

HTML Structure (index.html):





  AngularJS Routing Example


  

AngularJS SPA Example

The <div ng-view></div> directive is where the templates corresponding to the routes will be injected.

2. Creating Different Views and Templates for Routes

To display different content when a user navigates to different routes, you need to create separate HTML templates for each route.

Example of Templates:

  • home.html:

Home Page

{{ message }}

  • about.html:

About Page

{{ message }}

These templates will be dynamically inserted into the <div ng-view></div> based on the active route.

3. Passing Parameters through Routes

AngularJS allows you to pass parameters through routes, which can be accessed in the controllers. You can pass parameters using :paramName syntax in the URL, and then access these parameters using $routeParams.

Example of Passing Parameters:

Configure Route with Parameters:


$routeProvider
  .when('/user/:userId', {
    templateUrl: 'user.html',
    controller: 'UserController'
  });

In the above configuration, :userId is a dynamic parameter that will be passed to the controller.

Accessing Route Parameters in the Controller:


angular.module('myApp', ['ngRoute'])
  .config(function($routeProvider) {
    $routeProvider
      .when('/user/:userId', {
        templateUrl: 'user.html',
        controller: 'UserController'
      });
  })
  .controller('UserController', function($scope, $routeParams) {
    $scope.userId = $routeParams.userId;
  });

In this example:

  • The UserController will have access to the userId parameter from the URL via $routeParams.userId.

HTML Template (user.html):


User Profile

User ID: {{ userId }}

If the user navigates to /user/123, the userId parameter will be 123, and it will be displayed in the view.

4. Practical Example: Building a Multi-Page Application with AngularJS

Let’s build a simple multi-page application that shows a list of users and allows the user to view a detailed profile of each user. We'll demonstrate the usage of routing, controllers, and views with dynamic parameters.

Steps:

AngularJS Application Setup:


angular.module('userApp', ['ngRoute'])
  .config(function($routeProvider) {
    $routeProvider
      .when('/users', {
        templateUrl: 'users.html',
        controller: 'UsersController'
      })
      .when('/user/:userId', {
        templateUrl: 'userProfile.html',
        controller: 'UserProfileController'
      })
      .otherwise({
        redirectTo: '/users'
      });
  })
  .controller('UsersController', function($scope) {
    $scope.users = [
      { id: 1, name: 'John Doe' },
      { id: 2, name: 'Jane Smith' },
      { id: 3, name: 'Sam Brown' }
    ];
  })
  .controller('UserProfileController', function($scope, $routeParams) {
    const userId = $routeParams.userId;
    $scope.user = { id: userId, name: `User ${userId}` }; // Fetch user details using the userId
  });

HTML Templates:

  • users.html:

Users List

  • userProfile.html:

User Profile

User ID: {{ user.id }}

User Name: {{ user.name }}

HTML Structure (index.html):





  AngularJS Multi-Page Example


  

AngularJS Multi-Page Example

How It Works:

  • Users List Page: The users' list is displayed, and each user is linked to their profile page via the route /user/:userId.
  • User Profile Page: When a user clicks on a name, the user is taken to the profile page, which displays the userId and name.

AngularJS Filters

Filters in AngularJS are powerful tools used for formatting and transforming data before it's displayed in the view. They are often used to format dates, numbers, and strings, or to filter out unwanted data in a list. AngularJS provides a number of built-in filters, and you can also create custom filters to meet your application's specific needs.

1. Using Built-In Filters

AngularJS provides several built-in filters that are commonly used for formatting data. Here are some of the most widely used built-in filters:

Common Built-In Filters:

  • currency: Formats a number as currency.
  • date: Formats a date to a specific format.
  • filter: Filters an array of data based on a search query.
  • json: Converts an object to a JSON string.
  • limitTo: Limits the number of items in a list.

Examples of Using Built-In Filters:

currency Filter:

The currency filter formats a number as a currency string.


{{ 12345 | currency }}

{{ 12345 | currency:"€" }}

date Filter:

The date filter formats a date according to the specified format.


{{ '2024-01-01' | date:'yyyy-MM-dd' }}

{{ '2024-01-01' | date:'MMMM d, yyyy' }}

filter Filter:

The filter filter allows you to filter an array based on a search query.


  • {{ user.name }}

In this example, the list of users will be filtered by the searchText model as the user types.

json Filter:

The json filter converts an object to a JSON string for debugging or displaying raw data.


{{ user | json }}
limitTo Filter:

The limitTo filter limits the number of items in an array or string.


  • {{ user.name }}

This will only display the first 3 users in the users array.

2. Creating Custom Filters

AngularJS also allows you to create custom filters to apply specific transformations to your data.

Steps to Create a Custom Filter:

  • Define the Filter: Use the filter function to create a custom filter.
  • Use the Filter in the Template: Apply your custom filter just like a built-in filter.
Example: Custom Filter to Capitalize Text

Defining the Custom Filter:


angular.module('myApp', [])
  .filter('capitalize', function() {
    return function(input) {
      if (input) {
        return input.charAt(0).toUpperCase() + input.slice(1);
      }
      return input;
    };
  });

In this example, the capitalize filter takes an input string and capitalizes the first letter of the string.

Using the Custom Filter in the Template:


{{ 'hello world' | capitalize }}

This will apply the capitalize filter to the text "hello world" and output "Hello world".

3. Practical Example: Filtering and Displaying Lists Dynamically

Let's build a practical example where we use filters to dynamically display and filter a list of users. We'll create a list of users and allow the user to filter it based on specific criteria (e.g., name or age).

Steps to Create the Filtering Example:

Create AngularJS App:

angular.module('userApp', [])
  .controller('UserController', function($scope) {
    $scope.users = [
      { name: 'John Doe', age: 25 },
      { name: 'Jane Smith', age: 30 },
      { name: 'Sam Brown', age: 28 },
      { name: 'Anna Green', age: 22 },
      { name: 'James White', age: 35 }
    ];
    $scope.searchText = ''; // Model for search input
  });
HTML Template:




  AngularJS Filters Example


  

Users List

  • {{ user.name }} - {{ user.age }} years old

Top 3 Users

  • {{ user.name }} - {{ user.age }} years old

Users Data (JSON Format)

{{ users | json }}

In this example:

  • The input field is bound to the searchText model, which is used to filter the list of users.
  • The ng-repeat directive loops through the users array and uses the filter filter to match the searchText to user names or ages.
  • The limitTo:3 filter limits the displayed list to only the first 3 users.
  • The json filter is used to display the raw JSON representation of the users array.

AngularJS HTTP Requests and API Integration

AngularJS provides a service called $http that is used to make HTTP requests to interact with APIs and retrieve or send data to a server. The $http service is built on top of the browser's native XMLHttpRequest and supports both synchronous and asynchronous operations.

This guide will cover how to use the $http service for making HTTP requests, handling API responses with promises, error handling, and a practical example of integrating a REST API.

1. Using $http Service to Make HTTP Requests

The $http service allows you to make various types of HTTP requests, including GET, POST, PUT, and DELETE. These requests are used to interact with a REST API or any other HTTP service.

Basic Syntax:


$http({
  method: 'GET',     // HTTP Method (GET, POST, PUT, DELETE)
  url: 'https://api.example.com/data', // The URL of the API endpoint
  data: dataObject    // Optional: Data to send with the request (for POST/PUT requests)
}).then(function(response) {
  // Success callback
  console.log(response.data);  // Response data from the server
}, function(error) {
  // Error callback
  console.error(error);  // Error information
});

Common HTTP Methods with $http:

  • GET: Used to fetch data from the server.
  • POST: Used to send data to the server (e.g., submitting a form).
  • PUT: Used to update existing data on the server.
  • DELETE: Used to remove data from the server.

Example: Basic GET Request


angular.module('myApp', [])
  .controller('ApiController', function($scope, $http) {
    $http.get('https://jsonplaceholder.typicode.com/posts')
      .then(function(response) {
        $scope.posts = response.data;  // Assign the API data to a scope variable
      }, function(error) {
        console.error('Error fetching data:', error);
      });
  });

In this example, the GET request fetches data from https://jsonplaceholder.typicode.com/posts and assigns the response data to the $scope.posts variable.

2. Handling API Responses Using Promises

AngularJS’s $http service uses Promises to handle asynchronous operations. The .then() method of the $http service is used to specify success and error handlers that will be executed once the HTTP request is complete.

Promise Structure:

  • Success handler: The first function passed to .then() handles a successful response.
  • Error handler: The second function passed to .then() handles errors or failed requests.

Example: Using Promises for API Responses


$http.get('https://jsonplaceholder.typicode.com/posts')
  .then(function(response) {
    // Success: Handle the response data
    console.log('Data received:', response.data);
  }, function(error) {
    // Error: Handle the error
    console.error('Error occurred:', error);
  });

In this example:

  • The success handler receives the response object containing the data returned by the API (response.data).
  • The error handler receives an error object that contains information about the failure.

3. Error Handling in API Calls

It’s important to handle errors effectively when making API calls. AngularJS provides the error handler within the .then() method, but you can also catch specific HTTP status codes and provide meaningful feedback to the user.

Common HTTP Error Codes:

  • 404: Not Found (API endpoint does not exist)
  • 500: Internal Server Error (Issue on the server side)
  • 400: Bad Request (Invalid request sent to the server)

Example: Error Handling for Different Status Codes


$http.get('https://jsonplaceholder.typicode.com/invalid-url')
  .then(function(response) {
    // Success: Handle the response
    console.log('Data received:', response.data);
  })
  .catch(function(error) {
    // Error: Handle different error types
    if (error.status === 404) {
      console.error('Error 404: Not Found');
    } else if (error.status === 500) {
      console.error('Error 500: Server Error');
    } else {
      console.error('An unexpected error occurred:', error);
    }
  });

In this example:

  • .catch() handles any errors that occur during the HTTP request.
  • The error.status is checked to determine the type of error (e.g., 404, 500), and a specific error message is logged to the console.

4. Practical Example: Displaying Data from a REST API

Now, let's build a practical example where AngularJS will fetch and display a list of posts from a REST API. We’ll use jsonplaceholder.typicode.com, a fake online REST API for testing and prototyping.

Steps to Create the Example:

Define the AngularJS App and Controller:

angular.module('myApp', [])
  .controller('PostController', function($scope, $http) {
    // Fetch data from the API
    $http.get('https://jsonplaceholder.typicode.com/posts')
      .then(function(response) {
        // Assign the fetched posts to the scope variable
        $scope.posts = response.data;
      }, function(error) {
        console.error('Error fetching data:', error);
      });
  });
Create the HTML Template:




  AngularJS HTTP Example


  

Posts

  • {{ post.title }}

    {{ post.body }}

In this example:

  • The PostController makes a GET request to fetch posts from the API (jsonplaceholder.typicode.com).
  • The posts are then stored in the $scope.posts variable, which is used to display the list in the view using ng-repeat.
  • Each post is displayed with its title and body.

AngularJS and AJAX

1. Making Asynchronous HTTP Requests in AngularJS

AngularJS provides a powerful way to manage asynchronous operations and dynamically update content using AJAX (Asynchronous JavaScript and XML) without the need to reload the page. This makes AngularJS applications more responsive and efficient, particularly when interacting with APIs or fetching real-time data.

Basic Syntax:


$http({
  method: 'GET',     // HTTP Method (GET, POST, PUT, DELETE)
  url: 'https://api.example.com/data', // The API URL
}).then(function(response) {
  // Success callback
  console.log(response.data);  // Data received from the API
}, function(error) {
  // Error callback
  console.error('Error occurred:', error);
});

2. Using Promises to Handle Asynchronous Data

When you make asynchronous requests using $http, AngularJS returns a promise object. The promise object allows you to handle the response data once the request has completed. You can use .then() to define success and error handlers.

Understanding Promises:

A promise represents a value that may not be available yet but will be at some point in the future. The .then() method is used to handle the result once the promise is fulfilled (success) or rejected (error).

Promise Structure:


$http.get('https://jsonplaceholder.typicode.com/posts')
  .then(function(response) {
    // Success: Handle the data when it is available
    console.log('Data received:', response.data);
  }, function(error) {
    // Error: Handle failure scenarios
    console.error('Error occurred:', error);
  });

3. Real-Time Data Updates Using AJAX

AngularJS makes it easy to update content dynamically in real-time without reloading the page. You can periodically send AJAX requests to update your data at regular intervals or based on user interactions.

Example: Polling Data Every Few Seconds


angular.module('myApp', [])
  .controller('RealTimeController', function($scope, $http, $interval) {
    // Function to fetch data
    function fetchData() {
      $http.get('https://jsonplaceholder.typicode.com/posts')
        .then(function(response) {
          $scope.posts = response.data;
        }, function(error) {
          console.error('Error fetching data:', error);
        });
    }

    // Fetch data every 5 seconds
    $interval(fetchData, 5000);

    // Initial data fetch
    fetchData();
  });

4. Practical Example: Updating Content Dynamically Without Reloading the Page

Let’s build a practical example that uses AngularJS and AJAX to update content dynamically without refreshing the page. This example will fetch and display a list of posts from an API, and we’ll update the data periodically.

HTML Template:





  AngularJS Real-Time Data Example


  

Posts

Loading...
  • {{ post.title }}

    {{ post.body }}

JavaScript Code (app.js):


angular.module('myApp', [])
  .controller('RealTimeController', function($scope, $http, $interval) {
    // Function to fetch data from the API
    function fetchData() {
      $http.get('https://jsonplaceholder.typicode.com/posts')
        .then(function(response) {
          // Assign the fetched data to the scope variable
          $scope.posts = response.data;
        }, function(error) {
          console.error('Error fetching data:', error);
        });
    }

    // Fetch data every 10 seconds (real-time updates)
    $interval(fetchData, 10000);

    // Initial fetch
    fetchData();
  });

Explanation:

  • HTML Structure: The <ul> element is used to display a list of posts. The ng-repeat directive is used to iterate over the posts array and display the title and body of each post.
  • Controller Logic:
    • The fetchData() function makes an HTTP request to jsonplaceholder.typicode.com/posts to retrieve the data.
    • The $interval(fetchData, 10000) function is used to repeatedly call the fetchData function every 10 seconds, simulating real-time updates of the posts.
  • Dynamic Updates: The content of the posts list is updated every 10 seconds without reloading the page, as AngularJS binds the data to the view using two-way data binding.

Debugging and Optimizing AngularJS Applications

Debugging and optimizing AngularJS applications are critical to ensure smooth performance, minimal errors, and a great user experience. In this section, we'll cover some essential techniques for debugging AngularJS apps, common errors, and strategies for optimizing your AngularJS applications.

1. Using Developer Tools for Debugging AngularJS Applications

Developer tools in modern browsers (like Chrome DevTools) offer powerful features for debugging AngularJS applications.

Essential Tools for Debugging:

  • AngularJS Batarang (Chrome Extension): Batarang is a Chrome extension designed to help developers debug AngularJS applications. It provides a range of debugging tools, such as:
    • Model Inspector: View and modify the $scope model and its properties.
    • Performance Metrics: Inspect AngularJS application performance, including digest cycle time.
    • Directive View: Shows the active AngularJS directives in your application.
  • Console Logging: Using console.log() in AngularJS controllers or services helps you track values, errors, or function calls in real-time. However, excessive logging can clutter the console, so it's good practice to remove or limit it in production code.
  • 
    console.log($scope.data);
    
  • Network Tab: In Chrome DevTools, use the "Network" tab to monitor all HTTP requests made by the AngularJS app (such as API calls via $http). You can see the request status, payload, and responses, which is essential for debugging AJAX calls.
  • Sources Tab: The "Sources" tab in Chrome allows you to set breakpoints in JavaScript code, step through the code line by line, and inspect variables during runtime.

2. Common AngularJS Errors and How to Fix Them

Some common errors in AngularJS applications are often related to the framework’s two-way data binding, controller issues, and digest cycle problems.

Common AngularJS Errors:

  • $digest already in progress: This error occurs when AngularJS tries to trigger a $digest cycle while one is already in progress, causing a conflict.
  • 
    $timeout(function() {
      $scope.data = 'Updated value';
    });
    
  • $http service cannot find the URL: Occurs when $http tries to fetch data from an incorrect URL, leading to a 404 or other HTTP error.
  • 
    $http.get('https://api.example.com/data')
      .then(function(response) {
        // Success
      }, function(error) {
        // Error
        console.error('Error fetching data:', error);
      });
    
  • Expression has changed after it was checked: This error occurs in AngularJS when the value of a model changes after Angular has already checked for changes.
  • 
    $scope.$apply(function() {
      $scope.someData = 'Updated value';
    });
    
  • Error: $digest already in progress (digest cycle conflict): This error arises if changes to $scope are happening too frequently, possibly in a loop, or while AngularJS is already in the middle of digesting changes.
  • 
    $timeout(function() {
      $scope.data = 'Updated value';
    });
    

3. Optimizing Performance: Reducing Digest Cycle, Minimizing HTTP Requests

AngularJS's digest cycle is responsible for updating the DOM whenever there are changes in the scope model. However, if not handled properly, the digest cycle can become inefficient and cause performance issues.

Tips for Optimizing AngularJS Performance:

  • Reduce Digest Cycle Impact: Limit $scope changes to avoid unnecessary triggers of the digest cycle. Each change triggers a digest cycle that checks all $scope properties for changes.
  • Use ng-if vs ng-show/ng-hide: ng-if removes the element from the DOM when it’s not needed, reducing the load during the digest cycle. ng-show/ng-hide only hides the element, which may still affect the digest cycle.
  • 
    
    
    Content
    Content
  • Use track by in ng-repeat: When iterating over large lists, AngularJS checks every item in the list during each digest cycle. Using track by helps AngularJS efficiently identify unique items.
  • 
    
    • {{ item.name }}
  • Debounce Input Events: If you’re performing actions based on user input, such as searching or filtering, consider debouncing the input to prevent too many $digest cycles.
  • 
    angular.module('myApp')
      .controller('SearchController', function($scope, $timeout) {
        var debounceTimer;
        $scope.search = function() {
          $timeout.cancel(debounceTimer);
          debounceTimer = $timeout(function() {
            // Perform the search operation
          }, 500); // 500ms debounce
        };
      });
    
  • Minimize HTTP Requests: Cache data to avoid making repetitive requests to the server. Combine API calls to reduce the number of HTTP requests (e.g., use batch requests).
  • 
    // Example of caching with $http
    $http.get('api/posts', { cache: true })
      .then(function(response) {
        $scope.posts = response.data;
      });
    

4. Practical Example: Debugging and Optimizing a Complex AngularJS App

Let’s look at a practical example of debugging and optimizing an AngularJS app with performance issues.

Scenario: Your AngularJS app is performing slowly when fetching a list of users from an API, and users are reporting lag when typing in search fields.

Steps for Debugging and Optimizing:

  • Check the Network Tab: Open Chrome DevTools and check the network requests. If there are too many unnecessary API calls (e.g., repeated requests), optimize by caching results or reducing unnecessary API calls.
  • Use ng-repeat with track by: To efficiently render a list of users, use track by in ng-repeat.
  • 
    
    • {{ user.name }}
  • Debounce User Input: If the app allows users to search through the list, debounce the search input to reduce the number of calls made during typing.
  • 
    angular.module('myApp')
      .controller('SearchController', function($scope, $timeout) {
        var debounceTimer;
        $scope.search = function() {
          $timeout.cancel(debounceTimer);
          debounceTimer = $timeout(function() {
            // Perform search operation here
          }, 500); // Wait for 500ms after typing stops
        };
      });
    
  • Optimize Digest Cycle: Reduce unnecessary $scope updates within loops, and use $timeout() or $interval() to avoid triggering the digest cycle too frequently.
  • 
    $timeout(function() {
      $scope.users.push(newUser);
    });
    
  • Use Lazy Loading for Data: If the app displays large datasets, consider loading data only when needed (lazy loading). For instance, load additional user data when the user scrolls to the bottom of the page (infinite scroll).

AngularJS Security Best Practices

Security is a critical aspect of building web applications, and AngularJS provides several mechanisms to prevent common security threats like XSS (Cross-Site Scripting) and CSRF (Cross-Site Request Forgery). Implementing security best practices ensures that the AngularJS application remains robust, secure, and protected against common vulnerabilities.

1. Preventing XSS (Cross-Site Scripting) in AngularJS

XSS attacks occur when malicious scripts are injected into content from an untrusted source, allowing attackers to execute malicious JavaScript in the browser of unsuspecting users. AngularJS helps mitigate XSS vulnerabilities through automatic escaping of data bound to the view.

Preventing XSS in AngularJS:

  • Automatic HTML Escaping: AngularJS automatically escapes content bound to the view using data binding. For example, if you bind user input or API response to the DOM, AngularJS will escape HTML tags and prevent them from being executed as JavaScript.

Example:



{{ userInput }}
  • Using ng-bind Instead of Direct HTML: Use ng-bind or data binding (e.g., {{ variable }}) instead of ng-model or directly setting innerHTML to ensure AngularJS automatically escapes content.

Example:






{{ userInput }}
  • Avoid ng-bind-html With Untrusted Content: AngularJS provides the ng-bind-html directive for binding HTML content, but never use this with untrusted or user-generated content without sanitizing it first. Use $sanitize service to clean any HTML content before binding.

Example:


angular.module('myApp').controller('myController', function($scope, $sanitize) {
  $scope.safeHtml = $sanitize('Some bold text'); // Safe HTML
});

Example:


  • Sanitizing User Input: Always sanitize user inputs when accepting HTML, and store raw HTML only if absolutely necessary (e.g., for rich text). Avoid passing untrusted HTML directly to the DOM.

2. CSRF (Cross-Site Request Forgery) Prevention

CSRF attacks occur when a user is tricked into submitting a malicious request (e.g., form submission, AJAX request) to a web application that they are authenticated in, without their knowledge.

Preventing CSRF in AngularJS:

  • Token-Based Authentication: Always use CSRF tokens (anti-forgery tokens) to ensure that requests are coming from a trusted source. Most backend frameworks, such as ASP.NET or Django, offer mechanisms to generate and validate CSRF tokens.

Steps for CSRF Prevention:


angular.module('myApp').config(function($httpProvider) {
  $httpProvider.defaults.headers.common['X-CSRF-TOKEN'] = 'your-csrf-token';
});
  • Use of SameSite Cookies: Set the SameSite attribute on cookies to prevent cross-site requests from being automatically included in requests. This adds an additional layer of protection.

Setting SameSite Cookie:


document.cookie = "SESSIONID=your-session-id; SameSite=Strict";
  • Secure HTTP Headers: Use X-Content-Type-Options, X-Frame-Options, and Content-Security-Policy headers to protect your app against attacks.

Setting Headers:


$httpProvider.defaults.headers.common['X-Content-Type-Options'] = 'nosniff';
$httpProvider.defaults.headers.common['X-Frame-Options'] = 'DENY';
  • CSRF Protection with HTTP Interceptors: AngularJS allows you to set global HTTP headers or manipulate the request/response using interceptors. You can use an interceptor to add CSRF tokens to every HTTP request.

Example:


angular.module('myApp')
  .factory('csrfInterceptor', function($q) {
    return {
      request: function(config) {
        // Add CSRF token to request header
        config.headers['X-CSRF-TOKEN'] = 'your-csrf-token';
        return config;
      },
      responseError: function(rejection) {
        // Handle response errors
        return $q.reject(rejection);
      }
    };
  })
  .config(function($httpProvider) {
    $httpProvider.interceptors.push('csrfInterceptor');
  });

3. Best Practices for Secure Data Handling

Data security is crucial in any web application, especially when handling sensitive information like user credentials, payment data, and personal details. Following best practices can ensure data security and privacy.

Secure Data Handling Best Practices:

  • Use HTTPS: Ensure that your AngularJS application communicates with the server over HTTPS to encrypt data during transmission. This prevents attackers from intercepting or tampering with the data.

Example:


// Make sure to use HTTPS in your app URLs and API requests
var apiUrl = 'https://secure-api.example.com/data';
  • Sanitize User Inputs: Always validate and sanitize user inputs on both the client-side and server-side to prevent SQL injection and other attacks. Use AngularJS's built-in ng-pattern for validating user input patterns (e.g., email format, password strength).

Example:



  • Avoid Storing Sensitive Data in Local Storage: Avoid storing sensitive information like passwords or tokens in local storage or session storage. Instead, store sensitive data in a secure server session and use token-based authentication for access.

Password Hashing: Ensure that user passwords are hashed before being sent to the server. Use a strong cryptographic hashing algorithm (e.g., bcrypt) to hash passwords.

Example:


// Never send raw passwords, always hash them securely on the server
  • Access Control: Implement role-based access control (RBAC) and ensure that users only have access to data or features that they are authorized to access.

Example:


if (userRole === 'admin') {
  // Show admin features
}

4. Practical Example: Securing User Authentication

In a typical AngularJS application, user authentication is often implemented with token-based authentication (JWT, for instance). Here’s how to securely handle authentication in AngularJS.

1. User Login and Token Storage

Login Controller: After the user submits their credentials, send a request to the backend to authenticate the user and receive a token.

Example:


angular.module('myApp').controller('LoginController', function($scope, $http, $window) {
  $scope.login = function() {
    const userCredentials = {
      username: $scope.username,
      password: $scope.password
    };

    $http.post('/api/login', userCredentials)
      .then(function(response) {
        // On success, store the token in sessionStorage
        $window.sessionStorage.setItem('authToken', response.data.token);
      })
      .catch(function(error) {
        // Handle login failure
        console.error('Login failed', error);
      });
  };
});

2. Secure API Requests with Tokens

HTTP Interceptor: Use an HTTP interceptor to automatically attach the stored token to all API requests.

Example:


angular.module('myApp')
  .factory('authInterceptor', function($window) {
    return {
      request: function(config) {
        const token = $window.sessionStorage.getItem('authToken');
        if (token) {
          config.headers['Authorization'] = 'Bearer ' + token;
        }
        return config;
      }
    };
  })
  .config(function($httpProvider) {
    $httpProvider.interceptors.push('authInterceptor');
  });

AngularJS Testing

Testing is a crucial part of building reliable and maintainable AngularJS applications. AngularJS provides powerful tools for testing, such as Jasmine for unit testing and Karma for running tests in real browsers. This section covers how to write unit tests for AngularJS components and services, using Jasmine and Karma for test automation.

1. Unit Testing AngularJS Components

Unit testing in AngularJS ensures that each part of your application (such as controllers, services, directives, etc.) behaves as expected in isolation. For unit testing, AngularJS has a module called ngMock that helps you mock dependencies and test the application without needing to load an entire server or external resources.

Basic Unit Test Setup in AngularJS

Include Required Libraries:

  • Jasmine: A framework for writing tests.
  • Karma: A test runner.
  • AngularJS mock: For mocking dependencies.
Sample AngularJS Component (Controller):

angular.module('myApp', [])
  .controller('MyController', function($scope, $http) {
    $scope.greeting = "Hello, World!";
    
    $scope.getData = function() {
      $http.get('/api/data').then(function(response) {
        $scope.data = response.data;
      });
    };
  });
Unit Test for the Controller:

describe('MyController', function() {
  var $controller, $rootScope, $scope, $httpBackend;
  
  beforeEach(module('myApp'));  // Load the AngularJS module

  beforeEach(inject(function(_$controller_, _$rootScope_, _$httpBackend_) {
    // Inject dependencies
    $controller = _$controller_;
    $rootScope = _$rootScope_;
    $httpBackend = _$httpBackend_;
    
    $scope = $rootScope.$new();
    $controller('MyController', { $scope: $scope, $http: $httpBackend });
  }));

  it('should initialize greeting to "Hello, World!"', function() {
    expect($scope.greeting).toEqual("Hello, World!");
  });

  it('should get data from API', function() {
    $httpBackend.whenGET('/api/data').respond(200, { name: 'John Doe' });

    $scope.getData();
    $httpBackend.flush();  // Simulate the server response

    expect($scope.data.name).toEqual('John Doe');
  });
});

In the above code:

  • We use beforeEach to set up the environment, mock HTTP requests, and inject dependencies.
  • We verify the behavior of the controller's methods and scope values using Jasmine's expect assertions.

2. Using Jasmine for Unit Testing AngularJS Controllers and Services

Jasmine is a behavior-driven development framework for testing JavaScript code. It provides functions like describe(), it(), and beforeEach() to structure the tests.

Unit Testing AngularJS Services

A service in AngularJS is typically used to encapsulate business logic and provide data access. Let's say we have the following service:


angular.module('myApp')
  .service('DataService', function($http) {
    this.getData = function() {
      return $http.get('/api/data');
    };
  });
Test for DataService:

describe('DataService', function() {
  var DataService, $httpBackend;

  beforeEach(module('myApp'));

  beforeEach(inject(function(_DataService_, _$httpBackend_) {
    DataService = _DataService_;
    $httpBackend = _$httpBackend_;
  }));

  it('should fetch data from API', function() {
    $httpBackend.whenGET('/api/data').respond(200, { name: 'John Doe' });

    DataService.getData().then(function(response) {
      expect(response.data.name).toEqual('John Doe');
    });

    $httpBackend.flush();  // Simulate the server response
  });

  it('should handle error when API call fails', function() {
    $httpBackend.whenGET('/api/data').respond(500);

    DataService.getData().catch(function(response) {
      expect(response.status).toEqual(500);
    });

    $httpBackend.flush();  // Simulate the server response
  });
});

In the above example:

  • We mock the $httpBackend to simulate API responses.
  • We verify if the data returned by the service matches the expected value.

3. Setting Up Karma for Testing AngularJS Applications

Karma is a test runner that runs tests in real browsers. It helps you automate your tests by running them in real-time as you modify your code. Here’s how to set it up.

Installing Karma

Install Karma and required plugins via npm:


npm install karma karma-jasmine karma-chrome-launcher karma-phantomjs-launcher --save-dev
Initialize Karma:

karma init karma.conf.js

During initialization, select the options to use Jasmine and Chrome (or any other browser of your choice) as the browser.

Configuring Karma

Edit karma.conf.js to include the necessary files for testing. Here's a sample configuration:


module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine'],
    files: [
      'node_modules/angular/angular.js',
      'node_modules/angular-mocks/angular-mocks.js',
      'app/**/*.js',
      'tests/**/*.spec.js'
    ],
    browsers: ['Chrome'],
    reporters: ['progress'],
    singleRun: false,
    autoWatch: true
  });
};

Running Karma Tests

Run your tests with:


karma start karma.conf.js

This will open the Chrome browser (or the one you configured) and run the tests. Any updates to your code will automatically trigger Karma to rerun the tests.

4. Practical Example: Writing Tests for AngularJS Services

Let's extend our example by testing a more complex AngularJS service, such as a UserService that interacts with an API to fetch user details.

UserService Implementation:


angular.module('myApp')
  .service('UserService', function($http) {
    this.getUser = function(userId) {
      return $http.get('/api/users/' + userId);
    };
  });
Test for UserService:

describe('UserService', function() {
  var UserService, $httpBackend;

  beforeEach(module('myApp'));

  beforeEach(inject(function(_UserService_, _$httpBackend_) {
    UserService = _UserService_;
    $httpBackend = _$httpBackend_;
  }));

  it('should fetch user details from API', function() {
    $httpBackend.whenGET('/api/users/1').respond(200, { id: 1, name: 'John Doe' });

    UserService.getUser(1).then(function(response) {
      expect(response.data.name).toEqual('John Doe');
    });

    $httpBackend.flush();  // Simulate the server response
  });

  it('should return error if user not found', function() {
    $httpBackend.whenGET('/api/users/999').respond(404, { message: 'User not found' });

    UserService.getUser(999).catch(function(response) {
      expect(response.status).toEqual(404);
    });

    $httpBackend.flush();  // Simulate the server response
  });
});

In the above test:

  • We simulate a successful response and verify the user details.
  • We also simulate an error (e.g., user not found) and ensure the error is handled correctly.

AngularJS Optimizations

Optimizing AngularJS applications is essential for improving performance, especially as the size of the application grows. AngularJS provides powerful features, but without optimization, these can lead to performance issues, especially with large-scale applications. In this section, we'll explore some common techniques for improving performance in AngularJS applications.

1. Reducing Watchers to Improve Performance

In AngularJS, each binding in the view is tracked by a watcher. As the application grows and the number of watchers increases, the performance of the application can degrade because AngularJS has to check each watcher in every digest cycle.

How to Reduce Watchers

  • Limit the Use of ng-repeat: While ng-repeat is a powerful directive for displaying lists of data, each item in the list creates a watcher. For large lists, this can create significant performance overhead. Consider using pagination or infinite scrolling to limit the number of items being watched at once.


{{ item.name }}
{{ item.name }}
  • Use track by in ng-repeat: When using ng-repeat with large datasets, use the track by expression to help AngularJS track the items efficiently by their identity (e.g., using a unique id), rather than by their reference.

{{ item.name }}
  • Limit the Scope of Watches: Avoid placing watchers on large data structures or arrays directly. You can limit the scope of what needs to be watched by breaking data into smaller parts or using simple objects.

// Instead of watching a large array, watch a specific property or subset.
$scope.$watch('data.items.length', function(newVal, oldVal) {
  // Logic here
});
  • Use one-time bindings: AngularJS supports one-time bindings for data that doesn't change after initialization. This reduces the number of watchers for such bindings.

{{ ::item.name }}

2. Lazy Loading for Faster Initial Load Times

Lazy loading is the technique of loading resources only when they are needed, rather than loading everything upfront. This can significantly improve the initial load time of an AngularJS application.

How to Implement Lazy Loading

  • Lazy Loading Modules: AngularJS allows for lazy loading of modules by using the ngRoute service and configuring routes to load the corresponding controller and view only when needed.

angular.module('myApp')
  .config(function($routeProvider) {
    $routeProvider
      .when('/home', {
        templateUrl: 'home.html',
        controller: 'HomeController'
      })
      .when('/profile', {
        templateUrl: 'profile.html',
        controller: 'ProfileController',
        resolve: {
          loadProfile: function($q, $timeout) {
            var deferred = $q.defer();
            // Simulate lazy loading of the profile module
            $timeout(function() {
              deferred.resolve();
            }, 1000);
            return deferred.promise;
          }
        }
      });
  });
  • Lazy Load JavaScript Files: You can load JavaScript files lazily using ng-include or dynamically load scripts only when certain components or views are accessed.


  • Split Large Scripts into Smaller Files: Split your JavaScript code into smaller modules and load them when necessary. Tools like Webpack or Browserify can help you bundle your scripts and split them into chunks.

3. Optimizing Data Binding

Data binding is one of the core features of AngularJS. However, if not handled properly, it can become inefficient and negatively impact performance, especially when dealing with complex data structures.

How to Optimize Data Binding

  • Use One-Way Data Binding When Possible: Two-way data binding (ng-model) is convenient but comes with the overhead of continuously tracking changes in both directions. If the value only needs to be passed in one direction (from the controller to the view), use one-way data binding ({{expression}}) instead.


{{ user.name }}
  • Limit Digest Cycles: AngularJS performs a digest cycle to update the DOM with changes in the model. In large applications, digest cycles can become slow. Avoid unnecessary model changes and try to limit the number of times the digest cycle is triggered. Use ng-if and ng-show to conditionally render elements rather than hiding them with CSS.
  • Use ng-repeat with caution: Although ng-repeat is a useful directive, it creates a watcher for each iteration, which can be performance-intensive. Consider using track by to optimize it, as mentioned earlier.
  • Use $scope.$apply() Properly: AngularJS performs automatic two-way data binding when model values change. However, if you're making changes outside AngularJS’s digest cycle (e.g., when working with third-party libraries), you need to manually trigger $scope.$apply() to inform AngularJS of the changes.

// Example of manually triggering the digest cycle
$scope.$apply(function() {
  $scope.data = 'New Value';
});

4. Practical Example: Optimizing a Real-World AngularJS App for Performance

Let’s consider an example of a real-world AngularJS application that fetches data from an API and displays it in a list. We will apply the performance optimization techniques discussed above to enhance the app's performance.

Example Scenario:

We have an app that displays a list of items fetched from an API. The list is very large, and we need to optimize it for performance.

Initial Code (Non-Optimized):


  • {{ item.name }}

angular.module('myApp', [])
  .controller('ItemController', function($scope, $http) {
    $http.get('/api/items').then(function(response) {
      $scope.items = response.data;
    });
  });

In this example:

  • The ng-repeat directive creates a watcher for each item in the list, which could degrade performance if the list is large.

Optimized Code:

  • Lazy Loading: Only load a small set of items at a time using pagination or infinite scroll.

  • {{ item.name }}

angular.module('myApp', [])
  .controller('ItemController', function($scope, $http) {
    $scope.visibleItems = [];
    $scope.loadMore = function() {
      $http.get('/api/items?page=' + $scope.page).then(function(response) {
        $scope.visibleItems = $scope.visibleItems.concat(response.data.items);
        $scope.page++;
      });
    };
  });
  • One-Time Binding: Use :: to disable watchers for static data.

{{ ::item.name }}
  • Track by: Use track by in ng-repeat to improve the performance of the list rendering.

  • {{ item.name }}
  • In this optimized version:

    • Pagination is used to load a small subset of data at a time, reducing the number of watchers and improving initial load times.
    • One-time bindings are used for static data to prevent unnecessary updates.
    • The track by feature optimizes the rendering of repeated items by uniquely identifying them.

    Building a Complete AngularJS Application

    In this section, we will walk through building a complete AngularJS application using the various features and techniques we've discussed so far. We'll create a Todo List application, which will allow users to add, remove, and mark tasks as completed. This project will include working with directives, controllers, services, handling forms, performing validations, making API calls, and finally, deploying the application.

    1. Practical Project: Building a Todo List Application

    Let's start by building the application structure. We’ll build a simple Todo List app where users can add tasks, mark tasks as completed, and delete tasks.

    Step 1: Project Setup

    Create the Project Directory:

    
    my-todo-app/
    ├── index.html
    ├── app.js
    ├── todo.html
    └── style.css
    

    Include AngularJS:

    In the index.html file, include AngularJS.

    
    
    
    
      
      Todo List Application
      
      
      
    
    
      

    2. Working with Directives, Controllers, and Services

    Step 2: Define the AngularJS Application

    In app.js, define the AngularJS module and configure routing if necessary.

    
    var app = angular.module('todoApp', ['ngRoute']);
    
    // Configure routing for the application
    app.config(function($routeProvider) {
      $routeProvider
        .when('/', {
          templateUrl: 'todo.html',
          controller: 'TodoController'
        })
        .otherwise({
          redirectTo: '/'
        });
    });
    

    Step 3: Create a Controller

    Define the TodoController to manage the data and logic of the application.

    
    app.controller('TodoController', function($scope, TodoService) {
      $scope.todos = TodoService.getTodos();
      
      $scope.addTodo = function() {
        if ($scope.todoText) {
          TodoService.addTodo({
            text: $scope.todoText,
            completed: false
          });
          $scope.todoText = ''; // Clear the input field
        }
      };
    
      $scope.toggleTodo = function(todo) {
        TodoService.toggleTodo(todo);
      };
    
      $scope.removeTodo = function(todo) {
        TodoService.removeTodo(todo);
      };
    });
    

    Step 4: Create a Service

    A service will be responsible for managing the data (adding, toggling, and removing todos). In app.js, define a simple TodoService.

    
    app.factory('TodoService', function() {
      var todos = [];
    
      return {
        getTodos: function() {
          return todos;
        },
        addTodo: function(todo) {
          todos.push(todo);
        },
        toggleTodo: function(todo) {
          todo.completed = !todo.completed;
        },
        removeTodo: function(todo) {
          var index = todos.indexOf(todo);
          if (index !== -1) {
            todos.splice(index, 1);
          }
        }
      };
    });
    

    3. Handling Forms, Validations, and API Calls

    Step 5: Creating the Todo List View

    Now, we will define the todo.html file, which will contain the user interface for adding tasks, listing tasks, and toggling the completion status.

    
    

    Todo List

    • {{ todo.text }}

    Step 6: Adding CSS for Basic Styling

    In style.css, add some basic styling to the app.

    
    body {
      font-family: Arial, sans-serif;
    }
    
    .todo-container {
      width: 300px;
      margin: 0 auto;
      padding: 20px;
      border: 1px solid #ccc;
      border-radius: 5px;
    }
    
    h1 {
      text-align: center;
    }
    
    input[type="text"] {
      width: 80%;
      padding: 5px;
    }
    
    button {
      padding: 5px;
      background-color: #4CAF50;
      color: white;
      border: none;
      cursor: pointer;
    }
    
    button:hover {
      background-color: #45a049;
    }
    
    .completed {
      text-decoration: line-through;
    }
    

    4. Deploying the AngularJS Application

    Step 7: Deploying to a Server

    Once the app is ready, it’s time to deploy it. You can deploy an AngularJS application in a few different ways depending on your choice of hosting service.

    Using GitHub Pages (for static websites):

    • Create a GitHub repository and push your application to it.
    • Go to the repository’s settings and enable GitHub Pages for the main or master branch.

    Using Firebase Hosting:

    • Install Firebase CLI using npm install -g firebase-tools.
    • Initialize Firebase Hosting in your project directory using firebase init.
    • Deploy the application using firebase deploy.

    Using Heroku (for backend integration):

    • If your application requires server-side integration (e.g., using Node.js), you can deploy it to Heroku.
    • To deploy on Heroku, you would:
      • Create a package.json file for your Node.js app.
      • Push the app to a Heroku repository.
      • Use the Heroku CLI to deploy.

    5. Practical Example: Handling API Calls

    Let’s make this Todo List application even more practical by adding the functionality to save and retrieve tasks from an API.

    Step 8: Making API Calls with $http

    Modify the TodoService to interact with an API using AngularJS's $http service. For example, let’s simulate a backend API for storing todos.

    
    app.factory('TodoService', function($http) {
      var todos = [];
    
      return {
        getTodos: function() {
          // Get data from an API (this is a simulated API endpoint)
          $http.get('https://jsonplaceholder.typicode.com/todos')
            .then(function(response) {
              todos = response.data;
            });
          return todos;
        },
        addTodo: function(todo) {
          $http.post('https://jsonplaceholder.typicode.com/todos', todo)
            .then(function(response) {
              todos.push(response.data);
            });
        },
        toggleTodo: function(todo) {
          todo.completed = !todo.completed;
          // Optionally update the todo on the server
          $http.put('https://jsonplaceholder.typicode.com/todos/' + todo.id, todo);
        },
        removeTodo: function(todo) {
          var index = todos.indexOf(todo);
          if (index !== -1) {
            todos.splice(index, 1);
            // Optionally delete the todo on the server
            $http.delete('https://jsonplaceholder.typicode.com/todos/' + todo.id);
          }
        }
      };
    });
    

    Conclusion

    You have now built a simple but complete AngularJS Todo List application. This project includes working with:

    • Directives: Used in the form and list rendering.
    • Controllers: Managing the application's logic.
    • Services: For interacting with data and APIs.
    • Forms and Validations: Handling form inputs.
    • API Calls: Making HTTP requests to save and retrieve todos.
    • Deployment: Methods to deploy your AngularJS app to a web server.

    This is just a basic introduction to AngularJS application development. You can expand this application with features like authentication, persistence with a real backend, or more complex routing.

    Share This :

    Leave a Comment

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

    Scroll to Top