0

My functionality is very simple, and I have to imagine very common. I have a page that lists a collection of 'age groups', and provides functionality to add an 'age group'. I simply would like to have the page immediately reflect the change when a new 'age group' is added.

In early versions, I had simply used the $rootScope in my service to put all 'age groups' in the $rootScope when the $http request was completed. On this latest refactoring I am removing all use of $rootScope in my app, and it is becoming problematic.

The code for the HTML view is as follows: [NOTE: all code considered extraneous has been removed from all code snippets]

<div id="tabContent" class="tab-pane fade in active" ng-show="subIsSelected(1)">
    <div class="row">
        <div class="col-xs-12">
            <form class="form-inline">
                <button type="button" class="btn btn-success" ng-click="openAddAgeGroup()">Add Age Group</button>                  
            </form>
        </div>
    </div>
    <hr />
    <div class="row row-content">                    
        <div class="col-xs-12">
            <h4 ng-show="!areAgeGroups()">No current age groups.</h4>
            <ul class="media-list" ng-show="areAgeGroups()">
                <li class="media" ng-repeat="ageGroup in ageGroups" style="padding:10px; background: lightGray;">
                    <div class="media-left media-top" >
                        <img class="media-object img-thumbnail" style="width: 75px;" ng-src="./images/trfc.png" alt="club logo">
                    </div>
                    <div class="media-body">
                        <h3 class="media-heading" style="padding-top: 20px;">{{ageGroup.name}}</h3>
                        <button type="button" class="btn btn-xs btn-primary" style="width: 50px;" ng-click="openEditAgeGroup(ageGroup)">Edit</button>
                        <button type="button" class="btn btn-xs btn-danger" style="width: 50px;" ng-click="deleteAgeGroup(ageGroup)">Delete</button>
                    </div>
                </li>
            </ul>
        </div>
    </div>
</div>

when first loaded, the page correctly shows all 'age groups' that are in the $scope.ageGroups array.

On clicking the button to add an 'age group', an ng-dialog is created as follows:

$scope.openAddAgeGroup = function() {
    console.log("\n\nOpening dialog to add age group");
    ngDialog.open({ template: 'views/addAgeGroup.html', scope: $scope, className: 'ngdialog-theme-default', controller:"HomeController" });
};

and that dialog is populated as such:

<div class="ngdialog-message">
    <div class="">
        <h3>Add a New Age Group</h3>
    </div>
    <div>&nbsp;</div>
    <div>
        <form ng-submit="addAgeGroup()">
            <div class="form-group">
                <label class="sr-only" for="name">Age Group Display Name</label>
                <input type="text" class="form-control" id="name" placeholder="age group name" ng-model="ageGroupForm.name">
            </div>
            <div class="form-group">
                <label class="sr-only" for="birthyear">Birth Year</label>
                <input type="text" class="form-control" id="birthyear" placeholder="birth year" ng-model="ageGroupForm.birthyear">
            </div>
            <div class="form-group">
                <label class="sr-only" for="socceryear">Soccer Year</label>
                <div class="input-group">
                    <div class="input-group-addon">U</div>
                    <input type="text" class="form-control" id="socceryear" placeholder="soccer year" ng-model="ageGroupForm.socceryear"> 
                </div>                               
            </div>
            <button type="submit" class="btn btn-info">Add</button>
            <button type="button" class="btn btn-default" ng-click=closeThisDialog("Cancel")>Cancel</button>
        </form>
    </div>
</div>

When the form is submitted, the 'age group' is added to the database, from the controller:

'use strict';

