Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

Plunker

There are a lot of files, so perhaps you Angular aficionados will understand my explanation, or be able to follow the plunker I've provided above.

I'm creating a demo to modularize a webpage. Even the main content of the page will be in another template file. In the main content view, there's a table of links, the link opens an angular-bootstrap modal (another template/controller), which displays additional information for the object clicked.

I'm finding that every time a link is clicked, the factory is retrieving the source data. For something small, it's unnoticeable, but if you're performing an AJAX request to a large dataset, or transforming an XML to JSON, this could take a while. I am uncertain if this is a result of routing, or if it's a poor implementation of what I'm trying to accomplish, but I would like to:

  1. understand why this is happening
  2. retrieve the initial data once and share it between controllers

data.json

[
  {"firstName":"John",
   "lastName":"Denohn",
   "profession":"Student",
   "age":10
  },
  {"firstName":"Mark",
   "lastName":"Fatzigio",
   "profession":"Doctor",
   "age":20
  },
  {"firstName":"Jennifer",
   "lastName":"Cooler",
   "profession":null,
   "age":30
  },
  {"firstName":"Kimberly",
   "lastName":"Branch",
   "profession":"Teacher",
   "age":40
  }
]

index.html

<!DOCTYPE html>
<html lang="en" data-ng-app="myApp">
  <head>

    <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
    <link href="myApp.css" rel="stylesheet">

    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.8/angular.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.0rc1/angular-route.min.js"></script>
    <script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.10.0.js"></script>

  </head>

  <body>
    <div data-ng-view="" data-ng-cloak ></div>

    <script src="myApp.js"></script>
    <script src="myAppModels.js"></script>
    <script src="myAppControllers.js"></script>
  </body>

</html>

template_initialPageView.html

<div class="page-container" data-ng-cloak>

  <!-- Table Content -->
  <table class="table" data-ng-cloak>
    <thead>
      <tr><th colspan="{{keysToShow.length}}"><h1>People</h1></th></tr>
      <tr><th data-ng-repeat="header in keysToShow">{{ header.displayName }}</th></tr>
    </thead>
    <tbody>
      <tr data-ng-repeat="person in people | orderBy:orderProp">
        <td><a href="#{{[person.firstName, person.lastName].join('_')}}" 
              data-ng-click="showModal(person)"
              >{{ [person.firstName, person.lastName].join(' ') }}</a></td>
        <td>{{ person.profession }}</td>
      </tr>
    </tbody>
  </table>

</div>

template_modalContentView.html

<div class="modal-header">
  <button type="button" class="close" data-ng-click="cancelModal()">&times;</button>
  <h3 class="modal-title" id="myModalLabel">Person Info</h3>
</div>
<div class="modal-body">
  <tabset>
    <tab heading="Age">
      <h4>Name</h4>
      <p>{{ person.firstName + ' ' + person.lastName }}</p>

      <h4>Age</h4>
      <pre>{{ person.age }}</pre>
    </tab>
    <tab heading="Profession">
      <h4>Name</h4>
      <p>{{ person.firstName + ' ' + person.lastName }}</p>

      <h4>Profession</h4>
      <pre>{{ person.profession }}</pre>
    </tab>
  </tabset>
</div>
<div class="modal-footer">
  <button class="btn btn-primary" data-ng-click="closeModal()">Close</button>
</div>

myApp.js

var myApp = angular.module('myApp', ['ui.bootstrap', 'ngRoute']);

/* Routes */
myApp.config(function($routeProvider) {
  $routeProvider
    .when('/home', {
      templateUrl: 'template_initialPageView.html',
      controller: 'PageViewController',
      controllerAs: 'page'
    })
    .otherwise({
      redirectTo: '/home'
    });
});

myAppControllers.js

var myAppControllers = {};

// Define: Page View Controller
myAppControllers.PageViewController = function ($scope, $modal, modelFactory) {

  init();

  function init(){
    $scope.orderProp  = '';
    $scope.keysToShow = [{'displayName':'Fullname'},{'displayName':'Profession'}];
    modelFactory.getPeople().success(function(data){
      $scope.people = data;
    });
  }

  $scope.showModal = function(person) {
    console.log('person clicked', person);
    $scope.person = person;

    var modalInstance = $modal.open({
      controller: 'ModalController',
      controllerAs: 'modal',
      templateUrl: 'template_modalContentView.html',
      resolve: {
        person: function(){
          return $scope.person;
        }
      }
    });

  };

};


// Define: Modal Controller
myAppControllers.ModalController = function ($scope, $modalInstance, person) {
  init();

  function init(){
    $scope.person = person;
  }

  $scope.cancelModal = function (){
    $modalInstance.dismiss('cancel');
  };

  $scope.closeModal = function () {
    $modalInstance.close();
  };

};


// Add controlers to the module
myApp.controller(myAppControllers);

myAppModels.js

myApp.factory('modelFactory', function($http){
  var factory = {};

  factory.getPeople = function (){
    return $http.get('data.json');
  };

  return factory;

});
share|improve this question

1 Answer 1

up vote 1 down vote accepted

You can try this, it initially returns an empty array that will be populated when $http.get() returns and it re-uses the array each time it is called:

myApp.factory('modelFactory', function($http){
  var factory = {};

  factory.getPeople = function () {
    if (typeof(factory.people) == "undefined") {
      factory.people = [];
      $http.get('data.json').success(function(result) {
        var i = 0;
        for (i = 0; i < result.length; i++) {
          factory.people.push(result[i]);
        }
      });
    }
    return factory.people;
  };

  return factory;

});

And don't forget to change your assignment to accept an array and just use that:

$scope.people = modelFactory.getPeople();

(PLUNKR)

share|improve this answer
    
Please ignore my other comment, I wasn't returning factory.people and I didn't modify the controller, once I did I found it worked, now I just need to understand what you did -- I tried doing something similar, but I was passing in $scope or using $rootScope and checking .people in there; it seems referencing factory was something I should have done. –  vol7ron Apr 16 at 18:24
    
It stores the data in the factory which is a singleton, and just provides it whenever asked. It returns an empty array to start with so you have something to bind to and initiates a call to get the actual data, then populates the existing array. Angular will detect the new data and bind to everything so you can see it. –  Jason Goemaat Apr 16 at 18:26
    
I may branch off another question if I have time later. Regarding large datasets or those that have long delays in retrieval, I'm finding that that handlebars/mustaches (template placeholders) are being displayed, despite ng-cloak. This was not happening when the main page content was not in a view -- any thoughts? –  vol7ron Apr 16 at 18:50
    
One last follow-up: why is it that result must be iterated over? It looks like it's an array that's returned, thus I wouldn't think that it is assigned by reference -- thought factory.people = result; would work? Especially thought, factory.people = angular.copy(result); would work, but it doesn't. It's interesting that angular.copy(result, factory.people); does work. –  vol7ron Apr 16 at 19:33
    
You're returning an empty array and binding to it, then filling the array later so angular will notice your changes. If you change factory.people to a new array, that doesn't affect the array you original assigned to your scope. angular.copy(result, factory.people) must alter the exiting array too then. –  Jason Goemaat Apr 17 at 6:12

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.