-1

How to pass values between controllers in AngularJS

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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí