martijnve
martijnve

Reputation: 983

Unreadable data from $resource in angular.js

I have a very strange problem with an angular app we are building. Whenever i load some data from a resource defined like below (simplest example I could build) I get some data back wich I can use for databinding (eg. ng-repeat='message in messages' or as {{message.id}}) however I can never read it from javascript by accessing it as an array or object (depending on wether i used get({id:myId}) or query()).

Iterating over it only gives me keys like $get, $query, $save, etc... but no actual data.

app.factory('Message', ['$resource', function($resource) {
    return $resource('MY_URL/messages/:id', {id: '@id'});
}]);

app.service('messageService', ['$rootScope', 'Message', function($rootScope, Message) {
    var messages = Message.query();
    var selectedMessage = null;

    var service = {};

    service.get = function(id) {
                    // Problem A (see below for details)
        if (arguments.length === 1) return Message.get({id: id});
        else return messages;
    };

var MenuCtrl = function($scope, Project, messageService, otherService) {
    $scope.projects = Project.query();
    $scope.messages = messageService.get();

    // Problem B (details below)
};

At Problem A i want to be able to return a single element form a collection that has already been fetched, however i need some way to handle calls that happen before the data is ready.

At problem B i would like to process some of the fetched data and pass the result to "otherService" however i need a way to delay this until the data is ready.

Upvotes: 0

Views: 777

Answers (1)

Christian Smith
Christian Smith

Reputation: 8089

I have only seen this issue come up in unit testing, and the way to get around it is by "flushing" the mock $httpBackend.

According to the API docs:

The $httpBackend used in production, always responds to requests with responses asynchronously. If we preserved this behavior in unit testing, we'd have to create async unit tests, which are hard to write, follow and maintain. At the same time the testing mock, can't respond synchronously because that would change the execution of the code under test. For this reason the mock $httpBackend has a flush() method, which allows the test to explicitly flush pending requests and thus preserving the async api of the backend, while allowing the test to execute synchronously.

Here's an example with some context:

// define a resource within a service
angular.module('app.services', ['ngResource'])
  .factory('Message', function ($resource) {
    var url = ...
      , params = ...
      , actions = ...;

    return $resource(url, params, actions);
  }

// Query the resource in a controller
function MessagesCtrl ($scope, $routeParams, Message) {
  $scope.messages = Message.query();
}

// unit test with a mocked backend
describe('MessagesCtrl', function() {
  var scope, ctrl, $httpBackend, messages;

  beforeEach(inject(function (_$httpBackend_, $rootScope, $controller) {
    $httpBackend = _$httpBackend_;
    scope = $rootScope.$new();

    messages = [
      {
        id: '1',
        text: 'foo',
      }, {
        id: '2',
        text: 'foo',
      }
    ]

    $httpBackend.expectGET('/api/messages').respond(messages);
    ctrl = $controller('MessagesCtrl', {$scope: scope});
  }));

  it('should get a list of messages', function () {

    // THE NEXT LINE WILL SOLVE THE PROBLEM
    $httpBackend.flush();

    expect(scope.message).toEqualData(message);
  });
});

Upvotes: 1

Related Questions