Strange behavior with variable names and data between controllers - javascript

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?

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>

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>

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

Related

angularjs calling outer controller from nested ng controllers with ng-change

I'm trying to use angularjs to create a page that does the following:
Is initially empty, save for a dropdownlist that is automatically
populated with apps.
upon selecting one of those apps, data about it will be called from
another controller to the page.
I was successfully able to get the dropdownlist to automatically populate. however, I'm having issues getting it to make the page with ng-change, which I thing is due to the nested ng-controllers. The chartappsuccessfullogins function is not being called at all in my browser. Can anyone help me? Code is below:
My main html page. Note the use of ng-init:
<div ng-controller="chartsuccessfulapploginsController">
<div ng-controller="allappsController" ng-init="add()">
<form name="myForm">
<label for="repeatSelect"> Repeat select: </label>
<select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect" ng-change="chartsuccessfulapploginsController.add(value)">
<option ng-repeat="option in data.availableOptions" ng-init="Index = $index" value="{{data.availableOptions[Index].id}}" ng-model="APPID" >{{data.availableOptions[Index].name}}</option>
</select>
</form>
<hr>
<p> {{data}}</p>
<p> {{data.id[0]}}</p>
<p> {{data.name[0]}}</p>
<tt>repeatSelect = {{data.repeatSelect}}</tt><br/>
</div>
<p>{{returnCount}}</p>
<table border = "1">
<tr>
<td>{{chartObject.data}}</td>
<td>{{returnCount}}</td>
</tr>
</table>
<div google-chart chart="chartObject" style="height:600px; width:100%;"></div>
</div>
My get all apps controller. The html page above relies on this to populate the dropdownlist.
angular.module('scotchApp').controller('allappsController',['$scope', function($scope){
var userurl='';
$scope.add = function(){
userurl = 'http://localhost:8085/rest/uafapp/appslist';
var userdata = {};
var userconfig =
{
headers: {
'Content-Type':'application/json'
}
};
var userPostRequest = $.get(userurl, userdata, userconfig);
var userjson = '{\"USER_DATA_RETRIEVED\" : \"fail\"}';
userPostRequest.done(function(userdata){
userjson = JSON.stringify(userdata);
console.log("userjson :: " + userjson);
var postResponse = jQuery.parseJSON(userjson);
$scope.returnName = postResponse['apps'];
var availableOptionsArray = [];
for(i = 0; i < postResponse['apps'].length; i++){
var availableOptions = {};
availableOptions['id'] = postResponse['apps'][i]['appId'];
availableOptions['name'] = postResponse['apps'][i]['appName'];
availableOptionsArray[i] = availableOptions;
}
var returnData = {};
returnData['repeatSelect'] = null;
returnData['availableOptions'] = availableOptionsArray;
$scope.data = returnData;
console.log($scope.returnData);
$scope.$apply()
});
};
}]);
Part of the controller that defines the chart. It's pretty long, so I didn't include the irrelevant code.
angular.module('scotchApp').controller('chartsuccessfulapploginsController',['$scope','$route','$http','AuthTokenService', function($scope, $route, $http, AuthTokenService){
var appurl = '';
var failedappurl= '';
$scope.add = function(APPID) {
...}
Is your allappsController within your chartsuccessfulapploginsController in your controller file?
It should be inside because allappsController is the child scope, and chartsuccessfulapploginsController is the parent scope. You are trying to access the parent scope from the child scope.
If it is not inside, it thinks that ng-change="chartsuccessfulapploginsController.add(value)" is a new controller.
If that is the issue, the fix would be something like this:
angular.module('scotchApp').controller('chartsuccessfulapploginsController',['$scope','$route','$http','AuthTokenService', function($scope, $route, $http, AuthTokenService){
var appurl = '';
var failedappurl= '';
$scope.add = function(APPID) {} ...
//allappsController inside chartsuccessfulapploginsController
angular.module('scotchApp').controller('allappsController',['$scope',function($scope){
var userurl='';
$scope.add = function(){ ... };
}]);
}]);
Check this out: Use ng-model in nested Angularjs controller

ng-change is not firing when changing value from service

I need to reflect some changes to controller B (inside some event) when I make change at controller A. For that I am using a service.
When I am changing service value from FirstCtrl, ng-change is not firing at SecondCtrl. Is there anything I have missed or need to change?
Please note that I am using angular 1.5.6. and don't want to use watch or even scope.
Below is my code.
var myApp = angular.module('myApp', []);
myApp.factory('Data', function() {
return {
FirstName: ''
};
});
myApp.controller('FirstCtrl', ['Data',
function(Data) {
var self = this;
debugger
self.changeM = function() {
debugger
Data.FirstName = self.FirstName;
};
}
]);
myApp.controller('SecondCtrl', ['Data',
function(Data) {
var self = this;
self.FirstName = Data;
self.changeM = function() {
alert(1);
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="FirstCtrl as c">
<input type="text" ng-model="c.FirstName" data-ng-change="c.changeM()">
<br>Input is : <strong>{{c.FirstName}}</strong>
<div ng-controller="SecondCtrl as c1">
Input should also be here: {{c1.FirstName}}
<input type="text" ng-model="c1.FirstName" data-ng-change="c1.changeM()">
</div>
</div>
<hr>
</div>
As you dont want to use $scope trying modifying the code in order to use $emit and $on feature in angular js to communicate between two controllers. You can refer this link.
var myApp = angular.module('myApp', []);
myApp.factory('Data', function() {
return {
FirstName: ''
};
});
myApp.controller('FirstCtrl', ['Data',
function(Data) {
var self = this;
debugger
self.changeM = function() {
debugger
//Data.FirstName = self.FirstName;
Data.$on('emitData',function(event,args){
Data.FirstName=args.message
document.write(Data.FirstName)
})
};
}
]);
myApp.controller('SecondCtrl', ['Data',
function(Data) {
var self = this;
self.FirstName = Data;
self.changeM = function() {
Data.$emit('emitData',{
message:Data.FirstName
})
};
}
]);
The only way then is to directly copy the reference of the data object within the controller. Note that you don't need ng-change to update the value then.
If you want something else, either wrap the FirstName in a sub object of Data and do the same i did :
Data = {foo:'FirstName'};
Or use $watch since it's the whole purpose of that function.
Here is a working code with copying the Data object in the controller.
var myApp = angular.module('myApp', []);
myApp.factory('Data', function() {
return {
FirstName: ''
};
});
myApp.controller('FirstCtrl', ['Data',
function(Data) {
var self = this;
self.Data=Data;
debugger
self.changeM = function() {
debugger
};
}
]);
myApp.controller('SecondCtrl', ['Data',
function(Data) {
var self = this;
self.Data = Data;
self.changeM = function() {
alert(1);
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="FirstCtrl as c">
<input type="text" ng-model="c.Data.FirstName" data-ng-change="c.changeM()">
<br>Input is : <strong>{{c.Data.FirstName}}</strong>
<div ng-controller="SecondCtrl as c1">
Input should also be here: {{c1.Data.FirstName}}
<input type="text" ng-model="c1.Data.FirstName" data-ng-change="c1.changeM()">
</div>
</div>
<hr>
</div>
The only way I know to solve the problem is using watch, unfortunately. (I am new to angular.)
From the ngChange document (https://docs.angularjs.org/api/ng/directive/ngChange):
The ngChange expression is only evaluated when a change in the input value causes a new value to be committed to the model.
It will not be evaluated:
if the value returned from the $parsers transformation pipeline has not changed
if the input has continued to be invalid since the model will stay null
**if the model is changed programmatically and not by a change to the input value**

Angular controller's attribute not accessible outside its function

I have a form with two input fields (session.email and session.psw) bound to the LoginController.session attribute. When I click the reset button, I call the LoginController.reset() function.
I would like make it clear the session attribute, utilizing the variable sessionDefault (empty). However it works just one time, if I reset two times the form, sessionDefault is undefined.
How could I make it as a constant attribute of the controller?
app.controller('LoginController', function ($scope)
{
this.session={};
var sessionDefault=
{
email : "",
psw: ""
};
this.reset = function()
{ this.session = sessionDefault; };
});
Try out this out
for reset function just reset it with sessionDefault copy like as shown below
vm.reset = function () {
vm.session = angular.copy(sessionDefault);
};
here this refers to the controller instance
Notice that I use var vm = this; and then I decorate vm with the members that should be exposed and data-bindable to to the View. vm simply denotes view modal
This does 3 things for me.
Provides a consistent and readable method of creating bindings in my controllers
Removes any issues of dealing with this scoping or binding (i.e. closures in nested functions)
Removes $scope from the controller unless I explicitly need it for something else
Working Demo
script
var app = angular.module('myApp', []);
app.controller('LoginController', function ($scope) {
var vm = this;
vm.session = {};
var sessionDefault = {
email: "",
psw: ""
};
vm.reset = function () {
vm.session = angular.copy(sessionDefault);
};
});
html
<div ng-app='myApp' ng-controller="LoginController as login">
Email:<input type="text" ng-model="login.session.email"/>{{login.session.email}}
<br>
Psw:<input type="text" ng-model="login.session.psw"/>{{login.session.psw}}
<br>
<button ng-click="login.reset()">Reset</button>
</div>
Take a look at this beautiful stuff.
AngularJS’s Controller As and the vm Variable
this.reset = function()
{ this.session = sessionDefault; };
The this in this context refers to the function (reset). If you want to access the 'original' this you need to store it in a variable.
app.controller('LoginController', function ($scope)
{
this.session={};
var sessionDefault=
{
email : "",
psw: ""
};
var self = this;
this.reset = function()
{ self.session = angular.clone( sessionDefault); };
});

alternative method instead $watch in angularjs

I have a scope variable, when it returns true, i need to trigger some events or do something. I my case, the every first time, the scope variable returns undefined and later it returns true. In this case i used $watch method to get the expected funcionality. Is there any alternative approach to do the same instead using $watch ?
scope.$watch () ->
scope.initiateChild
, (value) ->
if value is true
$timeout ->
scope.buildOnboarding()
, 1000
You can try using AngularJS $on(), $emit() and $broadcast().
Here is an example: http://www.binaryintellect.net/articles/5d8be0b6-e294-457e-82b0-ba7cc10cae0e.aspx
You can use JavaScript getters and setters without any expense of using $watch.
Write code in the setter to do what you want when angular changes the your model's value you are using in scope. It gets null or an a State object as user types. Useful for working with type ahead text boxes that have dependencies on each other. Like list of counties after typing state without user selecting anything.
Here is some pseudo style code to get the idea.
<input ng-model="searchStuff.stateSearchText" />
<div>{{searchStuff.stateObject.counties.length}}</div>
<div>{{searchStuff.stateObject.population}}</div>
$scope.searchStuff=new function(){var me=this;};
$scope.searchStuff.stateObject = null;
$scope.searchStuff.getStateObjectFromSearchText = function(search){
// get set object from search then
return stateObject;
};
$scope.searchStuff._stateSearchText= "";
Object.defineProperty($scope.searchStuff, 'stateSearchText', {
get: function () {
return me._stateSearchText;
},
set: function (value) {
me,_stateSearchText = value;
me.stateObject = getStateObjectFromSearchText (value);
}
});
See this fiddle: http://jsfiddle.net/simpulton/XqDxG/
Also watch the following video: Communicating Between Controllers
A sample example is given below
Html:
<div ng-controller="ControllerZero">
<input ng-model="message" >
<button ng-click="handleClick(message);">LOG</button>
</div>
<div ng-controller="ControllerOne">
<input ng-model="message" >
</div>
<div ng-controller="ControllerTwo">
<input ng-model="message" >
</div>
javascript:
var myModule = angular.module('myModule', []);
myModule.factory('mySharedService', function($rootScope) {
var sharedService = {};
sharedService.message = '';
sharedService.prepForBroadcast = function(msg) {
this.message = msg;
this.broadcastItem();
};
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
return sharedService;
});
function ControllerZero($scope, sharedService) {
$scope.handleClick = function(msg) {
sharedService.prepForBroadcast(msg);
};
$scope.$on('handleBroadcast', function() {
$scope.message = sharedService.message;
});
}
function ControllerOne($scope, sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'ONE: ' + sharedService.message;
});
}
function ControllerTwo($scope, sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'TWO: ' + sharedService.message;
});
}
ControllerZero.$inject = ['$scope', 'mySharedService'];
ControllerOne.$inject = ['$scope', 'mySharedService'];
ControllerTwo.$inject = ['$scope', 'mySharedService'];

knockout binding print function text instead of variable value

I'm writing a small application in JS and I decided to use Knockout.
Everything work well except from a single value that is not printed correctly and I don't understand why.
This is the html view where error appends (viaggio.arrivo is not visualized, and in place of correct value appears a function code like this "function c(){if(0 <arguments.length){if ..." and so on)
<input data-bind="value: viaggio.arrivo" />
And this is the javascript View Model.
Code is pretty long so I put it in a jsFiddle.
function ViewModel() {
function Viaggiatore(nome, cognome, eta, citta) {
var self = this;
self.nome = nome; self.cognome = cognome;
self.eta = ko.observable(eta);
self.citta = ko.observable(citta);
}
function Viaggio(viaggiatore, partenza, arrivo, mete) {
var self = this;
self.viaggiatore = ko.computed(viaggiatore);
self.partenza = ko.computed(partenza);
self.arrivo = ko.observable(arrivo);
self.mete = ko.computed(mete);
}
self.viaggiatore = new Viaggiatore("Mario", "Rossi", 35, "Como");
self.viaggio = new Viaggio(
function(){ return self.viaggiatore.nome+" "+self.viaggiatore.cognome; },
function(){ return self.viaggiatore.citta; },
"Roma",
function(){ return "mete" ;}
);
}
ko.applyBindings(new ViewModel());
I think you need brackets on one of your parameters, like so:
<p data-bind="text: viaggio.partenza()"></p>
Check out the updated fiddle: http://jsfiddle.net/mGDwy/2/

Categories

Resources