2

So I am trying to code with AngularJS the best practice way without using $scope or $rootScope, but named controllers.

http://codepen.io/anon/pen/LpxOwx

Here's the Angular part:

.controller('demoController', function(Fac){
  var vm = this;
  vm.theme = Fac.theme;
  vm.changeTheme = function(theme) {
    Fac.changeTheme(theme);
    vm.theme = Fac.theme;
  };
 })

.controller('secondController', function(Fac){
  var vm = this;
  vm.changeTheme = function(theme) {
    Fac.changeTheme(theme);
  };
 })

 .factory('Fac', function() {
  var fac = {
  theme: '',
  changeTheme: function(theme) {
    fac.theme = theme === 'indigo' ? 'lime' : 'indigo'; 
  }
}
return fac;
});

I have forked a codepen to illustrate my problem. The demoController works as intended and updates the view. But it doesn't "work" when using the same Factory in the secondController. I assume that it works, but just doesn't update in the demoController.

The way I understood Angular data binding is, that all I need to do is use the Factory to share data between Controllers which bind to them and update accordingly on change.

I am sure, it's a very stupid mistake I'm doing here. What am I doing wrong?

UPDATE:

Thanks for the answers! I've encountered a new (related?) problem though:

http://plnkr.co/edit/cRa2FTkpbIsJ9TLzu5bD?p=preview

Can you tell me how I bind the user between LoginCtrl and ToolbarCtrl so that ng-show and ng-hide can update upon login and logout? When I login the Toolbar doesn't update accordingly. I have to refresh the whole page instead to make the login visible. When I logout it works because I explicitly set vm.user= {}; in the ToobarCtrl.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
dtrinh
  • 163
  • 1
  • 11

2 Answers2

0

Here is a working version of your codepen

I simply used a subvar to bind into view. Binding primitive var from factory to controller often lead to this king of trouble.

The factory look like this :

.factory('Fac', function() {
    var fac = {
      theme: {value:'indigo'},
      changeTheme: function(theme) {
        console.log(theme);
        fac.theme.value = theme === 'indigo' ? 'lime' : 'indigo'; 
      }
    }

both controllers look like this :

.controller('secondController', function(Fac){
      var vm = this;
      vm.theme = Fac.theme;
      vm.changeTheme = Fac.changeTheme;
  })

and i used it like this in the view :

<md-button href="#" class="md-primary md-raised" ng-click=vm.changeTheme(vm.theme.value)>
  Change theme
</md-button>

Hope it helped,

EDIT : Here is an answer where i explain why this actually don't work with a primite var

Community
  • 1
  • 1
Okazari
  • 4,597
  • 1
  • 15
  • 27
  • Thanks mate, I would never have solved this on my own! :) – dtrinh Oct 01 '15 at 11:25
  • @D.Trinh Don't forget to upvote and valid the answer if it did solve your issue – Okazari Oct 01 '15 at 14:02
  • Sorry, I am new to SO so it appears that I can't vote yet :/ I did encounter a new problem though.. http://plnkr.co/edit/cRa2FTkpbIsJ9TLzu5bD?p=preview Can you tell me how I bind the user between LoginCtrl and ToolbarCtrl so that ng-show and ng-hide can update upon login and logout? When I login the Toolbar doesn't update accordingly. I have to refresh the whole page instead to make the login visible. When i logout it works because i explicitly set vm.user= {}; in the ToobarCtrl. – dtrinh Oct 01 '15 at 15:05
  • @D.Trinh Same thing here http://plnkr.co/edit/UFAICTKzKcpgxxYgrkNd?p=preview i used charliefl answer to do this one. I shares vm.Auth instead of vm.user. in the html i use vm.Auth.user. In this case, when you do vm.user = Auth.user. if in you Auth factory you do "this.user = {...}" it will loose the reference aswell. Keep in mind that if you want to keep an eye on a var or an object, you need to track the upper reference and not directly this reference (as it can be removed) – Okazari Oct 02 '15 at 07:50
  • Awesome, thanks again! Could you explain how it's different from the previous example? Is there a rule of thumb to when to use which concept? – dtrinh Oct 02 '15 at 08:51
  • @D.Trinh That's just a matter of taste, if i wanted to do it like the first one, i would have to create a new var. Being lazy i didnt. – Okazari Oct 04 '15 at 21:45
  • Could you show the same with your first method? I don't understand why it doesn't work since I pass an object and no primitive this time.. – dtrinh Oct 06 '15 at 11:51
  • @D.Trinh If you re-asign a whole new object it will loose its reference (like a primitive var). Same thing for an array, assigning a new array will loose the reference. Since i share the "service" Auth object, i will keep the reference. Just keep in mind that if you need to change a whole value (primitive, object or array), you need to share it in an object. Bind the contening object (that you will never fully reasign) and you'll keep the binding forever. – Okazari Oct 06 '15 at 22:00
0

One simple approach is pass your factory object all the way through to the view:

.controller('demoController', function(Fac){
    var vm = this;
    vm.Fac= Fac;      
 });

Then in the view use the properties of the object:

<md-button href="#" class="md-primary md-raised" ng-click=vm.Fac.changeTheme(vm.Fac.theme)>
  Change theme
</md-button>

Current theme is {{vm.Fac.theme}}

Now you don't have primitives being passed to child scopes and controller is much leaner.

Note that this won't work for all situations that may have some controller logic needed within factory callbacks but for many situations it avoids repeating creating controller properties that may not really be needed.

You can also partition your factory object in ways to minimize duplicating the same properties in the scope where it is practical

DEMO

charlietfl
  • 170,828
  • 13
  • 121
  • 150
  • Thanks for the input. I haven't seen example code with that method yet. Is there any downside to this? – dtrinh Oct 01 '15 at 15:23