4

I need to use a datacontext in serveral controllers.
The datacontext can be change in different ways (by ajax call, by user/view, by server).
All controllers using the datacontext should be notified when the data in the datacontext has changed.

datacontext:

var userDataContext = { firstName: "John", lastName: "Doe" };

I simulated in every example a backend change with setTimeout

setTimeout(function() {
    console.log("change firstname");
    userContext.firstName= "Bart";
}, 3000);

Here is an fiddle example in Knockout, where it works fine.
http://jsfiddle.net/8ct0L1zu/4/


I also tried to do it with Angular but it partially work.

  • When I pass the complete object to the controller(by reference), changes works between controllers.
    When changing the datacontext in javascript/backend, it doesn't change my views.

  • I also prefer to have only the firstname and lastname in the controlers scope, and not the complete object.
    But in this case, nothing works.
    Reason: In our application there will be a lot of datacontexts with arrays of big objects.

http://jsfiddle.net/19xv3skn/1/


To really make it work, I used Angular with knockout. Angular for databinding and knockout for datacontext. This works fine, but I don't really like this solution.
http://jsfiddle.net/7chp5xLa/2/


Is there a best practice or better way to work with a observable datacontext in Angular?

Sébastien
  • 88
  • 1
  • 9

3 Answers3

2

I think the issue might be the way you think about managing data in your AngularJS app. You are editing the userContext data outside of the AngularJS lifecycle. Instead of managing what AngularJS 'sees', you edit the data after giving it to AngularJS. Instead, consider changing the userContext state from within Angular, there where you need it

In your example, you have two controllers that have the same data on $scope, maybe you should refer to a common parent controller that manages the data for the $scope instead:

jsfiddle

app.controller('ParentCtrl', function ($scope, userContext, $timeout) {
   $scope.firstName = userContext.firstName;
   $scope.lastName = userContext.lastName;
   $scope.user = userContext;
   $timeout(function() {
       console.log("change firstname");
       userContext.firstName= "Bart";
   }, 3000);
});

Things get more interesting when you want to do different things to and with userContext per controller. In that case you may need some synchronization between the controllers depending on your needs. There are numerous answers to questions about sharing state between controllers and communicating between controllers.

Benny Bottema
  • 11,111
  • 10
  • 71
  • 96
  • The idea is "Having no link between the datacontext and view". When I am using a ParentCtrl my data is linked to a specific view. My data doesn't need any knowledge about ui, its just data ready to be used by any controller/view. – Sébastien Sep 02 '14 at 08:10
  • It's true the userContext is outside his lifecycle. Angular observes variables using a technique called as dirty checking. So you can only work with variables inside a controller. Knockout uses the Observable pattern. Which for me works much better. http://blog.nebithi.com/knockoutjs-vs-angularjs/ There will be no perfect solution for me :s – Sébastien Sep 02 '14 at 08:46
  • True, it completely depends on your needs whether Knockout or AngularJs is a best fit. – Benny Bottema Sep 02 '14 at 09:14
  • >The idea is "Having no link between the datacontext and view".< But that's exactly the function of a controller. A Controller bridges the view and the data, by occupying the $scope with a model and defining behavior that makes the interaction possible. From then on the external data, userContext, is irrelevant, as all the data you manipulate is now on the $scope (or on the controller or service). Either you manipulate it within AngularJS' lifecycle, or you send it somewhere. The need for modifying yuor external data directly should be gone. – Benny Bottema Sep 02 '14 at 09:16
2

You have a couple of options.

1 - use a service and broadcast events.

// service
app.service("DataService", function($rootScope) {
    var _name = '';
    var result = {
        setName : function(name) {
            _name = name;
            $rootScope.$broadcast("nameChanged", _name);
        },
        getName : function() {
            return _name;
        }
    }
    return result;
});

// controller(s)
app.controller("ObservingController", function($scope) {
    $scope.$on("nameChanged", function(name) {
        console.log("Something changed with name.");
    });
});

// other controllers
app.controller("ModifyingController", function($scope, DataService) {
    // triggers the broadcast
    DataService.setName("bob");
});

2) Watch for changes with an expression

// controller - use the same DataService as above
app.controller("WatchingController", function($scope, DataService) {
    // bind the service to the scope for the expression
    $scope.DataService = DataService;
    $scope.$watch("DataService.getName()", function(newVal, oldVal) {
        console.log("Name changed " + newVal);
    });
});

It's up to you what you prefer. My service is also written somewhat poorly and is meant only to be an example. The $rootScope is only used to broadcast to all child scopes. I don't advise storing data there.

In the case of the data context, if you wrap that in a service and expose it, you can bind the service (or variables exposed by the service) to the scope like in the watch example and your watches should trigger.

// service
app.service("DataContextService", function() {
    var context = {
        firstName : "",
        lastName : ""
    };
    return {
        context : context
    };
});

// controller
app.controller("Watcher", function($scope, DataContextService) {
    $scope.context = DataContextService.context;
    $scope.$watch("context.firstName", function(newVal, oldVal) {
        console.log("Change..");
    });
});

app.controller("Modifier", function($scope, DataContextService) {
    // causes the watch to trigger
    DataContextService.context.firstName = "Bob";
});
NG.
  • 22,560
  • 5
  • 55
  • 61
  • This is quiet the same as my thirth solution. Except I am using Knockout and in your example, you are programming it yourself. Knockout has a complete observable pattern that is very powerfull (with computed, good perf, etc), for this reason I prefer to use Knockout then for the DataContextService. – Sébastien Sep 02 '14 at 08:35
0

Probably i didnt got what you need correctly but i suggest you to get a look at $broadcast

You can broadcast events between controllers, so if data change in controllerA then controllerB can listen and execute your logic (and viceversa) when that event is fired.

Here is an explanation : http://mariuszprzydatek.com/2013/12/28/sharing-data-between-controllers-in-angularjs-pubsub-event-bus-example/

OR a dirty way could be to use $rootScope to handle the same data in all the application, so when it change it change everywhere in the workflow.

Hope it helps

itsme
  • 48,972
  • 96
  • 224
  • 345