Gerome Bochmann
Gerome Bochmann

Reputation: 494

Strange behavior with variable names and data between controllers

I have a controller that is supposed to write text from an input field to the screen. If I use this controller by itself everything works as expected:

(function() {

    angular.module('test', []);

    function OneCtrl() {
        vm = this;
        vm.changeHandler = changeHandler;
        vm.item = "";
        vm.value = "";

    }

    angular
        .module('test')
        .controller('OneCtrl', OneCtrl);



    var changeHandler = function() {
        vm.value = vm.item;
        console.log(vm.item);
    };


})();

Try here: http://codepen.io/minuskruste/pen/qdrZqq

However, if I add another controller with the same behavior something really weird happens. First of all, the input from field 1 is not sent to console anymore and the text is also not inserted into the html body. Second of all, when I type something into input field 2 it behaves correctly. If I now go back to field 1 and type there, suddenly field 2 input is output to console, even though controller two was never told to do so! This is controller 2:

(function(){


function TwoController(){
    vm = this;
    vm.changeHandler = changeHandler;
    vm.item = "";
    vm.value = "";

}

angular
.module('test')
.controller('TwoController', TwoController);

var changeHandler = function() {
    vm.value = vm.item;
};

})();

Try here: http://codepen.io/minuskruste/pen/QbpNdY

Is this normal behavior? I was very surprised by it. I also checked if maybe the changeHandler() leaked to global space but since I've put everything in closures that's not the case. Furthermore this is consistent over different platforms i.e. Chrome and FF. Any ideas?

Upvotes: 0

Views: 57

Answers (3)

trevor
trevor

Reputation: 2300

Part of the issue you are having is that you are declaring vm without the var keyword, which makes it a global variable.

However, vm with the var keyword is in the local scope of the controller. As a result, it's not available to the changeHandler() anymore. If you reorder your code and declare changeHandler() inside the controller, it will work.

(function() {
  angular.module('test', []);

  function OneCtrl() {
    var vm = this;		
    vm.item = "";
    vm.value = "";    
    vm.changeHandler = function() {
      vm.value = vm.item;
      console.log(vm.item);
    }
  }  

  angular
  .module('test')
  .controller('OneCtrl', OneCtrl);
})();

(function(){
  function TwoController() {			
    var vm = this;		
    vm.item = "";
    vm.value = "";    
    vm.changeHandler = function() {
      vm.value = vm.item;
      console.log(vm.item);
    }
  }    

  angular
  .module('test')
  .controller('TwoController', TwoController);
})();
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.min.js"></script>
<body ng-app="test">
  <h1>Choice array</h1>
  <div>
    <form novalidate ng-controller="OneCtrl as one">
      <input type="text" ng-change="one.changeHandler()" ng-model="one.item">
      <div>{{one.value}}</div>
    </form>
    <br><br><br>
  </div>
  <div>
    <form novalidate ng-controller="TwoController as two">
      <input type="text" ng-change="two.changeHandler()" ng-model="two.item">
      <div>{{two.value}}</div>
    </form>
  </div>
</body>

Upvotes: 1

Kmart2k1
Kmart2k1

Reputation: 563

Here is a good article on the Controller As syntax.

http://www.johnpapa.net/angularjss-controller-as-and-the-vm-variable/

If you declare the vm variable in each controller it will prevent the overwriting behavior you are seeing. Javascript is functional scoped, which means if it doesn't find the variable declaration for vm in the current function, it will go up the prototypical chain until it finds the declaration (var vm). If it doesn't find any declaration in the global scope, it will automatically create one for you. By declaring inside each controller you will prevent them from both sharing the same global scope.

function OneCtrl() {
    // This construction makes sure I know which context is addressed. I can now hand vm (view-model) inside an object and the context doesn't change.
    var vm = this;
    vm.item = "";
    vm.value = "";

     vm.changeHandler = function() {
      console.log(vm.item);
      vm.value = vm.item;
    };
  }

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

Upvotes: 0

Andrey
Andrey

Reputation: 4050

This happens because you're using global "vm" variable, which containg this reference of controller. And with second controller you're overwriting vm variable with reference to second controller.

I've updated your code to use proper this references

Also angular supports another data binding approach with special $scope object: https://docs.angularjs.org/guide/scope

(function() {

	angular.module('test', []);

	function OneCtrl($scope) {

		// This construction makes sure I know which context is addressed. I can now hand vm (view-model) inside an object and the context doesn't change.
		this.changeHandler = angular.bind(this, changeHandler);
		this.item = "";
		this.value = "";

	}

	angular
		.module('test')
		.controller('OneCtrl', OneCtrl);



	var changeHandler = function() {
		this.value = this.item;
		console.log(this.item);
	};


})();

(function(){


	function TwoController(){
		
		// This construction makes sure I know which context is addressed. I can now hand vm (view-model) inside an object and the context doesn't change.
		this.changeHandler = angular.bind(this, changeHandler);
		this.item = "";
		this.value = "";
		
	}

	angular
	.module('test')
	.controller('TwoController', TwoController);

	var changeHandler = function() {
		this.value = this.item;
	};


})();
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.min.js"></script>
<body ng-app="test">

	<h1>Choice array</h1>

	<div>
		<form novalidate ng-controller="OneCtrl as one">

			<input 
			type="text" 
			ng-change="one.changeHandler()" ng-model="one.item">

			<div>{{one.value}}</div>
		</form>
		<br>
		<br>
		<br>

	</div>

	<div>
		<form novalidate ng-controller="TwoController as two">

			<input 
			type="text" 
			ng-change="two.changeHandler()" ng-model="two.item">

			<div>{{two.value}}</div>
		</form>
	</div>




</body>

Upvotes: 1

Related Questions