5

How can I switch out a service on-the-fly and have all components (relying on the service) automatically be bound to the data on the new strategy?

I have a Storage service and two storage strategies, StorageStrategyA and StorageStrategyB. Storage provides the public interface to controllers and other components to interact with:

angular.module('app').factory('Storage', function ($injector) {
    var storage;
    var setStrategy = function (name) {
        storage = $injector.get(name);
    };

    setStrategy('StorageStrategyB');

    return {
        getItems: function () {
            return storage.getItems();    
        }
        // [...]
    };
});

But when the strategy is changed, the two-way binding breaks and the view doesn't update with items from getItems() from the new strategy.

I've created a Plunker to illustrate the problem.

Is there a way to combine the strategy pattern with AngularJS and keep the two-way binding?

Please note that in my actual app I cannot just call Storage.getItems() again after the strategy has been changed, because there are multiple components (views, controllers, scopes) relying on Storage and the service change happens automatically.

Edit:
I have forked the Plunker to highlight the problem. As you can see, the data in the upper part only updates because I manually call Storage.getItems() again after the strategy has been changed. But I cannot do that, because other component - for example OtherController - are also accessing data on Storage and also need to automatically get their data from the new strategy. Instead, they stay bound to the initial strategy.

bernhardw
  • 3,239
  • 2
  • 20
  • 16
  • Can you explain more on "because there are multiple components (views, controllers, scopes) relying on Storage and the service change happens automatically." – Sai Mar 08 '14 at 18:10
  • Of course: `Storage` is actually a specific service for handling projects. There is a view/controller to list projects, one to show some graphs and so forth. In addition, a `Client` service relies on the `Project` service. They are all on the same page and use the `Project`'s methods and data. But they all don't care what strategy is used to persist the data. The strategy switches between *local* and *http*, whether there is an active connection or not. When I call `getItems()` on strategy change, all the other parts would still work on the old strategy/data. At least that's what I assume. – bernhardw Mar 08 '14 at 18:37
  • I've updated my question with a new Plunker to better show the actual problem. – bernhardw Mar 08 '14 at 19:59

3 Answers3

1

Javascript works on references. Your array items in app is same reference as items of strategyB items initially with the below statement and when you update the StrategyB items automatically items in your view gets updated(since same reference).

$scope.items = Storage.getItems();

So, when you switch strategy you are not changing the reference of items. It still points to StrategyB items reference.

You have to use the below mechanism to change the reference.

Then you can do something where you can communicate between controllers to change the items reference.

Please find the plunkr I have updated.

$rootScope.$broadcast("updateStrategy");

And then update your item list and others.

$scope.$on("updateStrategy",function(){
  $scope.name = Storage.getName();
  $scope.items = Storage.getItems(); //Here changing the reference.
  //Anything else to update
});
Sai
  • 2,068
  • 19
  • 24
  • Thank you for your answer. But if somehow possible, I would like to keep my components relying on `Storage` clean of any workarounds. As far as they are concerned, the service just updated its data. – bernhardw Mar 08 '14 at 20:55
  • @bernhardw Updated my answer. Hope it makes it clear. Don't get confused between two way data binding and javascript references. They are not the same. – Sai Mar 08 '14 at 21:49
0

the two way binding is still ok, you have a reference issue. when the AppController set up the $scope.items set to the StorageStrategyB items, then when you switch to StorageStrategyA, the AppController $scope.items is still set to StorageStrategyB items.

alonn24
  • 238
  • 2
  • 15
  • That's true, and that's my problem. I would like `items` to populate with data from the new strategy. `StorageStrategyA` in your example. – bernhardw Mar 08 '14 at 18:45
  • you can add a $scope.getItems to AppController that returns the Storage.getItems() and call that from the view: `ng-repeat="item in getItems()" – alonn24 Mar 08 '14 at 18:47
  • Thanks for your answer. But unfortunately multiple other components (even another service) rely on `Storage`, so while your suggestion works for that `ng-repeat`, it wouldn't everywhere else. Please see the edit of my question. – bernhardw Mar 08 '14 at 20:02
0
angular.module('app').controller('AppController', function ($scope, Storage) {



    Storage.setStrategy('StorageStrategyB');

    $scope.current = Storage.getName();

    $scope.items = Storage.getItems();  

    $scope.setStrategy = function (name) {
        Storage.setStrategy(name);
        $scope.current = Storage.getName();
        $scope.items = Storage.getItems();  
        console.log( $scope.items);
        console.log($scope.current);
    };

    $scope.addItem = function () {
        Storage.addItem($scope.item);
        $scope.item = '';
    };

});

You forgot

 $scope.items = Storage.getItems(); 

nice question :)

plnkr

Whisher
  • 31,320
  • 32
  • 120
  • 201
  • Thanks, but please see my note "Please note that [...]", the edit and the second Plunker :) The problem is that I have multiple other components also relying on `Storage`, and they don't update. – bernhardw Mar 08 '14 at 21:01