0

I have a controller that starts like this (simplified for this question):

angular.module('myApp.controllers')
    .controller('MyController', ['$scope', '$routeParams', 'MyService',
                                function ($scope, $routeParams, MyService) {

MyService.fetchWithId($routeParams.id).then(function(model) {
    $scope.model = model;
});

Which is fine, but then in many places throughout the controller, I have functions that are referred to in the view that refer to the model ...

$scope.someFunctionMyViewNeeds = function() {
    return $scope.model.someModelAttribute;
};

Since these often run before the fetch completes, I end up with errors like "cannot read property of undefined" when the view tries to see someModelAttribute.

So far, I've tried three things:

// before the fetch
$scope.model = new Model();

...but I really don't want a new model, and in some cases, cannot complete initialization out of the blue without other dependences.

Another idea is to litter the code with defense against the unready model, like:

return ($scope.model)? $scope.model.someModelAttribute : undefined;

... but that's a lot of defense all over the code for a condition that only exists while the fetch completes.

My third idea has been to "resolve" the model in the route provider, but I don't know how to do that and get at the $routeParams where parameter to fetch the model is kept.

Have I missed a better idea?

user1272965
  • 2,814
  • 8
  • 29
  • 49
  • If you want code to execute when `.fetchWithId` completes & calls back, you need to embed that code in the callback or invoke it from the callback, or use a library like async: https://github.com/caolan/async – Drew R Jan 18 '15 at 22:04
  • Why not just default it: `$scope.model = {}`? – Davin Tryon Jan 18 '15 at 22:12

1 Answers1

2

Try this if you want to use resolve.

var app = angular.module('app', ['ngRoute']);
app.config(function ($routeProvider) {
  $routeProvider.when('/things/:id', {
    controller: 'ThingsShowController',
    resolve: {
      model: function ($routeParams, MyService) {
        return MyService.fetchWithId(+$routeParams.id);
      }
    },
    template: '<a ng-href="#/things/{{model.id}}/edit">Edit</a>'
  });
  $routeProvider.when('/things/:id/edit', {
    controller: 'ThingsEditController',
    resolve: {
      model: function ($routeParams, MyService) {
        return MyService.fetchWithId(+$routeParams.id);
      }
    },
    template: '<a ng-href="#/things/{{model.id}}">Cancel</a>'
  });
});

// Just inject the resolved model into your controllers
app.controller('ThingsShowController', function ($scope, model) {
  $scope.model = model;
});

app.controller('ThingsEditController', function ($scope, model) {
  $scope.model = model;
});

// The rest is probably irrelevant
app.factory('Model', function () {
  function Model(attributes) {
    angular.extend(this, attributes);
  }
  return Model;
});
app.service('MyService', function ($q, Model) {
  this.fetchWithId = function (id) {
    var deferred = $q.defer();
    deferred.resolve(new Model({ id: id }));
    return deferred.promise;
  };
});
// Just to default where we are
app.run(function ($location) {
  $location.path('/things/123');
});
app.run(function ($rootScope, $location) {
  $rootScope.$location = $location;
});
// Because $routeParams does not work inside the SO iframe
app.service('$routeParams', function () {this.id = 123;});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.9/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.9/angular-route.min.js"></script>

<div ng-app="app">
  <div>Route: {{$location.path()}}</div>
  <div ng-view=""></div>
</div>
Scymex
  • 954
  • 9
  • 17
  • Thanks. Can you explain the plus sign? +$routeParams.id – user1272965 Jan 19 '15 at 03:33
  • It's a shorthard for writing `Number($routeParams.id)` and means to convert the string into a number. You could also use `parseInt($routeParams.id)`, se differences here: http://stackoverflow.com/a/4564199 (Because $routeParams.something will always be a string) – Scymex Jan 19 '15 at 07:59
  • Embarrassingly, I found that just defaulting to an empty object like the comment above suggested solved my problem, but I really appreciate learning this. – user1272965 Jan 19 '15 at 20:11