angular.module('ma-app')
    .controller('HomeController', ['$scope', 'ngDialog', '$state', 'authService', 'coreDataService', 'userService', '$rootScope', 'clubService', 'schedulingService', function($scope, ngDialog, $state, authService, coreDataService, userService, $rootScope, clubService, schedulingService) {

...

    $scope.addAgeGroup = function() {
        coreDataService.addAgeGroup($scope.ageGroupForm)
            .then(function(response) {
                coreDataService.refreshAgeGroups()
                    .then(function(response) {
                        coreDataService.setAgeGroups(response.data);
                        $scope.ageGroups = coreDataService.getAgeGroups();
                        console.log("\n\n\nretrieved age groups and put them in scope");
                        console.log($scope.ageGroups);
                        ngDialog.close();
                    }); 
            }, function(errResponse) {
                console.log("Failed on attempt to add age group:");
                console.log(errResponse);
            });   
    };

The coreDataService is defined as follows:

'use strict';

angular.module('ma-app')

    .service('coreDataService', ['$http', 'baseURL', 'googleGeolocateBaseURL', 'googleGeocodeKey', 'googleMapsBaseURL', function($http, baseURL, googleGeolocateBaseURL, googleGeocodeKey, googleMapsBaseURL) {
        var ageGroups = {};
        var ageGroupsLoaded = false;       


        this.getAgeGroups = function() {
            return ageGroups;
        };

        this.setAgeGroups = function(newAgeGroups) {
            ageGroups = newAgeGroups;
            ageGroupsLoaded = true;
        };

        this.addAgeGroup = function(formData) {
            //post age group:            
            var postString = '{ "birth_year": "' + formData.birthyear + '", "soccer_year": "U' + formData.socceryear + '", "name": "' + formData.name + '" }';
            console.log("Posting age group with string: " + postString);
            return $http({
                url: baseURL + 'age_groups/',
                method: 'POST',
                headers: {
                    'content-type': 'application/json' 
                },
                data: postString
            });
        };      
    }]);

So, when an 'age group' is added, console logging indicates that the new 'age group' is in the collection now stored in the $scope.ageGroups array, but the HTML's ng-repeat does not reflect the new 'age group'. Only when I navigate to another tab in the interface, and then return to the tab containing the 'age groups' is the newly added 'age group' displayed.

Ivan Vasiljevic
  • 5,478
  • 2
  • 30
  • 35

3 Answers3

0

Updated: After you update your value you need to tell angular that its been updated. If you wrap your code in a $timeout. On the next digest cycle the view will get updated. See here for more information.

...
$timeout(function() {
    $scope.ageGroups = coreDataService.getAgeGroups();
  // anything you want can go here and will safely be run on the next digest.
})
...

The $timeout basically runs on the next $digest cycle thus updating your value in the view.

Community
  • 1
  • 1
matt
  • 1,680
  • 1
  • 13
  • 16
  • sorry, but no, $scope.$apply() throws the in progress exception, i.e., wrapping one $apply inside another – Patrick Borrelli Feb 14 '17 at 22:38
  • See my updated answer. It's sorta strange but you can wrap your code in a $timeout. This will make sure the digest cycle includes your changes. Make sure to add `$timeout` as a dependency within your controller. – matt Feb 14 '17 at 22:47
  • no good, same result `coreDataService.refreshAgeGroups() .then(function(response) { coreDataService.setAgeGroups(response.data); $timeout(function(){ $scope.ageGroups = coreDataService.getAgeGroups(); })` gives the same result – Patrick Borrelli Feb 14 '17 at 23:06
  • That is a very dirty solution – Yaser Feb 14 '17 at 23:19
0

I would suggest wrapping your ng-repeat part in a directive, fire an event on add method and listen to it from the directive:

this.addAgeGroup = function(formData) { 
   // do stuff
   $scope.$broadcast('itemadded');
}

Inside your directive link function:

$scope.$on('itemadded', function() {
    $scope.ageGroups = coreDataService.getAgeGroups();
});
Yaser
  • 5,609
  • 1
  • 15
  • 27
0

I would recommend using the ControllerAs syntax which will refresh the ng-repeat, if the array to which it is bound to gets updated.

Refer the answer given here - ng-repeat not updating on update of array

Community
  • 1
  • 1
Neerajkumar R
  • 84
  • 1
  • 5