4

I have set up a service to return a listing of clients from my API. Using UI-router, I can successfully pass a client's id to the details state - however, it seems unnecessary here to make another API call to retrieve a single client when I have all the necessary data in my controller.

What is the best way to use the ID in my detail state URL to show data for that client? Also - if a user browses directly to a client detail URL - I'll need to then make a call to the API to get just that client data - or is there a better way?

EDIT: I am not looking to load the two views on the same 'page', but completely switch views here, from a listing page to a detail page.

Routes in App.js

$stateProvider
    .state('root', {
        abstract: true,
        url: '',
        views: {
            '@': {
                templateUrl: '../partials/icp_index.html',
                controller: 'AppController as AppCtrl'
            },
            'left-nav@root': {
                templateUrl: '../partials/left-nav.html'
            },
            'right-nav@root': {
                templateUrl: '../partials/right-nav.html'
            },
            'top-toolbar@root': {
                templateUrl: '../partials/toolbar.html'
            }
            /*'footer': {
                templateUrl: '../partials/agency-dashboard.html',
                controller: 'AppController as AppCtrl'
            }*/
        }
    })
    .state('root.clients', {
        url: '/clients',
        views: {
            'content@root': {
                templateUrl: '../partials/clients-index.html',
                controller: 'ClientsController as ClientsCtrl'
            }
        }
    })
    .state('root.clients.detail', {
        url: '/:clientId',
        views: {
            'content@root': {
                templateUrl: '../partials/client-dashboard.html',
                //controller: 'ClientsController as ClientsCtrl'
            }
        }
    })
    // ...other routes

Service, also in app.js

.service('ClientsService', function($http, $q) {
    this.index = function() {
        var deferred = $q.defer();
        $http.get('http://api.icp.sic.com/clients')
            .then(function successCallback(response) {
                console.log(response.data);
                deferred.resolve(response.data);

        },
        function errorCallback(response) {
           // will handle error here 
        });
        return deferred.promise;
    }
})

And my controller code in ClientsController.js

.controller('ClientsController', function(ClientsService) {
    var vm = this;
    ClientsService.index().then(function(clients) {
        vm.clients = clients.data;
    });
});

And finally, my listing page clients-index.html

<md-list-item ng-repeat="client in ClientsCtrl.clients" ui-sref="clients-detail({clientId : client.id })">
    <div class="list-item-with-md-menu" layout-gt-xs="row">
        <div flex="100" flex-gt-xs="66">
            <p ng-bind="client.name"></p>
        </div>
        <div hide-xs flex="100" flex-gt-xs="33">
            <p ng-bind="client.account_manager"></p>
        </div>
    </div>
</md-list-item>
Abhishek
  • 3,304
  • 4
  • 30
  • 44
DJC
  • 1,175
  • 1
  • 15
  • 39
  • http://stackoverflow.com/questions/27696612/how-do-i-share-scope-data-between-states-in-angularjs-ui-router – gyc Jun 15 '16 at 11:29
  • @gyc but this requires the two views to be shown at once - correct? In order to show the detail - the ui-view needs to be in the same template as the listing? – DJC Jun 15 '16 at 11:55
  • Numerous ways to approach this. Store clients in service is one. Or make `clients-detail` a child state of `clients` and use a `resolve` on `clients` to get the data. All child states inherit `resolve` of parent – charlietfl Jun 15 '16 at 12:07
  • @charlietfl and display in a separate view - as though you've changed page? Or within the same template - which is not what I'm after – DJC Jun 15 '16 at 12:21
  • Can nest states and have different views. Read docs – charlietfl Jun 15 '16 at 12:33
  • @charlietfl is there a particular section of the docs that explains this - I didn't post this question without looking at the docs first - I couldn't see where this info was given? – DJC Jun 15 '16 at 12:41
  • Whole section on it https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views . Also check how it;s done in demo – charlietfl Jun 15 '16 at 12:49
  • @CIvemy See my answer that runs with that... – malix Jul 11 '16 at 14:00

2 Answers2

3

I would change the service to cache the data. With $q.when() you can return a promise from a variable. So you save your response in a variable, and before doing the API call you check if the cache has been set. If there is any cache, you return the data itself. Otherwise, you do the usual promise call.

