How to pass values between controllers in AngularJS
Bài đăng này đã không được cập nhật trong 9 năm
Sometimes we are in a situation where there is a need to pass values between controllers in AngularJS app. In this post I will show a way to do that by:
Using factory or service to store data
I think this is the best way to share values between controllers. So let's us create an example app to demonstrate why it is the best way compared to other methods.
I am going to create a simple demo app which you can follow along. I assume the reader of this post has a basic understanding of how AngularJS, CSS, Javascript or HTML works.
<!DOCTYPE html>
<html>
<head>
<title>Demo App</title>
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
<script type="text/javascript"
src="http://code.angularjs.org/1.2.28/angular.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.10/angular-ui-router.js"></script>
</head>
<body ng-app="demoApp">
</body>
</html>
I am using bootstrap css, with AngularJS (angular.min.js) the core of AngularJS code and angular-ui-router.js to manage routing from page to page. You have to have internet connection for above scripts to download. You can download the source files and include them locally. Let's save the above code in a file: index.html
you can run the above code through command line in Linux:
google-chrome index.html
or if you use firefox
firefox index.html
or if you use Window, just open the file index.html
.
You see nothing now. I also create app.js
file is the brain behind the scene. Notice that in this demo app, for the sake of simplicity, I use only one file to include all controllers, services, configurations, which normally should have its own seperate files.Let's use create app.js
to create configution for routing and one controllers.
angular.module("demoApp",['ui.router'])
.config(["$stateProvider", "$urlRouterProvider",
function($stateProvider, $urlRouterProvider){
$stateProvider
.state("home",{
url: "/home",
templateUrl: "/index.html",
controller: "BookController"
});
$urlRouterProvider.otherwise("home");
}])
.controller("BookController",[ "$scope", function($scope) {
var books = [
{title: "Awakening of intelligence", author: "Krishnamurti", price: 40, orderQuantity: 0},
{title: "AngularJS cookBook", author: "Ari Lerner", price: 30, orderQuantity: 0},
{title: "C++ Programming", author: "Bjane Stroustroup", price: 35, orderQuantity: 0},
{title: "Java Programming", author: "Daniel Liang", price: 37, orderQuantity: 0},
{title: "Ruby on Rails", author: "Michael Hartl", price: 38, orderQuantity: 0}
];
$scope.books = books;
}]);
Here I just create a module demoApp
which inject ui.router to manage routing and view. I create a state called home with url: /home
and templeateUrl index.html
. I also create a controller named BookController
which initialize values for books
. books
has property title, author, price and orderQuantiy(normally we don't
You might wonder why I did not assign $scope.books
to the array directly. You will understand it as you go along.
Now let's add some HTML to index.html
in the <body>
tag to create a list of books. We create a template with id="/index.html
and put it in the <script>
tag.
<body ng-app="demoApp">
<div class="container">
<div class="row">
<div class="col-md-9 col-md-offset-3">
<ui-view></ui-view>
</div>
</div>
</div>
<script type="text/ng-template" id="/index.html">
<div class="page-header">
<h1>Passing values between controllers </h1>
</div>
<div class="panel panel-default" ng-controller="BookController">
<div class="panel-body">
<table class="table">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>Price</th>
<th>Ordered Quantity</th>
<th>Add to Cart</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="book in books">
<td>{{book.title}}</td>
<td>{{book.author}}</td>
<td>{{book.price}}</td>
<td >{{book.orderQuantity}}</td>
<td ng-controller = "CartController"><button ng-click="addToCart(book)">Add to Cart </button></td>
</tr>
</tbody>
</table>
</div>
</div>
</script>
</body>
The template above will be automatically rendered in the place of <ui-view>
tag. In real world development we would create a separate file for the templates to make the code look tidier easier to develop and collaborate with other. But we put all HTML in a single file to simplify the demonstration.
We will see a list of 5 books in the browers now by using ng-repeat
directive. You may have noticed that we create a button Add to Cart
so that we can buy some books. We put the CartController
using directive ng-controller
which we will create next in the file app.js
.
.controller("CartController",
["$scope",
function($scope) {
$scope.addToCart = function(book){
};
}]);
Note that this file is put below BookController
section (but you have to delete the semi-colon at the end of it, so that you have only one semi-colon at the end of the file).
Now the main point of this post is that when you click Add to Cart
button on any book that you want to buy, each book bought will be stored on a service so that it can be retrieved later by other controllers. Now we will create a service and put it right after config
function.
.service("orderBookService",[function(){
var obj = {
orderBooks: [],
totalBooks: 0,
totalAmount: 0
};
obj.addBooks = function(newBook) {
for(i=0; i < obj.orderBooks.length;i++){
if(obj.orderBooks[i].title == newBook.title){
obj.orderBooks[i].orderQuantity += 1;
obj.totalAmount += obj.orderBooks[i].price;
obj.totalBooks += 1;
return;
}
}
obj.orderBooks.push(newBook);
obj.orderBooks[i].orderQuantity += 1;
obj.totalAmount += obj.orderBooks[i].price;
obj.totalBooks += 1;
};
return obj;
}])
Now let's go step by step with the code above. we create an object obj
which has attribute orderBooks
which is an array of books ordered, totalBooks
number of books ordered, totalAmount
amount to be paid for all books ordered.
In function addBooks
we iterate through orderBooks
and see if the book you want to buy is already in orderBooks
, if so, we increase orderQuantity
by 1, increase the price by the price of the book and totalBooks
by 1. Otherwise if the book not exists in the orderBooks
yet, we add the book in.
Note that the value of orderBooks
, totalBooks
and totalAmount
are stored in memory for easy access later on by other controllers.
Now let's inject service orderBookService
into each controller and create appropriate functions respectively.
controller("BookController",[ "$scope","orderBookService", function($scope, orderBookService) {
.
.
.
}
.controller("CartController",
["$scope","orderBookService",
function($scope,orderBookService) {
$scope.order = orderBookService;
$scope.addToCart = function(book){
orderBookService.addBooks(book);
};
}]);
We set $scope.order = orderBookService
so that we have order
bind to the same object as orderBookService
, whatever value in orderBookService
object will be automatically update to order
and vice versa. we complete the function addToCart
by using service function addBooks(book)
.
Now we have to create a new state for our function config
to navigate to another template cart.html
, which we will create soon.
.state("cart",{
url: "/cart",
templateUrl: "/cart.html",
controller: "CartController"
});
Note that we use different controller for each state.
Let's now create template cart.html
and put it after template index.html
.
<script type="text/ng-template" id="/cart.html">
<div class="page-header">
<h1>Book Invoice</h1>
</div>
<div class="panel panel-default" ng-controller="CartController">
<div class="panel-body">
<table class="table">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>Price</th>
<th>Quantity</th>
<th>Amount</th>
</tr>
</thead>
<tfoot>
<tr>
<td>Total</td>
<td> </td>
<td> </td>
<td></td>
<td>{{order.totalAmount}}</td>
</tr>
</tfoot>
<tbody>
<tr ng-repeat="book in order.orderBooks" ng-if="book.orderQuantity != 0">
<td>{{book.title}}</td>
<td>{{book.author}}</td>
<td>{{book.price}}</td>
<td>{{book.orderQuantity}}</td>
<td> {{book.price * book.orderQuantity}}</td>
</tr>
</tbody>
</table>
</div>
<div>
<a href="#/home">Back home</a>
</div>
</div>
</script>
We create a table of books we have just ordered. Here we also use directive ng-repeat to iterate through each book.
Now we can see how data are passed between controllers using a service as a bridge.
We can also create a navigation bar to show the total number of books ordered. We create a file nav.html
and include it in index.html
.
<div class="navbar-collapse navbar-fixed-top navbar-inverse navbar" ng-controller="CartController">
<ul class="nav navbar-nav pull-right">
<li><a>Demo App on Passing value between controllers</a></li>
<li><a href="#/cart">Cart: {{ order.totalBooks }}</a></li>
</ul>
</div>
And include it by inserting the below code above <ui-view>
tag.
<div ng-include="'nav.html'"></div>
and also put a link to template cart.html
below the table in template index.html
</table>
</div>
<div>
<a href="#/cart" class="btn btn-default btn-primary">Proceed to checkout</a>
</div>
Now our demoApp is almost complete now. We see in the browser that when we add a book to cart we see each correspondent book's order Quantiy is increased and also a total of books ordered is updated in the navigation bar.
when we go back and forth between index.html
page and cart.html
page the value remain there( of course, except when refresh the page). But wait! The values of orderBooks are stored, $scope.books
are reset every time the page loads. We can make some change to make it preserve its state.
In BookControler
we add some code:
if($scope.books == "")
$scope.books = books;
else{
$scope.books = orderBookService.getBooks();
for(i=0;i<books.length;i++){
var flag = true;
for(j=0;j<$scope.books.length;j++){
if(books[i].title == $scope.books[j].title){
flag = false;
break;
}
}
if(flag){
$scope.books.push(books[j]);
}
}
}
Now template index.html
preserve its state, so that we can come back and add more book to the cart.
We have a nice little app! I hope you enjoy the learning. I welcome for any comments. Thanks for following the post.
All rights reserved