1

I am new to AngularJS. Here's my question.

I have a page-level variable like this var isStateA = false. I have assigned this variable to my controllers variable like this:

var isStateA = false;

app.controller('AController',function($scope){
    $scope.shouldShow = isStateA;
});

app.controller('BController',function($scope){
    $scope.shouldShow = !isStateA;
});

The shouldShow properties are bind to ng-show accordingly.

The expected behavior is when I change isStateA to be true. The values in scope of the two controller should change, and as a result, they should perform show/hide logic.

But this is not happening with my code above. I am wondering if there's a way to do it? Like when the isStateA value changed, notify related properties to apply the latest value?

Thank you

Allan Jiang
  • 11,063
  • 27
  • 104
  • 165

3 Answers3

4

You are getting this wrong because you don't understand

  • AngularJS watchers
  • javascript pass by reference

AngularJS knows that values have changed in your application because of watchers. Watchers are assigned in many places, but in your case when you use $scope.shouldShow in the template. Changing the value of $scope.shouldShow will be picked up by angular and work, but changing isStateA will not, and that's because they are not the same variable. Here is the correct solution:

// Your code
app.service('stateAService', function() {
    this.isStateA = false;
})
app.controller('AController', function($scope, stateAService) {
    $scope.stateAService = stateAService;
})
app.controller('BController', function($scope, stateAService) {
    $scope.stateAService = stateAService;
})

// Template, controller A
<div ng-show="stateAService.isStateA">
</div>

// Template, controller B
<div ng-show="!stateAService.isStateA"></div>

// Some code, anywhere else in your app
app.controller('WhateverController', function($scope, stateAService) {
    this.hideBAndShowA = function() {
        stateAService.isStateA = true;
        // The change to stateAService gets picked up by the watchers, all good
    }
})
Peter Ashwell
  • 4,292
  • 2
  • 18
  • 22
2

Angular doesn't $watch isStateA variable, so the second change doesn't get "picked up" by Angular.

When you assign:

$scope.shouldShow = isStateA;

all you are doing is assigning a primitive boolean (by-value) to the scope variable.

The way you approach this is a BAD PRACTICE, but if you insist...

To solve this, you can either make $scope.shouldShow a function:

app.controller('AController',function($scope){
    $scope.shouldShow = function(){ return isStateA; };
});

app.controller('BController',function($scope){
    $scope.shouldShow = function(){ return !isStateA; };
});

Or an object (as suggested by @aj_r).

Still, this would not work since you need to let Angular know that a change has happened. This is the BAD PRACTICE part (taken from this SO answer):

// assuming <body> is the root element of your Angular app
var $body = angular.element(document.body); 
var $rootScope = $body.scope().$root;        
$rootScope.$apply(function () {               
   isStateA = !isStateA;
});
Community
  • 1
  • 1
New Dev
  • 48,427
  • 12
  • 87
  • 129
1

isStateA is passed by value into $scope.shouldShow, meaning that the value is copied and no reference is created.

There is a simple solution, but I DO NOT recommend it because it does not follow proper patterns for AngularJS. If you want that then skip to the end of this answer.

You should really only update $scope variables from inside the controller (or a directive if you must have DOM interaction). I'm not sure what you are trying to do with this, but there should be a way to structure your code so that you can get rid of isStateA, and set $scope.shouldShow = true directly in your controller. Why do you need to access the same scope variable in 2 different controllers? Maybe you should combine them into a common controller, or create a service/factory that you can inject into both controllers.

The simple (but not recommended) solution is to create a reference using an object:

var isStateA = { shouldShow: false };

app.controller('AController',function($scope){
    $scope.state = isStateA;
});
// Now whenever you set isStateA.shouldShow, it should update the model and the view.

Note: if you set isStateA .shouldShow outside of a controller, then your view will NOT update because Angular won't know about it. As such, it won't run a digest cycle.

AJ Richardson
  • 6,610
  • 1
  • 49
  • 59
  • Any idea why the second button doesn't work and the first one does? Looks like it's how Angular monitors changes to $scope. How would you get it to work? http://plnkr.co/edit/SbzhkiCOtevIaXFm29lC?p=preview – Wayne Ellery Dec 28 '14 at 06:33
  • This would only work when it first runs. Changes to shouldShow outside of Angular would not be "picked up" since they happen outside of Angular's digest cycle (i.e. Angular would not know that the change has occurred). – New Dev Dec 28 '14 at 06:39
  • Based on your plnkr, your controllers can be easily combined into one controller, which eliminates the need for all of this, although I assume your situation is more complicated than your plnkr. Still, have a look at my fork to see what I mean: http://plnkr.co/edit/RWsn3lKZrwuWb5F9ZRb0?p=preview – AJ Richardson Dec 28 '14 at 06:47