Active Record or Data Mapper pattern for Angularjs?
Asked Answered
M

1

7

I'm very new to Angular, so hopefully I know enough to ask what seems like a reasonable design question.

I'm charting some data via Angular, and am using $resource. Before bringing Angular in to the project I made a chart factory function for creating chart objects from sample json data I just pasted into the view.

Now that I'm using Angular, it's tempting to put the charting functions into the 'Chart' resource, Active Record style, so that I have this one thing that can draw, save, update, etc.

While the advantage of that pattern is simplicity, the drawback is coupling persistence with behavior. For example, it would be pretty awkward if I wanted to save chart settings to local storage.

Having been bitten by AR enough times in my career already, I want to go with DM by leaving my chart object as-is, and having the controller pass data from the resource into my chart.

However! My hazy understanding of angularjs' dependency injection suggests that I might be able to create a resource or some such that could accept a common persistence interface - is the right word a 'scope'?

Example AR strategy:

App.factory('Chart', [
  '$resource', function($resource) {
    var chart = $resource('/api/charts/:id', {
      id: '@id'
    });

    chart.draw = function() { ... }

    return chart
  }
]);

App.controller('ChartsCtrl', [
  '$scope', 'Chart', function($scope, Chart) {
    $scope.charts = Chart.query(function() {
      $.each($scope.charts, function(i, c) { c.draw() })
    })
  }
])

Example DM strategy:

App.chart = function(resource) {
  return { draw: function() { ... } }
}

App.factory('ChartResource', [
  '$resource', function($resource) {
    return $resource('/api/charts/:id', {
      id: '@id'
    })
  }
])

App.controller('ChartsCtrl', [
  '$scope', 'ChartResource', function($scope, ChartResource) {
    $scope.charts = $.map(ChartResource.query(), function(i, resource) {
      chart = App.chart(resource)
      chart.draw()
      return chart
    }
  }
])

I think there is a third way, though, that I don't see because I don't quite grok how to leverage DI.

In other words, what's the AngularJS way to create an object with swappable persistence strategies?

Muleteer answered 2/7, 2013 at 21:48 Comment(2)
This should be more visible as it is a common problem when designing serious AngularJS applications.Barnabas
I'm thinking about that ChartResource would depend on Chart, and add a callback to the call which would take the received data and would make a new Chart instance out of it. That way a controller can call either ChartResource or LocalStorageChart (or a service that calls LocalStorageChart and uses ChartResource as a fallback\when time expires to invalidate). What do you think?Gargantua
R
3

The DataMapper strategy is actually already a form of Dependency Injection. You are passing the desired persistence implementation to the Chart constructor, and you can pass a different one in on a per-new basis. The non-DI way would be to hard-code the persistence implementation, as in your ActiveRecord-style example.

DataMapper isn't DI in the Angular.JS-specific sense of the term. Angular's DI doesn't really let you swap implementations around at run-time. However, you can use the official ngMock module to achieve this. ngMock is supposed to be used for testing, so this probably is not a terrific idea outside of that scenario.

There doesn't seem to be an Angular.JS-specific convention for this sort of thing. In fact, Angular.JS doesn't really have many conventions at all.

Rather than pass in the implementation in the constructor, you could alternatively offer a separate method for changing the persistence mechanism at any time. For example, to use ChartResource for initial network-based retrieval, then swapping to IndexedDB to store them locally:

// ChartResourceIndexedDB: same API as $resource but uses local IndexedDB
app.factory('ChartResourceIndexedDB', /* .. */);

app.controller('ChartsCtrl', [
  '$scope', 'ChartResource', 'ChartResourceIndexedDB',
  function($scope, ChartResource, ChartResourceIndexedDB) {
    $scope.charts = $.map(ChartResource.query(), function(i, resource) {
      chart = App.chart(resource)
      chart.draw();
      chart.setPersistence(ChartResourceIndexedDB);
      chart.save();
      return chart
    }
  }
]);
Rupertruperta answered 7/6, 2014 at 8:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.