In AngularJS, asynchronous operations like API calls, file uploads, and event handling require a way to manage execution flow efficiently. The $q
service provides a Promise-based API to handle such operations effectively.
This guide covers: What $q
is and why it’s used
How to create and use Promises with $q
Chaining multiple Promises
Handling errors in Promises
Best practices for working with $q
1. Understanding $q
and Promises
What is $q
in AngularJS?
$q
is AngularJS’s built-in Promise library that helps manage asynchronous operations. It allows us to:
- Execute multiple asynchronous tasks in order.
- Wait for tasks to complete before proceeding.
- Handle errors efficiently.
How a Promise Works
A Promise represents an eventual result of an operation:
- Pending: Initial state (before completion or failure).
- Resolved (Fulfilled): Operation completed successfully.
- Rejected: Operation failed.
2. Creating a Promise using $q
The $q
service provides the .defer()
method to create a Promise.
Example: Creating a Simple Promise
var app = angular.module("myApp", []);
app.controller("PromiseController", function($scope, $q) {
function asyncTask() {
var deferred = $q.defer();
setTimeout(function() {
if (Math.random() > 0.5) {
deferred.resolve("Success: Data loaded!");
} else {
deferred.reject("Error: Failed to load data.");
}
}, 2000);
return deferred.promise;
}
$scope.loadData = function() {
asyncTask().then(
function(response) {
$scope.result = response;
},
function(error) {
$scope.result = error;
}
);
};
});
HTML
<div ng-app="myApp" ng-controller="PromiseController">
<button ng-click="loadData()">Load Data</button>
<p>{{ result }}</p>
</div>
How it works?
asyncTask()
creates a Promise using$q.defer()
.setTimeout()
simulates an asynchronous operation.- The operation resolves or rejects randomly.
- The
loadData()
function callsasyncTask()
and updates the UI.
3. Using $q
for API Calls
Instead of handling API responses directly inside controllers, use $q
to wrap $http
requests.
Example: Fetching User Data
app.factory("UserService", function($http, $q) {
return {
getUsers: function() {
var deferred = $q.defer();
$http.get("https://jsonplaceholder.typicode.com/users")
.then(function(response) {
deferred.resolve(response.data);
})
.catch(function(error) {
deferred.reject("Error: Could not fetch users.");
});
return deferred.promise;
}
};
});
app.controller("UserController", function($scope, UserService) {
$scope.fetchUsers = function() {
UserService.getUsers().then(
function(users) {
$scope.users = users;
},
function(error) {
$scope.errorMessage = error;
}
);
};
});
HTML
<div ng-app="myApp" ng-controller="UserController">
<button ng-click="fetchUsers()">Fetch Users</button>
<ul>
<li ng-repeat="user in users">{{ user.name }}</li>
</ul>
<p>{{ errorMessage }}</p>
</div>
Why use $q
here?
- Improves code structure by separating API logic into a service.
- Enables chaining multiple requests efficiently.
4. Chaining Multiple Promises
Chaining Promises allows you to execute sequential asynchronous tasks.
Example: Fetch Users, Then Fetch Posts
app.factory("DataService", function($http, $q) {
return {
getUsersAndPosts: function() {
var deferred = $q.defer();
$http.get("https://jsonplaceholder.typicode.com/users").then(
function(userResponse) {
var users = userResponse.data;
$http.get("https://jsonplaceholder.typicode.com/posts").then(
function(postResponse) {
var posts = postResponse.data;
deferred.resolve({ users: users, posts: posts });
},
function(error) {
deferred.reject("Error fetching posts.");
}
);
},
function(error) {
deferred.reject("Error fetching users.");
}
);
return deferred.promise;
}
};
});
app.controller("MainController", function($scope, DataService) {
$scope.fetchData = function() {
DataService.getUsersAndPosts().then(
function(result) {
$scope.users = result.users;
$scope.posts = result.posts;
},
function(error) {
$scope.errorMessage = error;
}
);
};
});
Why use Promise chaining?
- Ensures one task completes before starting the next.
- Prevents callback hell (nested callbacks).
5. $q.all()
– Running Multiple Promises in Parallel
Sometimes, you need to execute multiple API calls simultaneously.
Example: Fetch Users and Posts in Parallel
app.factory("ParallelService", function($http, $q) {
return {
fetchAllData: function() {
var userRequest = $http.get("https://jsonplaceholder.typicode.com/users");
var postRequest = $http.get("https://jsonplaceholder.typicode.com/posts");
return $q.all([userRequest, postRequest]).then(
function(results) {
return { users: results[0].data, posts: results[1].data };
}
);
}
};
});
app.controller("ParallelController", function($scope, ParallelService) {
$scope.loadAllData = function() {
ParallelService.fetchAllData().then(
function(result) {
$scope.users = result.users;
$scope.posts = result.posts;
},
function(error) {
$scope.errorMessage = "Error loading data.";
}
);
};
});
Why use $q.all()
?
- Executes API calls simultaneously, improving performance.
- Reduces waiting time when fetching independent data.
6. Handling Errors with $q
Always handle errors properly to improve user experience.
Example: Catching Errors
$http.get("https://jsonplaceholder.typicode.com/users")
.then(function(response) {
console.log("Success:", response.data);
})
.catch(function(error) {
console.error("API Error:", error.status, error.statusText);
});
Best Practices for Error Handling
- Use
.catch()
to manage failures. - Show meaningful error messages to users.