How to properly use a checkbox to change $rootScope value in AngularJS
I am trying to create a multilingual section to an app, that allows the users to front to choose their language of preference when the app loads.
In doing so, I set the $rootScope.language to their preferred language.
Since that occurs only once at the load of the app, I wanted to add a preferences section where they can alter their choice, as needed.
In my Ionic V1 framework, I had created this in my template for the users to choose their language:
<ion-list>
<ion-radio ng-model="lang" ng-value="'en_US'" ng-click="updateLanguage('en_US')">English</ion-radio>
<ion-radio ng-model="lang" ng-value="'es_MX'" ng-click="updateLanguage('es_MX')">Espanol</ion-radio>
<ion-radio ng-model="lang" ng-value="'fr_CA'" ng-click="updateLanguage('fr_CA')">Francais</ion-radio>
</ion-list>
Here is part of my app.js that sets the $rootScope value for the language:
.run(function($rootScope) {
$rootScope.language = 'en_US';
})
Along with that, I was using this in my controllers.js:
.controller('PreferencesCtrl', function($scope, $rootScope, $state, $ionicPlatform, $ionicLoading) {
alert('PreferencesCtrl');
$scope.lang = $rootScope.language;
//Update Language
$scope.updateLanguage = function(lang) {
$scope.lang = lang;
$rootScope.language = lang;
};
});
The issue I am running into is that I can successfully change the language once, but if I attempt to change it again, the whole page goes white, and nothing is being rendered. It also appears that by setting the $rootScope, it also changes the view that is being rendered, as the back button goes away on the current screen.
My guess is that I am not setting the $rootScope.language in the updateLanguage function as the information is not being logged to the console on the second attempt to change the language.
If I change the updateLangauge to this:
//Update Language
$scope.updateLanguage = function(lang) {
$scope.language = lang;
};
Then I don't experience any of the white screen issues, however, the language is only changed for that particular view - not across the entire app.
Any thoughts on where I may be approaching this incorrectly?
UPDATE: It appears that after the $rootScope is being set again in the updateLangauge function that my PreferencesCtrl is then not being executed. I added an alert to the controller to see if it was firing after the $rootScope was set for the second time (when the screen goes white) and the alert never fires. So, it's like the controller is gone after you update the $rootScope.
What i think is going on here, is you're not updating your ng-model.
Whenever you click a language, you update your ng-model, and also pass the language to a method to update the $rootScope.language.
Change your method to update both:
$scope.updateLanguage = function(lang) {
$rootScope.language = lang;
$scope.lang = lang;
};
This should update both the UI ng-model and the rootscope value, making it available everywhere.
The only reason i can think of why your page goes white is because you have some kind of error in the debug console. check that. And you also have a typo in your App.js: it says $rootScope.langauge (should be language).
Furthermore, it might also be interesting to just keep a list of languages in your controller, rather than hardcoding them in the html.
So you could iterate over that, and use those values to update the rootscope.
Related
While transitioning an existing angular site, I encountered an annoying problem. The initial symptom was that a certain controller was not running it's initialize function immediately following the login. I logged and I tracked, and eventually I realized it was a design flaw of the page. Essentially, index.html contains a <header>, <ng-view>, and <footer>. There are a couple of ng-if attributes that live in the header that I want to evaluate after the login, but since the view is the only thing that is reloaded, it was not reinitializing the header controller, and thus not updating the ng-if values.
Then I was reminded of ngInclude, which seems like the perfect solution, until I got it hooked up and realize that doesn't work either. It loads the template the first time, and doesn't reinitialize when the view changes. So then I got the bright idea of passing the HeaderController to another controller or service, and controlling this one stubborn boolean value through a proxy of sorts. That also didn't work. Then I tried putting a function and a boolean into another service, and mirroring that property in the header controller, but thus far I have not gotten this working.
I have done plenty of research about multiple views in the index, and so far I hear a lot about this ui-router, but I'm still not convinced that is the way I want to go. It does not seem to be a simple solution. I have not tried putting the ng-include into the templates yet either, because then I feel like that is going back in time to when we had to update 100 pages every time we changed the menu.
I lost a whole day to this. If anyone could tell me how to trigger the evaluation of this one property in my header controller which I would like to live outside the other templates, please let me know!
Ok so you need to know in your HeaderController when the view has reloaded. There's a number of ways of doing this but the easier and maybe the more correct in this particular case is with an event.
So when you are refreshing the view you just do this, let's say you need the new value of ob1 and ob2 variables.
// ViewController
$rootScope.$emit('viewRefresh', {ob1: 'newvalue1', ob2: 'newvalue2'});
And in your HeaderController you need to listen for that event, and set on your $scope the new values for those attrs (if you're not using controller as syntax).
// HeaderController
$rootScope.$on('viewRefresh', function onRefresh(event, data) {
$scope.ob1 = data.ob1;
$scope.ob2 = data.ob2;
})
Another Solution
Sharing a Promise through a Service (using $q)
function HeaderService($q) {
var defer = $q.defer();
return {
getPromise: function() {return defer.promise},
notify: function(data) {defer.notify(data)}
}
}
function HeaderController(HeaderService) {
var vm = this;
HeaderService.getPromise().then(function(data) {
vm.ob1 = data.ob1;
vm.ob2 = data.ob2;
})
}
function ViewController(HeaderService) {
var data = {ob1: 'newvalue1', ob2: 'newvalue2'};
HeaderService.notify(data)
}
I am trying to call an API end point once a user clicks a button holding a myNavigator.pushPage() request. However,I can not get the $scope data generated from the $http.get request to be passed to the new page.
If I test using console.log('test'); inside the .success of the $http.get request I successfully get the log info in the console but any data held in $scope.var = 'something'; does not gets passed to the page! Really confused!
$scope.historyDetails = function(id){
var options = {
animation: 'slide',
onTransitionEnd: function() {
$http.get('http://xxx-env.us-east-1.elasticbeanstalk.com/apiget/testresult/testId/'+id).success(function(data) {
$scope.testscore = 'something'; // this is not getting passed to page!
console.log('bahh'); // But I see this in console
});
}
};
myNavigator.pushPage("activity.html", options);
}
Page:
<ons-page ng-controller="HistoryController">
...
<span style="font-size:1.2em">{{testscore}} </span><span style="font-size:0.5em;color:#555"></span>
...
</ons-page>
Yes, that's so because both pages has different controllers, resulting in different scopes. One can not access variables from one scope to another.
Hence one solution in this case can be using rootScope service.
Root Scope is parent scope for all scopes in your angular application.
Hence you can access variable of root scopes from any other scope, provided that you are injecting $rootScope service in that controller.
to know more about rootScope check this link.
Good luck.
Update 1:
check these articles
http://www.dotnet-tricks.com/Tutorial/angularjs/UVDE100914-Understanding-AngularJS-$rootScope-and-$scope.html
https://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
As Yogesh said the reason you're not getting your values is because if you look at $scope.testscore and try to find where is the $scope defined you will see that it's an argument for the controller function (thus it's only for that controller).
However we can see that the controller is attached to the page and you are pushing another page.
So in that case you have several options:
Use the $rootScope service as Yogesh suggested (in that case accept his answer).
Create your own service/factory/etc doing something similar to $rootScope.
(function(){
var historyData = {};
myApp.factory('historyData', function() {
return historyData;
});
})();
Technically you could probably make it more meaningful, but maybe these things are better described in some angular guides.
If you have multiple components sharing the same data then maybe you could just define your controller on a level higher - for example the ons-navigator - that way it will include all the pages. That would be ok only if your app is really small though - it's not recommended for large apps.
If this data is required only in activity.html you could just get it in that page's controller. For example:
myApp.controller('activityController', function($scope, $http) {
$http.get(...).success(function(data) {
$scope.data = data;
});
}
But I guess you would still need to get some id. Anyway it's probably better if you do the request here, now you just need the id, not the data.
You could actually cheat it with the var directive. If you give the activity page <ons-page var="myActivityPage"> then you will be able to access it through the myActivityPage variable.
And the thing you've been searching for - when you do
myNavigator.pushPage("activity.html", options);
actually the options is saved inside the ons-page of activity.html.
So you can do
myNavigator.pushPage("activity.html", {data: {id: 33}, animation: 'slide'});
And in the other controller your id will be myActivityPage.options.data.id.
If you still insist on passing all the data instead of an id - here's a simple example. In the newer versions of the 2.0 beta (I think since beta 6 or 7) all methods pushPage, popPage etc return a promise - which resolve to the ons-page, making things easier.
$scope.historyDetails = function(id){
myNavigator.pushPage("activity.html", {animation: 'slide'}).then(function(page) {
$http.get('...' + id).success(function(data) {
page.options.data = data;
});
});
});
Side note: You may want to close the question which you posted 5 days ago, as it's a duplicate of this one (I must've missed it at that time).
So I am relatively new to AngularJs and I am trying to figure out the best way for a tabController to remember what tab was previously clicked when switching to a new controller. So the situation would be I have 3 tabs. I click on tab 3 and then I click on something inside of it bringing me to a new controller and HTML template... What is the best way if I hit a "back" Button I created in that controller to remember exactly the state of it being the 3 tab.
I tried using $rootScope and then in each controller setting the tab number and setting the tabcontroller = $rootScope... but that was chaotic and too repetitive, and its not the right way.
This is not about $windoe.back(), this refers to coming up with a way that no matter where the navigation is the tab number is retained.
You can use a factory for this. In angular, a factory is a singleton, meaning only one instance of it exists for the whole project. Thus, by making something with it in one controller (and saving what you've did), you can access your changes in another controller.
angular.module('awesomeApp')
.factory('tabHistoryFactory', function () {
var tabHistory = {
setPrevTab: function(tab) { tabHistory.prevTab = tab; },
getPrevTab: function() { return tabHistory.prevTab; }
};
return tabHistory;
});
Then, in your first controller you'll have to inject this factory and before changing to another tab, just save the tab you're on using tabHistoryFactory.setPrevTab(tab). Then, in your second controller, you can access your previous tab by using tabHistoryFactory.getPrevTab(). Similarly, you can customize the behavior of your tab history by implementing other functions alongside those two.
Good luck!
I'm able to find form data is changed or not using $dirty.
ex: I changed text box or drop down and then $dirty become true. If I reverted to old data still it is true. I need to know if my changes are reverted or not. Do we have any property in Angularjs? If property is true I want to enable save button otherwise it should be disable.
https://docs.angularjs.org/api/ng/type/form.FormController
I need to implement around 10 pages and each page has 10 text boxes and a couple of drop downs. So I don't want track each control manually in my pages.
You can try using this module: https://github.com/betsol/angular-input-modified
From the README file:
This Angular.js module adds additional properties and methods to the
ngModel and ngForm controllers, as well as CSS classes to the
underlying form elements to provide end-user with facilities to detect
and indicate changes in form data.
This extra functionality allows you to provide better usability with
forms. For example, you can add decorations to the form elements that
are actually changed. That way, user will see what values has changed
since last edit.
Also, you can reset an entire form or just a single field to it's
initial state (cancel all user edits) with just a single call to the
reset() method or lock new values (preserve new state) just by calling
overloaded $setPristine() method.
DISCLAIMER: I haven't tried it myself and I notice the author overwrites the ngModel directive instead of adding a decorator, which could be dangerous...but at the very least, you can look at the source and get an idea of how to write your own service or directive with similar functionality.
Even though it does not follow the usage of $dirty, but an implementation similar to this might be helpful for you in the case of a Save button on update.
Inside your html:
<form name="testForm" ng-controller="ExampleController" ng-submit=" save()">
<input ng-model="val" ng-change="change()"/>
<button ng-disabled="disableSave">Save</button>
</form>
Inside your controller:
.controller('ExampleController', ['$scope', function($scope) {
$scope.disableSave = true; // Keep save button disabled initially
$scope.val = 'Initial'; // Initial value of the variable
var copyVal = $scope.val; // Copy Initial value into a temp variable
$scope.change = function() {
$scope.disableSave = $scope.val === copyVal;
};
$scope.save = function() {
// Save the updated value (inside $scope.val)
console.log($scope.val);
// Re-disable the input box (on successful updation)
copyVal = $scope.val;
$scope.disableSave = true;
};
}]);
Here is a working plunkr for the same.
I am building an angular app and have encountered several instances where I would like to redirect the user to a certain page. But that information about whether a user should be redirected or not is typically received after a server side request.
In the time it takes to do a server request, the original page starts rendering and hence creates a bad UX.
A case in point would be redirecting to login page when user is unauthorized.
Question 1 I know how to handle these cases individually. But was wondering if there is some standard pattern I can follow to solve this issue.
Question 2 Is there a standard pattern to control when to start rendering the page when information is being fetched from server. for instance
my view has
{{user.name}}
and controller has following code:
userService.load_user().then(function(user) {
$scope.user = user;
});
I don't want anything displayed till user is loaded, maybe just a loading sign. Currently i can do it as such:
//controller
userService.load_user().then(function(user) {
$scope.user = user;
$scope.loaded = true;
});
and
<!-- view -->
<div ng-show="!loaded">
<img src="loading.gif"/>
</div>
<div ng-show="loaded">
real code here.
</div>
This gets complicated when I want to wait on more than one requests.
Use the resolve property of the routes. Am assuming you're using ngRoutes or ui-router. Both include the resolve property on their routes.
To add a spinner or something similar while you wait for them to resolve, listen for the view change events within the shell view controller (assuming you have one) and add/remove the spinner accordingly.
By shell controller I just mean the highest level view within which the others are nested. It may or may not have a controller, but usually does. You might have a showSpinner property on that scope:
myApp.controller('mainCtrl', function($scope){
$scope.showSpinner = false;
$scope.$on('$stateChangeStart', function(){
$scope.showSpinner = true;
});
$scope.$on('$stateChangeSuccess', function(){
$scope.showSpinner = false;
});
});
Then you could just use ng-show='showSpinner' or ng-show='!showSpinner' on the spinner html element and the view element respectively.
This is the basic idea. You will probably end up with something more elaborate.
Here is a Plunker. There is a little more going on there (abstract state etc) than you requested, but you will quickly see how the resolve property is used and how the state is diverted.