1

Im trying to set a counter for the number of alerts that a user has. If I console.log the $scope.alerts and $scope.count, I can see that both are updating, but using {{alerts.length}} or {{count}} as the value of #msgCount will only give me the initial count on pageLoad, and doesn't update when the length of the array changes. What am I doing wrong?

My controller:

myApp.controller('globalAlerts', ['$scope', '$http', function ($scope, $http) {
        $scope.alerts = [{
    "type": "warning",
    "msg": "This is a generic alert. It is best suited for one or two line messages."
},
{
    "type": "danger",
    "msg": "<strong>Past Due!</strong> You have an invoice <a class=\"invoice-link\" href=\"\">192607</a> that is past due. <a href=\"invoices.html\">View all invoices.</a>"
},
{
    "type": "success",
    "msg": "This green alert indicates something positive occurred. Doesn't that make you feel happy?"
},
{
    "type": "info",
    "msg": "This blue alert is another generic warning usually used to inform. Isn't it soothing?"
}
];
    $scope.$watchCollection('alerts.length', function() {
        return $scope.alerts.length;

    }, true);
    $scope.closeAlert = function(index) {
      $scope.alerts.splice(index, 1);
    };



    $scope.dynamicPopover = {
            templateUrl: 'partials/alerts/globalAlerts.html',
            title: 'Recent Alerts'
    };

}]);

And the markup:

<div id="upperNav" ng-controller = "globalAlerts">
        <a class="btn btn-link" id="msg" popover-placement="bottom" uib-popover-template="dynamicPopover.templateUrl" popover-title="{{dynamicPopover.title}}" >
        <div class="badge badge-warning" id="msgCount">{{alerts.length}}</div>
        ALERTS</a></div>

With the popover markup:

<div id="alertCntr" ng-controller = "globalAlerts">
         <uib-alert ng-show="alerts.length" ng-repeat="alert in alerts track by $index" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>  
         <p ng-hide="alerts.length">You ain't got no more alerts, kid!</p>   
</div>
Christian Hill
  • 378
  • 1
  • 3
  • 17
  • I'm curious as to why you even need the `count` field on your scope. What are you using it for that `alerts.length` can not be used for? – KreepN Mar 08 '16 at 16:28
  • Absolutely nothing if I could get the length to update. I thought it was a solution to get around the length not updating. – Christian Hill Mar 08 '16 at 16:30
  • I set this up for you based off one of my other plnks, but you should get the gist. http://plnkr.co/edit/oMRDpGnXDU4sV1GWNdne?p=preview . It's the same idea, with a removal and lenght being updated with no watches. – KreepN Mar 08 '16 at 16:36
  • Interesting. This is exactly what I started off with. Do you think it's not updating because the ng-repeat is in a dynamic div (eg. popover) that doesn't render with the rest of the page on pageLoad? I thought this was the case, but then how would the main markup pick up the length on pageLoad? – Christian Hill Mar 08 '16 at 16:42

4 Answers4

1

For other users:

The HTML specifies that each chunk has its own controller. This means that the 2 controllers are independent and do not know when each other's alert array has been touched.

The easiest solution is to nest the HTML so they share a controller:

<div id="upperNav" ng-controller = "globalAlerts">
    <a class="btn btn-link" id="msg" popover-placement="bottom" uib-popover-template="dynamicPopover.templateUrl" popover-title="{{dynamicPopover.title}}" >
    <div class="badge badge-warning" id="msgCount">{{alerts.length}}</div>
    ALERTS</a>
    <div id="alertCntr">
       <uib-alert ng-show="alerts.length" ng-repeat="alert in alerts track by $index" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>  
       <p ng-hide="alerts.length">You ain't got no more alerts, kid!</p>   
     </div>
</div>

EDIT:

Updated plnkr that does what you want exactly: http://plnkr.co/edit/AIwGi7eIzilsGGL7Us3G?p=preview

KreepN
  • 8,528
  • 1
  • 40
  • 58
  • So Angular UI requires unsafe HTML to be in a separate folder to render correctly. If I move my code into a ng-template script tag, the popover won't render. https://angular-ui.github.io/bootstrap/#/popover – Christian Hill Mar 08 '16 at 17:02
  • @ChristianHill It would seem that in the doc you provided they have their templates inline. Why do yours need to be in a separate folder? I don't see that mentioned in there. – KreepN Mar 08 '16 at 17:15
  • Its a secondary method because the inline method doesn't support unsafe HTML. If you read the right-hand content, you'll see there is three methods of calling the popover, and the template method is only version that supports both compiled code and unsfafe HTML. – Christian Hill Mar 08 '16 at 17:17
  • @ChristianHill I saw that, but if you check their demo they use `uib-popover-template="dynamicPopover.templateUrl"` which equals `myPopoverTemplate.html` and is defined inline: `` – KreepN Mar 08 '16 at 17:20
  • When I use this method, the popover doesnt render. Im assuming its because the HTML is unsafe and is breaking my code. – Christian Hill Mar 08 '16 at 17:25
0

You have add another parameter after watch function. Like this.

$scope.$watchCollection('alerts.length', function() {
    $scope.count = $scope.alerts.length;
    return $scope.count;
},true);

You have to put another argument (true), which means 'deep watch'. This is applicable when you are watching a nested object or an array of objects.

You can also see here. How to deep watch an array in angularjs?

Btw, there is no use of putting ng-model to a div

<div class="badge badge-warning" id="msgCount">{{alerts.length}}</div>

This is enough.

Community
  • 1
  • 1
Imdadul Huq Naim
  • 364
  • 5
  • 10
  • Thanks. I edited the code above with the same edits I made. I added the true and removed the "count"-related code per KreepN's comment. It's still not updating. – Christian Hill Mar 08 '16 at 16:36
  • @ChristianHill It's most likely tied to the fact that you are spinning up 2 instances of your controller and they do not share data. Meaning, when you close the modal, and it splices, the one displaying the length is unaffected. Your app structure is the root cause of the problem here. – KreepN Mar 08 '16 at 16:41
  • So what would you recommend as a solution? – Christian Hill Mar 08 '16 at 16:43
0

As you want to talk between two controllers, you can either use $broadcast or use service for store the value. I prefer using services, you can use this 'serviceForStoringValue' later in other cases too.

.controller('controller1',function($scope,serviceForStoringValue){
  $scope.getCount = serviceForStoringValue.getCount;
})
 -----------------
.controller('controller2',function($scope,serviceForStoringValue){
  $scope.$watch('',function(n,o){
     serviceForStoringValue.setCount(n.length);
  },true)
})
-----------------
.factory('serviceForStoringValue',function(){
   var count;
   function getCount(){
     return count;
   }
   function setCount(n){
      count = n;
   }

   return {
    getCount:getCount,
    setCount:setCount
   }
 })


<div>{{getCount()}}<div>
Imdadul Huq Naim
  • 364
  • 5
  • 10
0

Many thanks to KreepN for giving me some direction. I finally realized that the controller call in the template was unnecessary since the div rendered within the controller div on the main page. Once I removed that all the content fell within my controller markup and the count worked as expected.

Christian Hill
  • 378
  • 1
  • 3
  • 17
  • Check my edit, i got it all working cause I really wanted to figure it out. If it end up working for you, feel free to accept the answer. Thanks. – KreepN Mar 08 '16 at 19:01