I have following code in angularjs .run()
so when the app is initial, I set 2 vars under rootScope.
Because these vars I decide to use in every routes (pages) and controllers.
async
app.run(function($rootScope, $timeout, firebaseAuth) {
$rootScope.$on('firebaseLoggedIn', function(event, authUser) {
$timeout(function() {
$rootScope._isLoggedIn = true;
$rootScope._authUser = authUser;
},0);
});
But the problem here, when I run it in my controllers, it always show undefined.
unless i put it in function block. So my guess is the controllers is run
before the .run() vars have value;
app.controller('foo', function($scope){
console.log($scope._authUser); //Always undefined
//unless I do following
//$scope.clickme = function() {console.log($scope._authUser)};
});
Any suggestion I am able to use the vars(async) from .run()?
If I call the following code in every controllers, it seems like I keep repeating myself.
$rootScope.$on('firebaseLoggedIn', function(event, authUser) {});
UPDATE
Here is where firebaseLoggedIn event from. I think I have to mention again. The callback is async.
app.service('firebaseAuth', ['$rootScope',
function($rootScope) {
//Define firebase DB url
this.firebaseRef = new Firebase("https://test.firebaseio.com");
this.onLoginStateChanges = new FirebaseAuthClient(this.firebaseRef, function(error, user) {
if (user) {
$rootScope.$emit("firebaseLoggedIn", user); //authenticated user
}
else if (error) {
$rootScope.$emit("firebaseLoginError", error); //authentication failed
}
else {
$rootScope.$emit("firebaseLogout"); //user not login
}
});
}]);
The run method will be ran when the injector is done loading all modules for your app. So your assumption about your controller being instantiated first is true.
What you're effectively doing with the $timeout(function() { ... },0) is forcing that code to run in the next digest loop. But I don't think this is what you're trying to accomplish anyway.
I'm not sure where 'firebaseLoggedIn' is being broadcasted from; but you may find this entire implementation would be more straight forward as an Angular Service.
app.service('FireBaseService', function($rootScope) {
// store these inside the service to be retrieved via methods below.
var isLoggedIn = false;
var authUser = {};
// let this be the only place you handle the LoggedIn event, setting user context.
$rootScope.$on('firebaseLoggedIn', function(event, authUser) {
isLoggedIn = true;
authUser = authUser;
});
// return some functions to expose the variables above
return {
getIsLoggedIn: function() {
return isLoggedIn;
},
getAuthUser: function() {
return authUser;
}
}
});
Then in your controller......
app.controller('foo', function($scope, $log, FireBaseService){
if (FireBaseService.getIsLoggedIn()){
$log.info(FireBaseService.getAuthUser().Name);
}
});
This enables you to have access to these variables anywhere you've injected the FireBaseService service.
Related
I have a angular service function that is being called multiple times.
In the index.html page I have the following line:
<li><i class="pull-right"></i><br/>{{appCtrl.service.getCurrentUser()}} </li>
In the application controller I set the variable
appCtrl.controller('AppController', function ($state, securityService, $log) {
$log.info('App controller');
var appCtrl = this;
appCtrl.service = securityService;
});
In my service I exposed the function
login.factory('securityService', function ($window, $log) {
var currentUser;
return {
setCurrentUser: function (user) {
currentUser = user;
$window.sessionStorage.setItem('User', JSON.stringify(currentUser));
},
getCurrentUser: function () {
$log.info('Calling current user');
if (!currentUser) {
var storedObject = $window.sessionStorage.getItem('User');
currentUser = JSON.parse(storedObject);
}
return currentUser;
}
}
});
The following line in the getCurrentUser function gets called multiple times when the application starts up or page refresh is being done.
$log.info('Calling current user');
The controller is being called only once, I monitor it by looking at $log.info('App controller');
Is it being called as part of the dirty checking process or am I doing something wrong?
Angular calls your function on every digest cycle, you can set breakpoint inside the function and check it. If you are on 1.3 version, then please take a look at One Time Binding feature. If not then call the service inside the controller and bind view to some scope variable:
$scope.currentUser = securityService.getCurrentUser();
And inside view bind to scope variable:
{{currentUser}}
Try this, this is correct factory declaration. Because internally AngularJS calls yout factory like: securityService(injects); , each time you inject (use) your factory.
login.factory('securityService', function ($window, $log) {
var currentUser;
return {
setCurrentUser: function (user) {
currentUser = user;
$window.sessionStorage.setItem('User', JSON.stringify(currentUser));
},
getCurrentUser: function () {
$log.info('Calling current user');
if (!currentUser) {
var storedObject = $window.sessionStorage.getItem('User');
currentUser = JSON.parse(storedObject);
}
return currentUser;
}
};
});
I've been struggling with this for a few days now and can't seem to find a solution.
I have a simple listing in my view, fetched from MongoDB and I want it to refresh whenever I call the delete or update function.
Although it seems simple that I should be able to call a previously declared function within the same scope, it just doesn't work.
I tried setting the getDispositivos on a third service, but then the Injection gets all messed up. Declaring the function simply as var function () {...} but it doesn't work as well.
Any help is appreciated.
Here's my code:
var myApp = angular.module('appDispositivos', []);
/* My service */
myApp.service('dispositivosService',
['$http',
function($http) {
//...
this.getDispositivos = function(response) {
$http.get('http://localhost:3000/dispositivos').then(response);
}
//...
}
]
);
myApp.controller('dispositivoController',
['$scope', 'dispositivosService',
function($scope, dispositivosService) {
//This fetches data from Mongo...
$scope.getDispositivos = function () {
dispositivosService.getDispositivos(function(response) {
$scope.dispositivos = response.data;
});
};
//... and on page load it fills in the list
$scope.getDispositivos();
$scope.addDispositivo = function() {
dispositivosService.addDispositivo($scope.dispositivo);
$scope.getDispositivos(); //it should reload the view here...
$scope.dispositivo = '';
};
$scope.removeDispositivo = function (id) {
dispositivosService.removerDispositivo(id);
$scope.getDispositivos(); //... here
};
$scope.editDispositivo = function (id) {
dispositivosService.editDispositivo(id);
$scope.getDispositivos(); //... and here.
};
}
]
);
On service
this.getDispositivos = function(response) {
return $http.get('http://localhost:3000/dispositivos');
}
on controller
$scope.addDispositivo = function() {
dispositivosService.addDispositivo($scope.dispositivo).then(function(){
$scope.getDispositivos(); //it should reload the view here...
$scope.dispositivo = '';
});
};
None of the solutions worked. Later on I found that the GET request does execute, asynchronously however. This means that it loads the data into $scope before the POST request has finished, thus not including the just-included new data.
The solution is to synchronize the tasks (somewhat like in multithread programming), using the $q module, and to work with deferred objects and promises. So, on my service
.factory('dispositivosService',
['$http', '$q',
function($http, $q) {
return {
getDispositivos: function (id) {
getDef = $q.defer();
$http.get('http://myUrlAddress'+id)
.success(function(response){
getDef.resolve(response);
})
.error(function () {
getDef.reject('Failed GET request');
});
return getDef.promise;
}
}
}
}
])
On my controller:
$scope.addDispositivo = function() {
dispositivosService.addDispositivo($scope.dispositivo)
.then(function(){
dispositivosService.getDispositivos()
.then(function(dispositivos){
$scope.dispositivos = dispositivos;
$scope.dispositivo = '';
})
});
};
Being my 'response' object a $q.defer type object, then I can tell Angular that the response is asynchronous, and .then(---).then(---); logic completes the tasks, as the asynchronous requests finish.
I am trying to retrieve my search and filter data from sessionStorage when the page refreshes.
sessionStorage.restorestate returns undefined, does anyone know why?
app.run(function($rootScope) {
$rootScope.$on("$routeChangeStart", function(event, next, current) {
if (sessionStorage.restorestate == "true") {
$rootScope.$broadcast('restorestate'); //let everything know we need to restore state
sessionStorage.restorestate = false;
}
});
//let everthing know that we need to save state now.
window.onbeforeunload = function(event) {
$rootScope.$broadcast('savestate');
};
});
Plunkr: http://plnkr.co/edit/oX4zygwB0bDpIcmGFgYr?p=preview
When you refresh the page in an Angular app, it is like completely rebooting the application. So to restore from the session storage, just do it when the service factory executes.
app.factory('CustomerSearchService', ['$rootScope',
function($rootScope) {
...
function restoreState() {
service.state = angular.fromJson(sessionStorage.CustomerSearchService);
}
if (sessionStorage.CustomerSearchService) restoreState();
...
}
]);
The saving part was already correct.
app.factory('CustomerSearchService', ['$rootScope',
function($rootScope) {
...
function saveState() {
sessionStorage.CustomerSearchService = angular.toJson(service.state);
}
$rootScope.$on("savestate", saveState);
...
}
]);
app.run(function($rootScope) {
window.onbeforeunload = function(event) {
$rootScope.$broadcast('savestate');
};
});
DEMO
I'm using a service to make user data available to various controllers in my Angular app. I'm stuck trying to figure out how to use the $http service to update a variable local to the service (in my case "this.users"). I've tried with and without promises. The server is responding correctly.
I've read several excellent articles for how to use $http within a service to update the scope of a controller. The best being this one: http://sravi-kiran.blogspot.com/2013/03/MovingAjaxCallsToACustomServiceInAngularJS.html. That does not help me though because it negates the benefits of using a service. Mainly, modifying the scope in one controller does not modify throughout the rest of the app.
Here is what I have thus far.
app.service('UserService', ['$http', function($http) {
this.users = [];
this.load = function() {
var promise = $http.get('users.json')
.success(function(data){
// this.users is undefined here
console.log(this.users);
}
};
promise.then(function() {
// this.users is undefined here
console.log('this.users');
});
}]);
Any help is greatly appreciated. Thank you.
Try using
var users = [];
rather than
this.users = [];
and see what
console.log(users);
outputs in each of those cases.
Your service is oddly defined, but if you have a return in it you can access it from any controller:
app.service('UserService', ['$http', function($http) {
var users = [];
this.load = function() {
var promise = $http.get('users.json')
.success(function(data){
// this.users is undefined here
console.log(users);
users = data.data;
}
};
return {
getUsers: function(){
return users;
}
}
}]);
so in your controller, you can use:
var myUsers = UserService.getUsers();
UPDATE to use a service correctly here, your service should return a promise and the promise should be accessed in the controller: Here's an example from another answer I gave
// your service should return a promise
app.service('PickerService', [$http', function($http) {
return {
getFiles: function(){
return $http.get('files.json'); // this returns a promise, the promise is not executed here
}
}
}]);
then in your controller do this:
PickerService.getFiles().then(function(returnValues){ // the promise is executed here as the return values are here
$scope.myDirectiveData = returnValues.data;
});
this does not have scope anymore where you are trying to use it do this instead:
app.service('UserService', [$http', function($http) {
var users = [];
this.load = function() {
var promise = $http.get('users.json')
.success(function(data){
console.log(users);
}
};
promise.then(function() {
console.log(users);
});
}]);
all local variables to a service should just be vars if you assign them to this as a property than they will be included every time the service is injected into a controller which is bad practice.
I think what your asking for is a solution along the lines of defining your service like this:
angular.module('app')
.service('User', function($http, $q) {
var users = null;
var deferred = $q.defer()
return {
getUsers: function() {
if(users) {
deferred.resolve(users);
} else {
$http.get('users.json');
.success(function(result) {
deferred.resolve(result);
})
.error(function(error) {
deferred.reject(error);
});
}
return deferred.promise;
}
};
});
Then in one Each controller you would have to do this:
angular.module('app')
.controller('ACtrl', function($scope, User) {
User.getUsers().then(function(users) {
// Same object that's in BCtrl
$scope.users = users;
});
});
angular.module('app')
.controller('BCtrl', function($scope, User) {
User.getUsers().then(function(users) {
// Same object that's in ACtrl
$scope.users = users;
});
});
NOTE: Because the deferred.promise the same promise passed to all controllers, executing deferred.resolve(users) in the future will cause all then success callbacks in each of your controllers to be called essentially overwriting the old users list.
All operations on the list will be noticed in all controllers because the users array is a shared object at that point. This will only handle updates to the user list/each individual user on the client side of your application. If you want to persist changes to the server, you're going to have to add other $http methods to your service to handle CRUD operations on a user. This can generally be tricky and I highly advise that you check out ngResource, which takes care of basic RESTful operations
In my main describe I have the following:
beforeEach(inject(function(...) {
var mockCookieService = {
_cookies: {},
get: function(key) {
return this._cookies[key];
},
put: function(key, value) {
this._cookies[key] = value;
}
}
cookieService = mockCookieService;
mainCtrl = $controller('MainCtrl', {
...
$cookieStore: cookieService
}
}
Later on I want to test how a controller believes if the cookie already exists, so I nest the following describe:
describe('If the cookie already exists', function() {
beforeEach(function() {
cookieService.put('myUUID', 'TEST');
});
it('Should do not retrieve UUID from server', function() {
expect(userService.getNewUUID).not.toHaveBeenCalled();
});
});
However when I'm making the change to cookieService it's not persisting into the controller being created. Am I taking the wrong approach?
Thanks!
EDIT: Updated the testing code and this is how I'm using $cookieStore:
var app = angular.module('MyApp', ['UserService', 'ngCookies']);
app.controller('MainCtrl', function ($scope, UserService, $cookieStore) {
var uuid = $cookieStore.get('myUUID');
if (typeof uuid == 'undefined') {
UserService.getNewUUID().$then(function(response) {
uuid = response.data.uuid;
$cookieStore.put('myUUID', uuid);
});
}
});
Your unit tests need not have to create a mock $cookieStore and essentially re-implement its functionality. You can use Jasmine's spyOn function to create a spy object and return values.
Create a stub object
var cookieStoreStub = {};
Set up your spy object before creating the controller
spyOn(cookieStoreStub, 'get').and.returnValue('TEST'); //Valid syntax in Jasmine 2.0+. 1.3 uses andReturnValue()
mainCtrl = $controller('MainCtrl', {
...
$cookieStore: cookieStoreStub
}
Write unit tests for the scenario in which cookie is available
describe('If the cookie already exists', function() {
it('Should not retrieve UUID from server', function() {
console.log(cookieStore.get('myUUID')); //Returns TEST, regardless of 'key'
expect(userService.getNewUUID).not.toHaveBeenCalled();
});
});
Note: If you'd like to test multiple cookieStore.get() scenarios, you might want to move the creation of the controller into a beforeEach() inside the describe() block. This allows you to call spyOn() and return a value appropriate for the describe block.