Ways to load data into a controller through a service in AngularJS

I have a service that loads data using $ http and returns a promise (simplified for brevity):

angular.module('myApp').factory('DataService', ['$http', function($http) { function unwrapFriendList(data) { ... return unwrappedFriendList; } return { getFriendList: function() { return $http.get('/api/friends').then(unwrapFriendList); } } }]); 

Here is a view that uses this data after the promise is resolved, and the result is saved in $scope.friends :

 <div ng-repeat='friend in friends'> {{friend.firstName}} {{friend.lastName}} </div> 

When it comes to loading this data into the controller, I came across several ways.

Option 1: a controller that uses data downloaded via ng-route resolve

 angular.module('myApp').controller('FriendListCtrl', ['$scope', 'friendList', function($scope, friendList) { $scope.friends = friendList; }]); 

Route Section:

 angular.module('myApp', ...).config(function($routeProvider) { $routeProvider .when('/friends', { templateUrl: 'views/friends.html', controller: 'FriendListCtrl', resolve: { friendList: ['DataService', function(DataService) { return DataService.getFriendList(); }] } }) ... }); 

Option 2: a controller that starts data loading by itself

 angular.module('myApp').controller('FriendListCtrl', ['$scope', 'DataService', function($scope, DataService) { DataService.getFriendList().then(function(friendList) { $scope.friends = friendList; }); }]); 

Questions

  • Are there other commonly used ways to do this? If yes, please illustrate the sample code.
  • What are the limitations of each approach?
  • What are the benefits of each approach?
  • Under what circumstances should I use each approach?
+6
source share
2 answers

Unit testing

Option 1: Using permissions makes mocking dependencies in control unit tests very simple. In the first version:

 $routeProvider .when('/friends', { templateUrl: 'views/friends.html', controller: 'FriendListCtrl', resolve: { friendList: ['DataService', function(DataService) { return DataService.getFriendList(); }] } }) angular.module('myApp') .controller('FriendListCtrl', ['$scope', 'friendList', function($scope, friendList) { $scope.friends = friendList; }]); 

Since friendList is injected into the controller, mocking it in the test is as simple as passing a simple object to the $controller service:

 var friendListMock = [ // ... ]; $controller('FriendListCtrl', { $scope: scope, friendList: friendListMock }) 

Option 2: You cannot do this with the second option, and you will have to spy on / stub the DataService. Since data requests in the second embodiment are immediately called up when the controller is created, testing will be very confusing as soon as you start to execute several, conditional or dependent (more about this later) data requests.

View Initialization

Option 1: Allows you to prevent initialization of the view until all permissions have been executed. This means that everything expected to wait for data (including directives) will have it immediately.

Option 2: If data requests are made in the controller, the view will be displayed, but will not have any data until the requests are completed (which will be at some unknown point in the future). This is akin to a flash of unrelated content and can be harsh, but it can work.

Real difficulties arise when you have components in your view that are waiting for data and not providing it because they are still being retrieved. Then you will have to hack this, forcing each of your components to wait or delay initialization for some unknown amount of time, or to have $watch an arbitrary variable for them before initialization. Very dirty.

Prefers to allow

While you can load source data in controllers, solutions already do this in a much cleaner and more declarative way.

The ngRoute resolver, however, lacks several key features, the most notable of which is the dependent solution. What if you want to provide 2 data items to your controller: the customer and information about their regular store? This is not easy with ngRoute:

 resolve: { customer: function($routeParams, CustomerService) { return CustomerService.get($routeParams.customerId); }, usualStore: function(StoreService) { // can't access 'customer' object here, so can't get their usual store var storeId = ...; return StoreService.get(storeId); } } 

You can hack this by loading the usualStore from the controller after entering customer , but why bother when it can be done purely in ui-router with dependent permissions:

 resolve: { customer: function($stateParams, CustomerService) { return CustomerService.get($stateParams.customerId); }, usualStore: function(StoreService, customer) { // this depends on the 'customer' resolve above return StoreService.get(customer.usualStoreId); } } 
+3
source

Are there other commonly used ways to do this?

It depends if you have data that is in a different domain, and this may take a while to load, so you cannot show the view until it is received, so you must solve the problem first.

What are the limitations of each approach?

Limiting the use of the first template to resolution may be that the page will not be displayed until all data is loaded

The limitation of the second is that the data may take longer and your view will look like "{{}}" if you did not solve it with css

What are the benefits of each approach?

The advantage of the first is that I said earlier that you will allow the data and make sure that it is present before viewing.

Under what circumstances should I use each approach?

the solution is very useful if we need to load some data loaded before the controller initialization and presentation visualization

And secondly, when you have no checks, and these download problems are expected, and the data is in your hands!

+2
source

Source: https://habr.com/ru/post/979887/


All Articles