I'm trying to get some JSON data to pass between controllers. I get some JSON with $http, set my callback with a $q defer and assign the result to my $rootScope.productList.
Everything is working, but when I add a $watch on $rootScope.productList, it returns me undefined. Do you have any solution about this?
My $watch inside a controller:
$rootScope.watch('productList', function(newVal, oldVal) {
$scope.filters = $rootScope.productList;
console.log($rootScope.productList);
});
and inside another controller, I get my data. I replaced the $http by a timeout to reproduce the behaviour.
$timeout(function() {
$rootScope.productList = $scope.productList;
console.log($rootScope.productList);
}, 500);
http://plnkr.co/edit/7wWxXgq5BARYgm0tYVgf
I tried with a $watch, but I'd take any workaround.
The problem here is that watch() is not a recognized function - what you probably want is $watch().
In other words, you were just missing the $ next to 'watch' in your original code.
Try this:
$rootScope.$watch('productList', function(newVal, oldVal) {
$scope.filters = $rootScope.productList;
console.log($rootScope.productList);
})
If you need to be able to transfer data between controllers, you should be implementing a service in angular. The $rootScope wasn't created for you to set your data.
Related
I Have the following code in which I'm trying to get a JSON file and attach its contents to my $scope.
The first console.log returns the result that I need, but the second returns undefined.
What would be the correct way to write this so that the data is stored?
'use strict';
var myApp = angular.module('myApp.view1', ['ngRoute']);
myApp.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/view1', {
templateUrl: 'view1/view1.html',
controller: 'View1Ctrl'
});
}]);
myApp.controller('View1Ctrl', [
'$scope',
'$http',
function($scope, $http) {
$http.get('view1/info.json')
.then(function(res){
$scope.data = res.data.info
console.log($scope.data)
});
console.log($scope.data)
}]);
AngularJS's $http.get()'s then() callback is called asynchronously.
That means that in the callback, it will work, but not in the second one.
The reason for this is with something called Promises. I definitely suggets looking into them. Here is kind of what happens in order:
$http.get('view1/info.json')
That tells javascript I want to get the JSON data. But it does not actually get it yet. Then, it does that console log.
console.log($scope.data)
Well $scope.data is undefined. Nothing has set it yet! Then, javascript actually gets the JSON data. When it gets the data, the function within the then is called. After this,
$scope.data = res.data.info
console.log($scope.data)
$scope.data is defined, which is why this console.log works. Read more about promises for more info!
Here is a good starting point: https://www.youtube.com/watch?v=wA0gZnBI8i4
Well, your data is stored. But not at the time your second console.log() gets executed. The "then" promise/callback will run after your second console.log(). If you need $scope.data in your templates, this should work. Angulars digest cycle will be run after the callback of $https.
I want to change the color of font in an input field depending on the value coming from firebase.
In jquery I use $(document).ready() but my code fires before the firebase data is loaded into the dom.
I have reverted to using using setTimeout() to give the dom enough time to load which is not really the way to do it.
There must be an event the fires after the data is attched to the DOM?
app.controller('myCtrl', function($scope, $stateParams, $firebaseObject) {
var ref = new Firebase('xxxxxxxxx');
$scope.Details = $firebaseObject(ref);
//what I really need is, "tell me when the firebase object is loaded to the DOM
//so I can do my stuff"
setTimeout(function(){
if($("#idInput").val() ==='foo'){
$("#idInput").css("color", "red");
}
}, 500);
});
Use $loaded(), if you need to use the data from the $firebaseObject.
$scope.Details = $firebaseObject(ref);
$scope.Details.then(function(data) {
// loaded data here
});
Otherwise though, the $firebaseObject informs the $digest loops when the data has loaded.
Another tactic is to use resolve in the router to load the data into the controller. This is much cleaner, because you don't need to unwrap the promise.
.config(function($stateProvider) {
$stateProvider.state('home', {
controller: 'myCtrl',
template: 'myTemplate.html',
resolve: {
details: function($firebaseObject, $stateParams) {
var ref = new Firebase('xxxxxxxxx');
var childRef = ref.child($stateParams.id);
return $firebaseObject(childRef).$loaded();
}
}
});
})
Then in your controller the data will be resolved:
.controller('myCtrl', function($scope, details) {
$scope.Details = details; // totally available to use
})
Read the docs for more information on resolving data with routing and AngularFire.
Don't seems that you need to use setTimeout here, you could use ng-class directive to apply CSS on change of input value.
HTML
<input id="idInput" ng-model="idInput" ng-class="{ 'red': idInput == 'foo' }"/>
CSS
.red {
color: red;
}
If you are really interested to do something after data gets loaded through $firebaseObject(ref) then you should use $loaded method over $firebaseObject which will get called once data loaded. Will prefer to use $timeout instead of setTimeout to make sync scope variable binding with html by running digest cycle. Additionally don't do DOM manipulation from controller its considered as antipattern.
Code
$scope.Details = $firebaseObject(ref);
$scope.Details.$loaded()
.then(function(data) {
//do some code
$timeout(function(){
//you shouldn't use DOM manipulation from angular controller.
//if($("#idInput").val() ==='foo'){
//$("#idInput").css("color", "red");
}
}, 500);
})
I am using a jsonn object to load in data between objects. currently using a factory to return the object and bind it between the controllers. Right now I am making a copy like so :
var LevelsHere = $http.get("my.json")
.success(function(data){
var dataCopy = angular.copy(data);
return dataCopy;
});
return {
all: function() {
return LevelsHere;
}
};
This works fine, but I have a button that I want to call this function and refresh it so It gets a clean copy from the my.json (so any changes are reverted).
Just to clarify, in each controller I call it in into a scope within the controller like so
UserService.all().then(function(data){
$scope.storeHere= data.data;
});
I am thinking maybe something like a $rootscope might be the way to go because I am sharing between controllers. So - have the root scope (which is a copy of the json) be shared between controllers. Then when I press my refresh button it would refresh that $rootscope with a fresh copy of my.json so changes would revert back.
Maybe I could use the method I am trying now? I tried the having the refresh button call the $get again but it wasnt binded to both places so it was only refreshing in one controller.
To quickly review - I have json I'm bringing in and using in 2 controllers with a factory calling it. I want to be able to refresh that so it refreshes in both places.
Here is my attempt at the refresh :
$scope.cancelProcedure = function() {
//refresh data
UserService.all().then(function(data){
$scope.levels = data.data;
};
The problem with this is it calls the current data, and doesn't refresh with a new call. I'm not sure how to make it refresh in both places. Thanks!!
To give you an answer I assume the following:
There's one resource you want to get my.json and update from time to time.
You want to access and update the data from two (or more) controllers
You don't want to pollute your $rootScope
In that case the ideal solution would be to store the method to get/update the data in the factory as well as the current data. In each controller, where you need that data, you simply inject the factory and assign it to the $scopeof that controller.
Here's one example:
angular.factory('dataFactory', ['$http', function ($http) {
var dataFactory={};
dataFactory.currentData = null;
dataFactory.update = function () {
return $http.get("my.json")
.success(function(data){
dataFactory.currentData = data;
return data;
});
};
return dataFactory;
}]);
angular.controller('firstCtrl', ['$scope', 'dataFactory', function ($scope, dataFactory) {
$scope.data = dataFactory;
}]);
angular.controller('secondCtrl', ['$scope', 'dataFactory', function ($scope, dataFactory) {
$scope.data = dataFactory;
}]);
In your HTML you can then use e.g. ng-bind="data.currentData" and ng-click="data.update()".
Further thoughts: If you don't want to put the factory in your controllers $scope you might even consider to further break down your logic and create one or two directives that are based on that factory. If that makes sense is not easy to tell with the given information, however.
I hate asking questions that there's already good info for but I'm missing something to keep my implementation from working.
My scenario is using a recursive function on a service to load my data in iterative chunks. All the data is captured by the scope but only the first set is displayed unless you navigate away and then back again. Clearly, I need to $watch my scope. I just can't figure out how to do so.
AccountService runs a method called getAccountsByPage which is passed an argument of 1. That function then calls itself with a value of 2, and so forth.
$routeProvider.when('/accounts/', {
...
controller: function ($scope, AccountService) {
var accounts = $scope.accounts = AccountService.getAccountsByPage(1);
$scope.$watch('accounts', function(newVal, oldVal) {
console.log(newVal, oldVal);
});
}
});
console outputs: undefined, undefined
[Object, Object, Object...] undefined
To be clear, getting the data isn't the problem. Updating the view is. Angular says not to use $watch on the controller but it seems that everyone does so...
From the code you've posted, $scope.accounts only ever gets set once when the controller first gets instantiated. It doesn't matter what AccountService.getAccountsByPage is doing underneath. Whatever it returns will be put into $scope.accounts, and trigger the $watch that one time. It won't be triggered again until $scope.accounts changes, which I don't see happening anywhere from the code posted.
I have my angular controller setup like most of the examples shown in the docs such that it is a global function. I assume that the controller class is being called when the angular engine sees the controller tag in the html.
My issue is that i want to pass in a parameter to my controller and i don't know how to do that because I'm not initializing it. I see some answers suggesting the use of ng-init. But my parameter is not a trivial string - it is a complex object that is being loaded by another (non-angular) part of my js. It is also not available right on load but takes a while to come along.
So i need a way to pass this object, when it finally finishes loading, into the controller (or scope) so that the controller can interact with it.
Is this possible?
You can use a service or a factory for this, combined with promises:
You can setup a factory that returns a promise, and create a global function (accessible from 3rd-party JS) to resolve the promise.
Note the $rootScope.$apply() call. Angular won't call the then function of a promise until an $apply cycle. See the $q docs.
app.factory('fromoutside', function($window, $q, $rootScope) {
var deferred = $q.defer();
$window.injectIntoAngularWorld = function(obj) {
deferred.resolve(obj);
$rootScope.$apply();
};
return deferred.promise;
});
And then in your controller, you can ask for the fromoutside service and bind to the data when it arrives:
app.controller('main', function($scope, fromoutside) {
fromoutside.then(function(obj) {
$scope.data = obj;
});
});
And then somewhere outside of Angular:
setTimeout(function() {
window.injectIntoAngularWorld({
A: 1,
B: 2,
C: 3
});
}, 2000);
Here's a fiddle of this.
Personally, I feel this is a little bit cleaner than reaching into an Angular controller via the DOM.
EDIT: Another approach
Mark Rajcok asked in a comment if this could be modified to allow getting data more than once.
Now, getting data more than once could mean incremental updates, or changing the object itself, or other things. But the main things that need to happen are getting the data into the Angular world and then getting the right angular scopes to run their $digests.
In this fiddle, I've shown one way, when you might just be getting updates to an Array from outside of angular.
It uses a similar trick as the promise example above.
Here's the main factory:
app.factory('data', function($window) {
var items = [];
var scopes = [];
$window.addItem = function(item) {
items.push(item);
angular.forEach(scopes, function(scope) {
scope.$digest();
});
};
return {
items: items,
register: function(scope) { scopes.push(scope); }
};
Like the previous example, we attach a function to the $window service (exposing it globally). The new bit is exposing a register function, which controllers that want updates to data should use to register themselves.
When the external JS calls into angular, we just loop over all the registered scopes and run a digest on each to make sure they're updated.
In your non-angular JavaScript, you can get access to the scope associated with a DOM element as follows:
angular.element(someDomElement).scope().someControllerFunction(delayedData);
I assume you can find someDomElement with a jQuery selector or something.