AngularJs - keeps getting old value of scope even if it is updated - angularjs

In my application, I'm retrieving some fields from the database and setting the values in local storage when a user logs in.
and then retrieving from local storage to display it to user:
if (localStorage.getItem('a') != undefined) {
$rootScope.a = localStorage.getItem('a');
}
So this is working fine. But the problem is when the value gets updated in the database and user logs in after logging out, then even if the local storage has correct value (i.e., recently updated value), the first time it will display the old value of the scope variable which just got updated.
I tried $apply() and also $digest() as suggested in different posts here :
$timeout( function () {
$scope.$apply( function () {
$rootScope.a = localStorage.getItem('a');
});
});
But it didn't work out. It always displays the old value of scope.
It will only display the new value after reloading the page once.
P.S. - The web page in my application won't be reloaded in any module, even when logging in and out.

You can try watching for the scope variable like this:
$rootScope.$watch('a', function (newVal, oldVal){
if newVal != oldVal
$rootScope.a = newVal;
}
Something else to try is to change 'a' from string to object as I think that angular watches for values using object reference.
here's some useful reference for $watch
http://www.learn-angular.org/#!/lessons/watch
https://www.bennadel.com/blog/2852-understanding-how-to-use-scope-watch-with-controller-as-in-angularjs.htm
Hope it helps in any way
EDIT
ok I tested it. You don't need watch neither $apply if you refresh the scope when data refreshing.
Here's what I've done:
(function() {
angular.module('myapp', []).controller('myctrl', [
'$scope', function($scope) {
var data, getDataFromLocalStorage;
console.log("scope is ", $scope);
getDataFromLocalStorage = function() {
return JSON.parse(localStorage.getItem('data'));
};
data = [
{
id: 1,
text: "test1"
}, {
id: 2,
text: "test2"
}, {
id: 3,
text: "test3"
}
];
localStorage.setItem('data', JSON.stringify(data));
$scope.myData = getDataFromLocalStorage();
return $scope.changeData = function() {
var dataNew;
dataNew = [
{
id: 4,
text: 'text4'
}, {
id: 5,
text: 'text5'
}, {
id: 6,
text: 'text6'
}
];
localStorage.setItem('data', JSON.stringify(dataNew));
return $scope.myData = getDataFromLocalStorage();
};
}
]);
}).call(this);
https://codepen.io/NickHG/pen/rzvGGx?editors=1010

Related

How to avoid adding to many watchers on same object

Currently I'm getting a pretty messy response from the server, and If store everything in one Object and then bind that as model on formly-form I have huge lag on when typing something to text input.It's caused because there are 3 arrays with over 850 items and as I understand Angular Formly observe those things too.
So I decided to split data in 2 objects
One that holds everything and It's not binded as model to formly-form
Other that holds initial data which are pretty light, and those things are binded to the formly-form model.
Here is the skeleton:
$scope.allData = {}
$scope.formlyData = {}
$scope.initData = function() {
myService.get()
.then(function(response) {
$scope.allData = response.data
$scope.formlyData['important'] = response.data.important
})
}
Remember huge arrays ? Well basically they should be stored in select inputs, and with formly I tried to make It like this:
var vm = $scope
$scope.formFields = [{
fieldGroup: [
{
'key': 'actors',
'type': 'select',
'className': 'col-sm-6',
'templateOptions': {
options: []
},
controller: function($scope) {
$scope.to.options = vm.allData.actors
}
}
]
}]
And this doesn't work because allData is empty when It's executed and It's populated later because of $http call which is async.
Okay I tried to use watcher:
var vm = $scope;
$scope.formFields = [
fieldGroup: [
{
'key': 'actors',
'type': 'select',
'className': 'col-sm-6',
'templateOptions': {
options: []
},
controller: function($scope) {
vm.$watch('allData', function(newVal) {
$scope.to.options = newVal.actors
})
}
}
]
]
But something is not good and loading takes a quite long (response is fetched for 200ms).Is It because I have about 10 fields with same behaviour and then there is a many watchers on same object ?
Any better idea to handle this, or sort of improvment ? Thanks.

Passing Data from Service to Template via Controller and $stateParams

I have a service with the following code:
.service('ChatService', function() {
return { //Gets Data from controller
sendData: function(data) {
this.chatData = data;
console.log('this.chatData: '+this.chatData);
},
chats: this.chatData,
getChats: function() {
return this.chatData;
},
getChat: function(chatId) {
for(i=0; i<this.chats.length; i++) {
if (this.chats[i].id == chatId) {
return this.chats[i];
}
}
}
}
})
The Important thing here is that sendData retrieves info
[{id: 1, message: "Chat Message 1"},{id: 2, message: "Message 2"}]
from the controller. The getChats in Services is then called $scope.chats = ChatService.getChats(); by the same controller to show in the template.
When that item is clicked a new page with more info is to show hence the getChat and getChats function in the service.
The code in the controller for the page to load more details is
$scope.chatId = $stateParams.chatId;
$scope.chat = ChatService.getChat($scope.chatId);
However I'm getting error cannot read property length of undefined. Now if I change the chats: this.ChatData in the services to
chats: [{id: 1, message: "Chat Message 1"},{id: 2, message: "Message 2"}]
It works like a charm but I need it to display in real time what is in the controller because I'm downloading and receiving info from the server that will be updated in real time for a chat app.
Use angular.copy to update the reference:
app.service('ChatService', function() {
//Gets Data from controller
this.sendData = function(data) {
//this.chatData = data;
//console.log('this.chatData: '+this.chatData);
//Use angular.copy
angular.copy(data, this.chats);
};
this.chats = [];
this.getChats = function() {
return this.chats;
};
this.getChat = function(chatId) {
for(i=0; i<this.chats.length; i++) {
if (this.chats[i].id == chatId) {
return this.chats[i];
};
};
};
});
By using the angular.copy to update the array reference, controllers that have previously fetched the reference will get updated.
For more information, see AngularJS angular.copy API Reference.

AngularJS: Trying to make filter work with delayed data

I have this html:
<div ng-app='myApp'>
<div ng-controller='testCtrl'>
<h3>All possesions</h3>
{{possessions}}
<h3>Green cars</h3>
{{greenishCars}}
</div>
</div>
And this script:
angular.module('myApp', [])
.factory('getPossessionsService', ['$timeout',
function($timeout) {
var possessions = {};
$timeout(function() {
possessions.cars = [{
model: "Mazda 6",
color: "lime green"
}, {
model: "Audi A3",
color: "red"
}, {
model: "Audi TT",
color: "green"
}, {
model: "Volkswagen Lupo",
color: "forest green"
}];
possessions.jewelry = [{
type: "ring",
metal: "gold"
}, {
type: "earring",
metal: "silver"
}];
}, 1000);
return possessions;
}
])
.controller('testCtrl', ['$scope', 'filterFilter', 'getPossessionsService',
function($scope, filterFilter, getPossessionsService) {
$scope.possessions = getPossessionsService;
$scope.greenishCars = filterFilter($scope.possessions.cars, carColorIsGreenShade);
function carColorIsGreenShade(car) {
return ['green', 'forest green', 'lime green'].indexOf(car.color) != -1;
}
}
]);
I am trying to get $scope.greenishCars to update correctly when the data is available, but it is not. I understand that this has because $scope.possessions.cars is an array and therefore not a reference to the data, so it is not updated. But how should I alter my script so that greenishCars get updated when the data arrives? I am guessing I should use $scope.possessions "directly", but I do not quite see how I should rewrite this nicely....
See this plunk.
Edit: Thoughts on which answer to choose
As in comments in the answers to Oliver and MajoB, I ended up with using both filter and watch. In my special case the request for the data was made in another place (not in the controller of my page in question), so it was not so easy to act on the resolving of the promise (with promise.then as suggested by Oliver), I therefore used a watch. But there is a couple of things to be aware of with watches. If the variable you want to watch is not on the scope, then you must provide the variable by returning it from a function. And if you want to watch for a change in an existing property (say somebody repaints my existing Mazda 6 in a different color), then none of the watch-answers works, as you need to add "true" when calling $watch. When you add 'true' as the second paramenter to $watch, then it watches for changes in the actual values of the variable, and it also does this check for values deeply in an object/array (without it just checks references, see this blog). I ended up with this controller/$watch for my real-life use-case (which is a little bit different that in my example above, and is based on Olivers filter-plunk), and it looks like this (changed to fit the plunk):
.controller('testCtrl', ['$scope', 'greenFilter', 'getPossessionsService',
function($scope, greenFilter, getPossessionsService) {
var none-scope-possessions = getPossessionsService; // I do not want to expose all properties on the scope....
$scope.greenishCars = [];
$scope.$watch(function() {return none-scope-possessions.cars}, function(newValue){
if (newValue) {
$scope.greenishCars = greenFilter(newValue);
}
},true);
}
]);
You can watch the data changes:
$scope.$watch('possessions.cars', function(){
$scope.greenishCars = filterFilter($scope.possessions.cars, carColorIsGreenShade);
});
I would recommend just building your own custom filter like so:
.filter('green', function() {
return function(possessions) {
if (!possessions) return null;
var filtered = [];
angular.forEach(possessions, function(possesion){
if (['green', 'forest green', 'lime green'].indexOf(possesion.color) != -1) {
filtered.push(possesion);
}
});
return filtered;
};
})
Then pass your collection through that filter like so:
<div ng-controller='testCtrl'>
<h3>All possesions</h3>
{{possessions}}
<h3>Green cars</h3>
{{possessions.cars|green}}
</div>
Everything else will happen automatically. See the updated plunkr.
You can watch possesions collection for changes:
$scope.$watchCollection('possessions', function (newValue) {
if (newValue)
{
$scope.greenishCars = filterFilter(newValue.cars, carColorIsGreenShade);
}
});
http://plnkr.co/edit/Sl6dW0pqKr593OXrgDb9?p=preview
First thing is , var possessions = {}; you have declared an object , cars is an field in this object , which has array of cars . so either return possessions.cars or in the markup call it via possessions.cars[0].. if you want to call only one value ..or if you want to display all the values the use ng-repeat=" car in possessions.cars" .

Passing data among controllers

In my AngularJS I have two controllers. One is responsible for handling data about people. The other controller is used to handle information about how the person data is displayed:
.controller('PersonCtrl', ['$rootScope', '$scope', function ($rootscope, $scope)
{
$scope.Persons = ['John', 'Mike'];
} ]);
.controller('WidgetCtrl', ['$scope',
function($scope) {
$scope.myWidgets = {
'1': {
id: '1',
name: 'Home',
widgets: [{
col: 0,
row: 0,
sizeY: 1,
sizeX: 1,
name: "John"
}, {
col: 2,
row: 1,
sizeY: 1,
sizeX: 1,
name: "Mike"
}]
}
};
Notice in the PersonCtrl, I have two names: John and Mike. In the WidgetCtrl, I want to attach those names to the name field (in the example below, I just typed it in so that it's obvious where the data goes). How do I do this?
just to elaborate the comment... you can use services to share data between controllers. check out this video from egghead.io
egghead.io is an excellent resource for starting with angualrjs.
this is how that service should look like
app.service('productService', function() {
var personList = [];
var add = function(newObj) {
personList.push(newObj);
}
var get = function(){
return personList;
}
return {
add: add,
get: get
};
});
EDIT
to address your comment on how to get names in widget
controller('WidgetCtrl', ['$scope', 'PersonService'
function($scope, personService ) {
/* init the myWidgets*/
var count = 0;
angular.forEach(personService.personList, function(item){
$scope.myWidgets['1'].widgets[count].name = item.name;
count = count +1;
});
}]);
A controller is for mediation between a model (in its $scope) and the template. Sometimes nested controllers is the right way to do things, i.e. your WidgetCtrl assumes that it is inside a PersonCtrl. In this case it can access the parent controller scope members through the scope prototypical inheritance.
A more sophisticated way of doing it though is to use a service that deals with fetching data from a back end and presenting it to various controllers that may need it.
There are a few ways to share data between controllers - events, services and controller inheritance through the use of parent controllers.
There is no right or wrong way, but typically if you are fetching data from a server then a service is the way forward. If you wanna utilize the observer pattern aka pub/sub then use events. We use multiple solutions in our spa.
This however looks like the job for a service - especially since you say one is responsible for showing data - aka the truth - aka a controller (widget). The other is responsible for handling data about a person - use a service which returns the array you need.
Service example:
.factory('persons', function() {
var persons = ['john', 'mike'];
function getPersons() {
return persons;
}
return {
getPersons: getPersons
}
});
Some ex HTML for the update:
<button ng-click="updateWidget(getPeople())">Update Widget</button>
Your Controller:
.controller('WidgetCtrl', ['$scope', 'persons'
function($scope, persons) {
//Both not used only for example purposes...
var peeps = [];
peeps = $scope.getPeople();
//Now in yer HTM you can pass this function into the updateWidget fct
// ex <button ng-click="updateWidget(getPeople())">Update Widget</button>
$scope.getPeople = function() {
// Now this is not async but be away in the future if you req from server you need to use promises
return persons.getPersons();
}
$scope.updateWidget = function(people) {
var item = $scope.myWidgets[1].widgets;
angular.forEach(items, function(item, $index) {
item.name = people[$index];
});
}
$scope.myWidgets = {
'1': {
id: '1',
name: 'Home',
widgets: [{
col: 0,
row: 0,
sizeY: 1,
sizeX: 1,
name: "X"
}, {
col: 2,
row: 1,
sizeY: 1,
sizeX: 1,
name: "Y"
}]
}
};

WebSQL data into AngularJs DropDown

I have very simple question about getting data from WebSql
I have DropDown i.e
<select id="selectCatagoryFood" data-role="listview" data-native-menu="true"
ng-init="foodCatagory = foodCatagories.cast[0]"
ng-options="foodCatagory as foodCatagory.text for foodCatagory in foodCatagories.cast"
ng-model="foodCatagory"
ng-change="changeFoodCatagory()">
</select>
now i want to add data init from webSQL. I already get Data from webSql but i am confuse that how to add that data into DropDown
An example or hints maybe very helpful for me.
Update 1 :: Add Controller Code
myApp.controller('foodSelection',function($scope,foodCatagories){
$scope.foodCatagories = foodCatagories;
$scope.changeFoodCatagory = function(){
alert($scope.foodCatagory.value);
}
});
Update 2 webSQL and JayData
_context.onReady({
success: showData,
error: function (error){
console.log(error);
}
});
function showData(){
var option = '';
_context.FoodGroup.forEach(function(FG)
{
option += '<option value="'+FG.FoodGroupID+'">'+FG.Description+'</option>';
}).then(function(){
console.log(option);
});
}
Update 3
var myApp = angular.module('myApp',[]);
myApp.factory('foodCatagories',function(){
var foodCatagories = {};
foodCatagories.cast = [
{
value: "000",
text: "Select Any"
}
];
return foodCatagories;
});
Update 4
One thing that i didn't mention is that I am using JayData for getting data from webSQL to my App
I will try to explain how it works:
EDIT: Live demo
html
Here is your stripped down select.
<select ng-options="item as item.text for item in foodCategories"
ng-model="foodCategory"
ng-required="true"
ng-change="changeFoodCategory()">
</select>
The directive ng-options will fill automatically the option elements in your select. It will take the foodCategories variable from the $scope of your controller and foreach item in the collection, it will use the text property as the label shown (<option>{{item.text}}</option>') and it will select the whole objectitemas the value of the selectedoption. You could also refer to a property as the value like ({{item.text}}). Then yourng-modelwould be set to theid` value of the selected option.
The directive ng-model corresponds to the variable in the $scope of your controller that will hold the value of the selected option.
The directive ng-required allows you to check if a value has been selected. If you are using a form, you can check if the field is valid formName.ngModelName.$valid. See the docs for more details on form validation.
The directive ng-change allows you to execute a function whenever the selected option changes. You may want to pass the ng-model variable to this function as a parameter or call the variable through the $scope inside the controller.
If no default value is set, angular will add an empty option which will be removed when an option is selected.
You did use the ng-init directive to select the first option, but know that you could set the ng-model variable in your controller to the default value you would like or none.
js
Here I tried to simulate your database service by returning a promise in the case that you are doing an async request. I used the $q service to create a promise and $timeout to fake a call to the database.
myApp.factory('DbFoodCategories', function($q, $timeout) {
var foodCategories = [
{ id: 1, text: "Veggies", value: 100 },
{ id: 2, text: "Fruits", value: 50 },
{ id: 3, text: "Pasta", value: 200 },
{ id: 4, text: "Cereals", value: 250 },
{ id: 5, text: "Milk", value: 150 }
];
return {
get: function() {
var deferred = $q.defer();
// Your call to the database in place of the $timeout
$timeout(function() {
var chance = Math.random() > 0.25;
if (chance) {
// if the call is successfull, return data to controller
deferred.resolve(foodCategories);
}
else {
// if the call failed, return an error message
deferred.reject("Error");
}
}, 500);
/* // your code
_context.onReady({
success: function() {
deferred.resolve(_contect.FoodGroup);
},
error: function (error){
deferred.reject("Error");
}
});
*/
// return a promise that we will send a result soon back to the controller, but not now
return deferred.promise;
},
insert: function(item) {
/* ... */
},
update: function(item) {
/* ... */
},
remove: function(item) {
/* ... */
}
};
});
In your controller you set the variables that will be used in your view. So you can call your DbFoodCategories service to load the data into $scope.foodCategories, and set a default value in $scope.foodCategory that will be used to set the selected option.
myApp.controller('FoodSelection',function($scope, DbFoodCategories){
DbFoodCategories.get().then(
// the callback if the request was successfull
function (response) {
$scope.foodCategories = response; //response is the data we sent from the service
},
// the callback if an error occured
function (response) {
// response is the error message we set in the service
// do something like display the message
}
);
// $scope.foodCategory = defaultValue;
$scope.changeFoodCategory = function() {
alert($scope.foodCatagory.value);
}
});
I hope that this helped you understand more in detail what is happening!
See this example and how use $apply to update the data in scope.
in the new version we released a new module to support AngularJS. We've started to document how to use it, you can find the first blogpost here
With this you should be able to create your dropdown easily, no need to create the options manually. Something like this should do the trick:
myApp.controller('foodSelection',function($scope, $data) {
$scope.foodCatagories = [];
...
_context.onReady()
.then(function() {
$scope.foodCatagories = _context.FoodGroup.toLiveArray();
});
});
provided that FoodGroup has the right fields, of course

Resources