.service('ClientsService', function($http, $q) {
    var clients = null;

    this.getClient = function(id) {
        if (clients !== null) {
            return $q.when(id ? clients[id] : clients);
        }

        var deferred = $q.defer();
        $http.get('http://api.icp.sic.com/clients').then(function(response) {
            clients = response.data;
            deferred.resolve(id ? clients[id] : clients);
        }, function (response) {
            // will handle error here
        });

        return deferred.promise;
    }
})
rpadovani
  • 7,101
  • 2
  • 31
  • 50
  • How is that used to show a particular client's data in the detail state? – DJC Jul 11 '16 at 13:44
  • @CIvemy you have your data as before, but they aren't saved in the controller but in the service, in this way you do not have to do an API call again but your data are in your service. There are different strategies on how to manage your data then: you can use another controller, or in the same controller having an `actualClient` which keeps track of the selected client, or using a filter: https://docs.angularjs.org/api/ng/filter/filter#! The important thing is you already have your data in this way and you don't need to call API again – rpadovani Jul 11 '16 at 13:52
  • I have edited the question to have one cached method `getClient(id)` which gets the clients once, stores them and then returns the requested client by his ID. – ssc-hrep3 Jul 11 '16 at 13:56
  • @CIvemy You can see it now, other users need to approve it first. – ssc-hrep3 Jul 11 '16 at 15:43
3

You can use inherited states like suggested here.

$stateProvider
    // States
 .state("main", {
      controller:'mainController',
      url:"/main",
      templateUrl: "main_init.html"
  })  
  .state("main.details", {
      controller:'detailController',
      parent: 'main',
      url:"/:id",
      templateUrl: 'form_details.html'
  })  

Your service does not change. Your controllers check if the Model has been retrieved:

app.controller('mainController', function ($scope, ClientsService) {
  var promise = $scope.Model ? $q.when($scope.Model) : ClientsService.index();
  promise.then(function(data){
    $scope.Model = data;
  });
})
app.controller('detailController', function ($q, $scope, ClientsService, $stateParams) {
  var promise = $scope.Model ? $q.when($scope.Model) : ClientsService.index();
  promise.then(function(data){
    $scope.Model = data;
    $scope.Item = data[$stateParams.id];
  });
})

See http://plnkr.co/edit/I4YMopuTat3ggiqCoWbN?p=preview

[UPDATE] You can also, if you must, combine both controllers:

app.controller('mainController', function ($q, $scope, ClientsService, $stateParams) {
  var promise = $scope.Model ? $q.when($scope.Model) : ClientsService.index();
  promise.then(function(data){
    $scope.Model = data;
    $scope.Item = data[$stateParams.id];
  });
})
Community
  • 1
  • 1
malix
  • 3,566
  • 1
  • 31
  • 41
  • Thanks - this seems like it wouldwork - I'll give it a go now. Out of interest - is there a way to achieve this without having to have 2 separate controllers for index and detail? – DJC Jul 11 '16 at 14:01
  • Yes, the answer I link to uses the same one, you can check `$stateParams` to see if you are in details or not. But each "object" should do one thing and one thing well :) if you have duplicate code between controllers, move it to a service... IMHO :D – malix Jul 11 '16 at 14:03
  • Can you explain that last bit - are you suggesting a different approach to the answer you have given? Would you recommend combining this with rpadovani's answer above - ie caching the client data? – DJC Jul 11 '16 at 14:08
  • could you expand please? – DJC Jul 12 '16 at 10:59
  • Expanded answer, no cache needed – malix Jul 12 '16 at 12:37
  • Do you need any more precisions? Vote it up and/or accept it if it suits your needs? :) – malix Jul 12 '16 at 13:47
  • 1
    Hi @malix - I will absolutely vote up answers and accept one. What is the advantage of having it in separate controllers though? Thanks for the edit - I've just seen that – DJC Jul 12 '16 at 13:49
  • Well the main controller really doesn't need to know anything about details and there might be some processing you want to do in details that you don't have in main... In reality you could have only one controller for the whole app but it breaks SRP amongst other things... :) https://en.wikipedia.org/wiki/Single_responsibility_principle – malix Jul 12 '16 at 14:54
  • Yeah I guess that makes sense - I was thinking well a client is a client, so everything to do with clients should be in a single clients controller, but I guess you are probably right. Seems like a lot of HTTP requests to call in all these controllers though! – DJC Jul 13 '16 at 08:43
  • Yes, http calls should be optimized so that's why we share the state provided by the service... – malix Jul 13 '16 at 12:33