b85411
b85411

Reputation: 9990

Better way to reference $scope in AngularJS

This is how I have my JS set up:

Basically I have a page, and on that page there is a chart. I want to have a loading spinner show while the chart data is loading.

angular.module('myApp', [])
  .service('chartService', ['$http', function($http) {
    var svc = {};
    svc.updateChartData = function($scope) {
      $scope.loading = true;
      $http({method: 'GET', url: 'http://example.com/getjson'})
        .success(function(response) {
          var data = google.visualization.arrayToDataTable(JSON.parse(response));
          var options = {
            ...
        };
      var chart = new google.visualization.ComboChart(document.getElementById('chart_div'));
      chart.draw(data, options);
      $scope.loading = false;
    });
  }

  return svc;
}])

.controller('PageController', ['$scope', '$http', 'chartService', function($scope, $http, chartService) {
  $scope.loading = true;
  // When select option changes
  $scope.updateData = function() {
    chartService.updateChartData($scope);
  };
}])

.controller('ChartController', ['$scope', '$http', 'chartService', function($scope, $http, chartService) {
  // On load
  chartService.updateChartData($scope);
}]);

I am using ng-hide="loading" and `ng-show="loading" to make sure the spinner and the chart show at the correct times.

However, I've noticed that the call below // On load - doesn't actually turn the loading to false. Another message on SO suggested there is a better way to achieve this than by passing $scope around so any suggestions would be appreciated. Thank you.

Upvotes: 0

Views: 92

Answers (2)

Red
Red

Reputation: 26

first,you have two controllers,I'm assuming they are nested relations. PageController include ChartController. you want to change the value of the parent controller in the child controller. You must use a reference type rather than a value type.

$scope.loading =true;

change to

$scope.loading ={status:true};

and if you want to set false,Should be

$scope.loading.status =false;

NOT

 $scope.loading ={status:false};

second, you can pass a callback function to service. like this

    svc.updateChartData = function(callback) {
        ....
       .success(){
            callback();
       }
    }  

controller code change to

 .controller('ChartController', ['$scope', '$http', 'chartService', 

function($scope, $http, chartService) {

                // On load
                chartService.updateChartData(function(){
                $scope.loading =true;
                 });

 }]);

Upvotes: 0

devqon
devqon

Reputation: 13997

It is not a good practice to pass your scope object to a service, a service is meant to be stateless. Instead utilize the callbacks of the $http:

chartService.updateChartData().finally(function(){
    $scope.loading = false;
});

And, as Grundy mentioned below, return your $http from your service to enable callbacks:

svc.updateChartData = function($scope) {
    return $http({ //.. the options });
}

I see some more bad practices though. You shouldn't add the data to your DOM from your service, instead utilize also for this the callbacks:

svc.updateChartData = function($scope) {
    return $http({method: 'GET', url: 'http://example.com/getjson'});
}

controller:

// When select option changes
$scope.updateData = function() {
    chartService.updateChartData().then(function(data) {
        // success
        // do something with the return data from the http call
    }, function (error) {
        // error
        // handle error
    }).finally (function() {
        // always
        $scope.loading = false;
    });
};

For your google chart it would make most sense to create a directive.

Upvotes: 1

Related Questions