Updating HTML element outside of ng-view

All we need is an easy explanation of the problem, so here it is.

I have the below HTML on a page:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" ng-app="myCart">
<head>
    <title>AngularJS Shopping Cart</title>
    <link href="css/jsonstore.css" rel="nofollow noreferrer noopener" rel="stylesheet" />
</head>
<body>
    <div id="container">
        <div id="header">
            <h1>The JSON Store</h1>
            <div class="cart-info">
                My Cart (<span class="cart-items">{{item.basketCount}}</span> items)
            </div>
        </div>
        <div id="main" ng-view>
        </div>
    </div>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular-resource.js"></script>
    <script src="js/routing.js"></script>
    <script src="js/dataresource.js"></script>
    <script src="js/basketinfo.js"></script>
    <script src="js/index.js"></script>
    <script src="js/detail.js"></script>
</body>
</html>

The div “main” gets replaced by HTML templates by my routes however I would like to update the header section with a shopping basket count.

I have tried model binding it as shown in the HTML and below:

function DetailController($scope, item, basketDetail) {
    $scope.item = item;
    $scope.item.basketCount = basketDetail.getCount();

    //more code
}

I’ve also tried just injecting the service and calling it from the HTML. Both ways do not do anything.

Can someone help please?

Thanks

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

Your header div is really a view, just like the other views you’ve defined for use with ng-view. Someday, you might want to show more than just a basketCount model in that header view. But the fact that you are projecting even one piece of model data into that header section makes that section a view. So, I would recommend that be given its own $scope to project that model, hence its own controller.

What remains then is where to put the basketCount model? And we must consider that multiple views may allow the user to do something that need to affect that model. Angular’s normal answer for “many need access” is dependency injection. So, I would put the basketCount model into a service. Views/controllers that need access to it can inject it. Someday your app may have additional views that don’t need access to these models, so those views would not inject the service.

Potentially, the entire basket could be modeled in this service:

app.factory('basketService', function() {
   return {
     items: [],
     itemCount: 0,
     ...
   }
});


function HeaderCtrl($scope, basketService) {
   $scope.basket = basketService;
   ...
}

function DetailCtrl($scope, basketService) {
   $scope.basket = basketService;
   ...
}

<div id="header" ng-controller="HeaderCtrl">
   <h1>The JSON Store</h1>
   <div class="cart-info">
        My Cart (<span class="cart-items">{{basket.itemCount}}</span> items)
   </div>

Method 2

You’ll need to inject $rootScope, then update it:

function DetailController($scope, $rootScope, basketDetail) {
    $rootScope.item = $rootScope.item || {};
    $rootScope.item.basketCount = basketDetail.getCount();

    //more code
}

There are a lot more ways to do this, but this is my first suggestion, because it’s probably the easiest.


EDIT: per your request, other ways to do this…

You could use $parent to push to your parent scope. The upside is it’s pretty quick and clean… the downside is it’s a little sloppy in that one of your controllers makes assumptions about what it’s parent is to some degree (but that’s still not terrible, really):

   {{item.basketCount}}
   <div ng-controller="InnerCtrl">
   </div>


function InnerCtrl($scope, basketDetail) {
   $scope.$parent.item = $scope.$parent.item || {};
   $scope.$parent.item.basketCount = basketDetail.getCount();
}

Then there’s the method @asgoth mentioned above where you use nested controller and a method on the parent scope to update the parent scope. Valid, but like my other solution in this “other ways to do it” section, it relies on assumptions made about the controller’s container, and it also relies on you creating an additional controller.

Finally, you could create a service. Now services aren’t generally used this way, but you could use one this way.. Where you could take your basketDetail service, and use it to pass the value back and forth. Like so:

app.factory('basketDetail', function() {
   return {
      items: { basketCount: 0 },
      getCount: function() {
          //return something here
          return 123;
      }
   }
});


function FooCtrl($scope, basketDetail) {
   $scope.items = basketDetail.items;
   $scope.items.basketCount = basketDetail.getCount();
}

function BarCtrl($scope, basketDetail) {
   $scope.items = basketDetail.items;
}


<div ng-controller="FooCtrl">
  {{items.basketCount}}
</div>
<div ng-controller="BarCtrl">
  {{items.basketCount}}
</div>

This works because the $scope in both controllers is keeping a reference to the same object, which is maintained by your basketDetail service. But again, this isn’t really the recommended way.

All of that said: $rootScope, IMO is most likely what you’re looking for.

  • It doesn’t require the creation of an additional controller.
  • It doesn’t require the creation of any additional function references.
  • Will not cause the creation of any additional parent/child Scope nesting and subsequent watches.

Method 3

No real need for $rootScope. Create a parent controller (e.g. RootController) with a function on its scope. The child scopes will automatically inherit it:

<div id="container" ng-controller="RootController">
...


function RootController($scope) {
   $scope.item = {};

   $scope.setBasketCount = function (detail) {
      $scope.item.basketCount = detail.getCount();
   }
}

In your detail controller you just use the setBasketCount() function:

function DetailController($scope, item, basketDetail) {
    $scope.item = item;
    $scope.setBasketCount(basketDetail);

    //more code
}

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply