2

Please consider the following angularjs code for a controller:

(function (app) {
    var controller = function ($scope, $state, datacontext) {
        $scope.$parent.manageTitle = "Account Management";
        $scope.accounts = [];

        var init = function () {
            getRecords();
        };

        var getRecords = function () {
            return datacontext.getAccounts().then(function (data) {
                $scope.$apply(function () {
                    $scope.accounts = data;
                });
            });
        };

        init();
    };

    app.controller("accountsCtrl", ["$scope", "$state", "datacontext", controller]);
})(angular.module("app"));

Removing the $scope.$apply wrapper and leaving just the "$scope.accounts = data" in the getRecords method breaks the code. The data is retrieved but the ng-repeat directive in the html is not automatically updated. I'm trying to get my arms around the entire $apply/$digest model, but it sure seems to be that the $apply should NOT be required in this case.

Am I doing something wrong?

Thanks.

<------------------------------------------ EDIT ---------------------------------------->

Ok, thanks for the responses. Here is the datacontext. It uses Breeze. I still can't figure out what the problem is - - I just don't see why $apply is required in the code, above.

(function (app) {
    var datacontext = function () {
        'use strict';
        breeze.config.initializeAdapterInstance('modelLibrary', 'backingStore', true);
        breeze.config.initializeAdapterInstance("ajax", "angular", true);
        breeze.NamingConvention.camelCase.setAsDefault();

        var service;
        var manager = new breeze.EntityManager('api/ProximityApi');
        var entityQuery = breeze.EntityQuery;

        var queryFailed = function (error) {
        };

        var querySuccess = function (data) {
            return data.results;
        };

        var getAccounts = function () {
            var orderBy = 'accountName';
            return entityQuery.from('Accounts')
                .select('id, accountName')
                .orderBy(orderBy)
                .using(manager)
                .execute()
                .then(querySuccess, queryFailed);
        };

        service = {
            getAccounts: getAccounts
        };

        return service;
    };
    app.factory('datacontext', [datacontext]);
})(angular.module('app'));

Thanks again!

Ting
  • 1,658
  • 16
  • 26
CCPony
  • 988
  • 1
  • 7
  • 20

2 Answers2

3

Thanks for your answers. Jared - you're right on the money. By default, Breeze does not use angular $q promises, but uses third-party Q.js promises instead. Therefore, I needed $apply to synchronize the VM to the view. Recently however, the Breeze folks created angular.breeze.js, which allows the Breeze code to use angular promises, instead. By including the angular.breeze module in the application, all Breeze code will use native angular promises and $http instead.

This solved my problem and I could remove the $apply call.

See: http://www.breezejs.com/documentation/breeze-angular-service

CCPony
  • 988
  • 1
  • 7
  • 20
1

The reason that you need to use the $apply function is the result of using Breeze to to return the data. the $apply function is used to get angular to run a digest on all the internal watches and update the scope accordingly. This is not needed when all changes occur in the angular scope as it does this digest automatically. In your code, because you are using Breeze the changes are taking place outside the angular scope, thus you will need to get angular to manually run the digest, and this is true for anything that takes place out side of angular (jQuery, other frameworks ect...). It is true that Breeze is using promises to update the data, however Angular does not know how to handle the changes after the promise returns because it is out side the scope. If you were using an angular service with promises then the view would be updated automatically. If your code is working correctly as is then it would be the correct way to use $apply in this way.

The only thing I might suggest is to change the way you are calling the apply to make sure that it will only run if another digest is not currently in progress as this can cause digest errors. I suggest you call the function as such:

if(!$scope.$$phase){$scope.$apply(function () {
   $scope.accounts = data;
});

Or the other option would be to write a custom wrapper around the $apply function like this SafeApply

Jared Reeves
  • 1,390
  • 2
  • 15
  • 29