29

I've been trying out Angular JS for the past couple of days and one thing I can't figure out is how to work with relationships between models.

The project I'm working on has a Users model and an Accounts model. I have it set up on my database that each Account has a field called 'ownedBy' which is a foreign key reference to the id of the user that owns that account.

In Angular I have the following set up in a file called main.js

var myApp = angular.module('myApp', ['ngResource']);

var Users = myApp.factory('Users', function($resource) {
    var User = $resource('http://api.mydomain.ca/users/:id',
        {id:'@id'},
    {});
    return User;
});

var Accounts = myApp.factory('Accounts', function($resource) {
    var Accounts = $resource('http://api.mydomain.ca/accounts/:id',
        {id:'@id'},
    {});
    return Accounts;
});


function UsersCtrl($scope, Users) {
    $scope.users = Users.query();
}

function AccountsCtrl($scope, Accounts) {
    $scope.accounts = Accounts.query();
}

and the following template

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>Angular Test</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css?v=2.2.1">
</head>
<body>
<div ng-app="myApp">
    <div ng-controller="UsersCtrl">
        <table class="table table-striped">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>First Name</th>
                    <th>Last Name</th>
                </tr>
            </thead>
            <tbody>
                <tr ng-repeat="user in users">
                    <td>{{user.id}}</td>
                    <td>{{user.firstName}}</td>
                    <td>{{user.lastName}}</td>
                </tr>
            </tbody>
        </table>
    </div>
    <div ng-controller="AccountsCtrl">
        <table class="table table-striped">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Owned By</th>
                </tr>
            </thead>
            <tbody>
                <tr ng-repeat="account in accounts">
                    <td>{{account.id}}</td>
                    <td>{{account.ownedBy}}</td>
                </tr>
            </tbody>
        </table>
    </div>
</div>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular-resource.min.js"></script>
<script src="/bootstrap/js/bootstrap.min.js?v=2.2.1"></script>
<script src="js/main.js"></script>
</body>
</html>

and this is working. It pulls a JSON resource from my REST server and displays it in a table. What's the next step I need to take to end up with one table that shows users and their account numbers? (the equivalent of a database JOIN?) Is there a different way to do it for a one to many relationship? (ie... an account has many transactions)

Thanks for the help :)

Devin Crossman
  • 7,454
  • 11
  • 64
  • 102
  • Do you have control over what the server returns? You should rebuild the model as you want, before binding it with Angular, and the best place for that is in your server api. – Ben Felda Jan 25 '13 at 21:14
  • 1
    @BenFelda With RESTful APIs, it is quite common to do client-side joins. – Josh David Miller Jan 25 '13 at 21:16
  • I do have control over what the server returns. But I thought it was best to keep the user and account models separate (normalized?). How would you recommend I have the server return the data? – Devin Crossman Jan 25 '13 at 21:18
  • I would build out the object on the server side `{user: {account, account...}}` so the client could just take the data and bind it to my model, and make just one server call to get all the data. – Ben Felda Jan 25 '13 at 21:39
  • I did responded this at this post: http://stackoverflow.com/a/17055281/1558820 – Klederson Bueno Jun 12 '13 at 00:28

2 Answers2

25

$resource doesn't contain any way to deal with relationships that aren't handled by the server, but it's pretty simply with $http:

module.factory( 'UserService', function ( $http, $q ) {
  return {
    get: function getUser( id ) {
      // We create our own promise to return
      var deferred = $q.defer();

      $http.get('/users/'+id).then( function ( user ) {
        $http.get('/accounts/'+user.id).then( function ( acct ) {

          // Add the account info however you want
          user.account = acct;

          // resolve the promise
          deferred.resolve( user );

        }, function getAcctError() { deferred.reject(); } );
      }, function getUserError() { deferred.reject(); } );

      return deferred.promise;
    }
  };
});

And then in your controller, you can just use it like any other promise:

UserService.get( $scope.userId ).then( function ( user ) {
  $scope.user = user;
});

And it's available for your template!

<div>
    User: "{{user.firstName}} {{user.lastName}}" with Acct ID "{{user.acct.id}}".
</div>
Josh David Miller
  • 120,525
  • 16
  • 127
  • 95
  • Thanks for your answer it looks like it will work. I thought $resource wrapped $http and was supposed to be superior in some way. When, if ever, should I use $resource? – Devin Crossman Jan 25 '13 at 21:21
  • 6
    It *does* wrap $http and $resource is totally awesome for *very typical* REST use cases. Once you venture beyond the basics, though, $resource can start to get in the way. For example, you *could* solve this problem using $resource, but it's trickier and messier. One of the most common complaints (including my own) is that $resource doesn't return a promise, which would make this a cinch. – Josh David Miller Jan 25 '13 at 21:27
  • Can you use the promise $http returns instead of $q -- i.e., is there a reason you are using $q? Also, is there a difference between returning "deferred" vs returning "deferred.promise"? I ask because I see the $q API docs use the latter. I did not try running your code (yet). – Mark Rajcok Jan 26 '13 at 05:47
  • 1
    @MarkRajcok Returning `deferred` rather than `deferred.promise` was a typo. :-) I fixed it. We could probably return the promise from `$http` rather than our own from $q, so long as the inner call to `then` *returned* the modified object. I'm not 100% sure though. I used this way both so I wouldn't have to test it and so what was happening behind the scenes would be more clear. – Josh David Miller Jan 26 '13 at 07:42
  • I would be awesome if resource could do that. – Lukasz Madon Jan 29 '14 at 10:24
  • Is there any reason for wrapping `deferred.reject` in the functions `getAcctError` and `getUserError`, instead of just `}, deferred.reject);` ? – Alex McMillan Apr 30 '14 at 04:17
  • 1
    @AlexMcMillan In this case, no. But I assume in most cases the service will want to handle the error in some way rather than repeatedly handling it in (potentially, at least) multiple controllers. So I opted for verbosity here. – Josh David Miller Apr 30 '14 at 04:31
0

I use js-data if I need relationships in the UI. The library handles relationships and data modelling pretty elegantly in general. I have found it easier to use even if you are just looking for a nice API interface. I prefer it to ngResource.

In your case you would have a model for User and model for Account

src/app/data/account.model.coffee

angular.module 'app.data' #this can be your module name
  .factory 'Account', (DS) ->
    DS.defineResource
      name: 'account'
      endpoint: 'accounts'
      relations:
        belongsTo:
          user:
            localKey: 'userId'
            localField: 'user'

src/app/data/user.model.coffee

angular.module 'app.data'
  .factory 'User', (DS) ->

    DS.defineResource
      name: 'user'
      endpoint: 'users'
      relations:
        belongsTo:
          account: #make sure this matches the 'name' property of the other model
            foreignKey: 'userId'
            localField: 'account'
Tyrone Wilson
  • 4,328
  • 2
  • 31
  • 35