I'm using AngularFire in a multiplayer game and it sure looks like AngularFire is deleting my objects after the first load of a controller.
I'm using browserify to concatenate my JS files together so all my modules look like CommonJS modules.
My controllers are all loaded via ngViews and defined routes. I'm trying to keep knowledge of what user objects in Firebase look like confined to a user service; hence, all AngularFire invocations for the user object live in the service.
Here's my HomeController:
module.exports = function(ngModule) {
ngModule.controller('HomeController',
function($scope, user) {
if (!$scope.user) {
return;
}
user.getInvitations($scope);
user.getGames($scope);
});
};
I'm using AngularFire's auth from an auth service I defined. When the user object comes in, it is stored on the $rootScope. Here's a snippet of my auth service:
ngModule.run(function(angularFireAuth, warbase, $rootScope) {
// Here's where Firebase does all the work.
angularFireAuth.initialize(warbase, {
scope: $rootScope,
name: 'user',
path: '/login'
});
});
My user service uses a gotUser promise that is resolved when the user is present on the $rootScope. Here's a snippet of my user service:
$rootScope.$watch('user', function() {
if (!$rootScope.user) {
return;
}
userRef = warUsers.child(getPlayerId());
userInvitationsRef = userRef.child('invitations');
userGamesRef = userRef.child('games');
gotUser.resolve();
});
// ...
getInvitations: function(scope) {
scope.invitations = [];
gotUser.promise.then(function() {
angularFire(userInvitationsRef, scope, 'invitations');
});
},
getGames: function(scope) {
scope.games = [];
gotUser.promise.then(function() {
angularFire(userGamesRef, scope, 'games');
});
}
When my HomeController is loaded by a user with no games and no invitations everything works as expected. The user creates a game and it shows up on the home screen.
If I then reload the page the user's game objects (from path /users/:userId/games/) are cleared. There is no code to remove these values in my application at the moment.
I thought there might be a reference to AngularFire hanging around and syncing the blank value I set in the user service, so I removed the scope.games = []; line. If I don't set the initial value on the scope in my service I get this error in the console: Uncaught Error: Firebase.set failed: First argument contains undefined.
I'm guessing this has something to do with my unfamiliarity with controller lifecycles under ngView, my mis-use of services to DRY up my Firebase/AngularFire references, and general AngularJS newbishness all rolled into a galloping ball of fail, but I'd appreciate any pointers anyone can provide.
Related
Suppose there is a size several link. Every link click is handled by controller. Consider the situation:
User visit some page. Let say that it is /search where user inputs keywords and press search button.
A background process started (waitint for search response in our case)
user goes to another link
after some time user goes back to fisrt page (/search)
At the point 4 angulajs load page as user goes to it at first time. How to make angulajs remeber not state but process? E.g. if process is not finished it shows progress bar, but if it finished it give data from process result and render new page. How to implement that?
Notes
I have found this but this is about just state without process saving.
I have found that but this is about run some process at background without managing results or process state (runing or finished)
You can use angularjs service to remember this "Process" of making an api call and getting the data from it .
here is a simple implementation.
the whole idea here is to create a angular service which will make an api call,
store the data aswell as the state of the data, so that it can be accessed from other modules of angularjs. note that since angularjs services are singleton that means all of their state will be preserved.
app.service('searchService', function() {
this.searchState={
loading: false,
data: null,
error: null
}
this.fetchSearchResults = function(key){
// call api methods to get response
// can be via callbacks or promise.
this.searchState.loading=true;
someMethodThatCallsApi(key)
.then(function(success){
this.searchState.loading=false;
this.searchState.data=success;
this.searchState.error=null
})
.catch(function(error){
this.searchState.loading=false;
this.searchState.data=null;
this.searchState.error=error
});
}
this.getState = function(){
return this.searchState
}
});
// in your controller
app.controller('searchController',function(searchService){
// in your initialization function call the service method.
var searchState = searchService.getState();
// search state has your loading variables. you can easily check
// and procede with the logic.
searchState.loading // will have loading state
searchState.data // will have data
searchState.error // will have error if occured.
});
Even if you navigate from pages. the angular service will preserve the state and you can get the same data from anywhere in the application. you simply have to inject the service and call the getter method.
Based on the question, (a little bit more context or code would help answers be more targeted), when considering async operations within angularJS, its always advisable to use getters and setters within service to avoid multiple REST calls.
Please note - Services are singletons, controller is not.
for eg:
angular.module('app', [])
.controller('ctrlname', ['$scope', 'myService', function($scope, myService){
myService.updateVisitCount();
$scope.valueFromRestCall = myService.myGetterFunctionAssociated(param1, param2);
//Use $scope.valueFromRestCall to your convinience.
}]
.service('myService', ['$http', function($http){
var self = this;
self.numberOfVisits = 0;
self.cachedResponse = null;
self.updateVisitCount = function(){
self.numberOfVisits+=1;
}
self.myGetterFunctionAssociated = function(param1, param2){
if self.cachedResponse === null || self.numberOfVisits === 0 {
return $http.get(url).then(function(response){
self.cachedResponse = response;
return response;
});
}
else {
return self.cachedResponse;
}
}
return {
updateVisitCount: function(){
self.udpateVisitCount();
},
myGetterFunctionAssociated : function(param1, param2){
return self.myGetterFunctionAssociated(param1, param2);
}
}
}]
I have an existing application that uses a MapProvider like so:
mapModule.factory('MapProvider', ['$injector', function($injector) {
return $injector.get('GoogleMapsService');
}]);
This MapProvider is used extensively across the application and is injected into various other controllers and services (rightly or wrongly).
I now need to add a BaiduMapsService, which I have been able to get working as a test with:
mapModule.factory('MapProvider', ['$injector', function($injector) {
if(true) {
return $injector.get('GoogleMapsService');
} else {
return $injector.get('BaiduMapsService');
}
}]);
And flipping the if value accordingly. (Both of these services are using a TypeScript interface, so have the same methods). Now, I need to add a $http call to the API, which will return which map to use, based on the provided data. How can I make my factory asynchronous, without having to change all my MapProvider.someCallHere() calls to MapProvider.then(m => m.someCallHere()).
Ideally, when MapProvider is injected across my application, it will be able to resolve using the async data (only once), and then inject the necessary service afterwards.
Alternatively, is there a way to defer / delay loading Angular at all, until I make an API call and set some global data somewhere?
Thanks.
You can postpone the application bootstrap (also, don't use ng-app, do it manually) until you get data from server. I've answered this before on this question but each case has its own specific details.
I usually see a config value being declared on the app before the application gets bootstraped, this is very useful for multi-tenant apps. So that this preference values can be used in the whole app as an injected provider.
For example:
var app = angular.module('app', []);
// retrieve the $http provider
var ngInjector = angular.injector(["ng"]);
var $http = ngInjector.get("$http");
// load config function. Returns a promise.
function loadConfig(){
return $http.get("/config.json").then(function(response) {
// declare the configuration value on your app
app.constant("Config", response.data);
}, function(err) {
console.error("Error loading the application config.", err)
});
}
// Call loadConfig then bootstrap the app
loadConfig().then(function () {
angular.element(document).ready(function() {
angular.bootstrap(document, ["app"]);
});
});
Finally from your factory, you can use the Config constant to retrieve the preferred map.
mapModule.factory('MapProvider', ['$injector', 'Config', function($injector, Config) {
if(Config.preferedMap == 'GoogleMap') {
return $injector.get('GoogleMapsService');
} else {
return $injector.get('BaiduMapsService');
}
}]);
Only way I can think is to hold initialize whole angular (and modules) until you got your "config" (and set is as global variable).
I'm looking for the best architectural solution.
I have following html:
<body ng-controller="individualFootprintController as $ctrl">
<div ng-hide="$ctrl.authenticated">
<h1>Login</h1>
With Corporate Account: click here
</div>
And controller:
function individualFootprintController($http) {
var self = this;
$http.get("/is_auth").success(function () {
self.authenticated = true;
})
.error(function() {
self.authenticated = false;
}
);
}
Questions:
1) Is this controller appropriate place for having this logic?
2) I want to have actual "is_authenticated" value. How can make this happen, if I want to fire request only once
Presumably authentication with the backend requires an actual token of some kind. I.e. you don't just set a true/false flag somewhere and call it authentication, but to be able to communicate with the backend you need to include a username/password/cookie/token in the request or the request will be denied.
A controller is a bad place to store such a thing, since controllers aren't permanent. Or at least you shouldn't make them permanent to the extent possible. Also, storing the token in a controller doesn't allow anything else access to it.
Whether you are logged in or not should be based on whether you have a valid authentication token or not.
This token must be stored in a canonical place, best suited for that is a service.
Other services get the token from there and also decide whether the app is currently "logged in" based on whether a token is available.
A rough sketch of how that should be structured:
app.service('AuthService', function () {
this.token = null;
});
app.service('FooService', function (AuthService, $http) {
$http.get(..., AuthService.token, ...)
});
app.controller('LoginStatusController', function (AuthService) {
Object.defineProperty(this, 'isLoggedIn', {
get: function () { return AuthService.token != null; }
});
});
<div ng-controller="LoginStatusController as ctrl">
<div ng-hide="ctrl.isLoggedIn">
When you actually log in and obtain a token, you set AuthService.token and it will be available to all other services and controllers. If and when the token becomes invalid or unset, all services and controllers lose their authenticated status.
What I usually do is the following :
Using ui-router
Take advantage of the resolve hook (which resolves some args and inject them into controller), and define my routes as subroutes of a main one which checks auth on each route change
scripts/services/user.js
angular.module('yourmodule')
.service('userSrv', function ($http, $q) {
var srv = {};
srv.getAuthenticatedUser = function() {
return $http.get("/authenticated_user");
};
return srv;
});
scripts/routes.js
angular
.module('yourmodule')
.config(function ($stateProvider) {
$stateProvider
.state('authenticated', {
abstract: true,
template: '<ui-view />',
controller: 'AuthenticatedCtrl',
resolve: {
signedInUser: function(userSrv, $q) {
return userSrv.getAuthenticatedUser()
.then(function(null, function() {
//Catch any auth error, likely 403
//And transform it to null "signedInUser"
//This is the place to handle error (log, display flash)
return $q.resolve(null);
});
}
}
})
.state('authenticated.myspace', {
url: '/myspace',
templateUrl: 'views/myspace.html'
});
});
Take advantage of $scope inheritance inside your view
scripts/controllers/authenticated.js
angular.module('yourmodule')
.controller('AuthenticatedCtrl', function (signedInUser, $scope, $state) {
//Here you set current user to the scope
$scope.signedInUser= signedInUser;
$scope.logout = function() {
//this is where you would put your logout code.
//$state.go('login');
};
});
views/myspace.html
<!-- here you call parent state controller $scope property -->
<div ng-hide="signedInUser">
<h1>Login</h1>
With Corporate Account: click here
</div>
1) I want to have actual "is_authenticated" value. How can make this happen, if I want to fire request only once
My solution asks for authenticated user on each route change. This seems strange but this is actualy viable and fast. The query should not be > 30ms it's a very small SELECT under the hood. THOUGH, asking "am I authenticated" and "get the authenticated user" are pretty the same thing, except one return a boolean, and the other return the user. I suggest, like I just show you, to handle "am I authenticated" question by requesting the authenticated user, then booleaning it with "if(user)" (null value handling).
2) Is this controller appropriate place for having this logic?
Yes and no. As you can see, the controller is the very place to "set the user to the scope thing", but scope inheritance allow you to not repeat it for each route. Though, http api logic should be ported to a service, and routing event ("get the authenticated user for this page, please" is a routing event IMHO) should be set in a separate file.
NB: if you need complete route "protection" (like redirection on non-authenticated, ask another question and I'll be glad to answer it)
Not an easy one to describe this, but basically I have a service which is set up as a provider so I can configure it. It has an array of APIs used in my project which is initially empty. Various config blocks can add an API object to the array and this seems to be working. I console.log the output each time and the array is growing.
I then inject my service into something else (in this case an $http interceptor function) and use a service method to return the array, but each time I get an empty array.
I thought the way this worked is that all config blocks ran first and that the $http call which is being intercepted is happening way after that, so the array should be full of APIs by the time it's intercepted.
Anyway, here's some code
angular.module('authModule').provider('authService', function(){
var _apis = [];
return({
addApi: addApi,
$get: instantiateAuth
});
function addApi(newApi){
_apis.push(newApi);
console.log("API added", _apis);
}
function instantiateAuth() {
return({
getApis: function(){
console.log("Getting APIs", _apis);
return _apis;
}
});
}
})
.config(function($httpProvider){
$httpProvider.interceptors.push(function($log, $rootScope, $q) {
return {
request: function(config) {
var injector = angular.injector(['ng', 'authModule']);
var authService = injector.get('authService');
console.log("apis", authService.getApis());
}
};
});
});
And an example config block
angular.module('myModule').config(function ($provide, authServiceProvider) {
authServiceProvider.addApi({
url: 'https://apiurl.com',
other: 'stuff'
});
authServiceProvider.addApi({
url: 'https://apiurl2.com',
other: 'stuff'
});
});
So, each time the appApi method is called in a config block (twice here), this line outputs the array console.log("API added", _apis); and it correctly outputs 1 item after the first call and two items after the second call.
When this code - authService.getApis() - fires the first time an HTTP call is intercepted, it logs an empty array to the console.
Any help would really be appreciated.
EDIT:
The problem seems to be this line
var injector = angular.injector(['ng', 'authModule']);
My provider seems to be reset/recreated each time this happens, so maybe I'm misunderstanding how to use the injector. I was originally just injecting my authService the normal way in the function parameters but I was getting a circular dependency (my auth service needs to open a modal window, but angular-ui modals rely on the http service and my http calls are being intercepted to check with my auth service that the user is authenticated :( )
Yes, angular.injector(['ng', 'authModule']) essentially creates a new injector instance (an application instance, in layman's terms):
angular.injector(['authModule']) !== `angular.injector(['authModule'])
ng module is loaded by default, it doesn't have to be specified explicitly. And singleton services are singletons only within the same injector instance:
injector.get('authService') === injector.get('authService');
But
angular.injector(['authModule']).get('authService') !== `angular.injector(['authModule']).get('authService')
To reuse current injector instance (which is a desirable behaviour in almost every situation) $injector service should be used:
$httpProvider.interceptors.push(function($log, $rootScope, $q, $injector) {
return {
request: function(config) {
var authService = $injector.get('authService');
}
};
});
$injector.get is known and straightforward solution to get around circular dependencies.
In my application I have an orchestration service that gets the uris from different services that register with it. service_1 and service_2 could be on different machines but one registered, the uris of their machines will be stored.
In my other application which makes use of that orchestration service, I want to call to the orchestration service to get the uris to use, but then I want to set them as Angular constants, or at least be able to use the uri's values.
So this is the service that's going to be using the 'constant' which is the uri pulled from orchestration service:
(function () {
'use strict';
angular
.module('data.model-view', ['restapi'])
.factory('MVService', MVService);
MVService.$inject = ['$http', '$q', 'exception', 'logger', 'restapi'];
/* #ngInject */
function MVService($http, $q, exception, logger, restapi) {
var HOST = restapi.mvservice.HOST;
var MODULE = restapi.mvservice.MODULE;
...
//below is an example of what will use the above host/module in order to
//get the data needed for this application
function getModels() {
return $http.get(HOST + MODULE + '/model/retrieveAll')
.then(success)
.catch(fail);
function success(response) {
return response.data;
}
function fail(e) {
return exception.catcher('XHR Failed for retrieveAll')(e);
}
}
So then this is restapi module where I'd like the constants to be set, so I can have access to them throughout the application, but I need to get them from the orchestration service first.
(function() {
'use strict';
var data = '';
angular
.module('restapi', [])
.factory('restapi', function($http, exception) {
var HOST = %%ORCSERVICE%%;
var MODULE = '/orchestration/service/rest/data';
return $http.get(HOST + MODULE)
.then(success)
.catch(fail);
function success(response) {
//so at this point I can actually access the data I need
//with a console.debug(response.data);
return response.data;
}
function fail(e) {
return exception.catcher('XHR Failed to reach orc')(e);
}
}).constant('restapi', constant());
function constant() {
//set constants here based on the data pulled from above
//ideally I want the result of this to be like
//{
// mvservice: {
// 'HOST': 'http://xxxxxxxxxx.amazonaws.com'
// 'MODULE': '/rest/service/whatever'
// },
// ... //other service here
//}
}
})();
Like I say in the comment above, I can actually get the data I need (the uris) from the $http.get immediately above. I'd just like then to be able to get the data set as a constant, or at least in a form that I can access it. Because when MVService spins up, it needs the its own uir from the orchestration service in order to be able to make its rest calls. Sorry might be a little confusing, let me know if there is a need for clarification.
Try bootstrapping app after getting necessary data:
var injector = angular.injector(['ng']),
http = injector.get('$http');
http.get(HOST + MODULE).then(function (result) {
app.value('restapi', result.data);
angular.bootstrap(document, [app.name]);
});
There is multiple way :
If you want to use angular.constant you can do it by getting the url by delaying bootstrap of angular until you get your values. Because you can't set constant once the ng-app has loaded. To do this see karaxuna's answer.
Another way is to perform your angular queries in the constructor of your service. Because the routing and call of controllers won't happen until all promise of the service's instantiation phase will be resolved. So you can store the result of your request as fields of your service and then access it without any racing problems on the controllers/directives/angular.run parts of your application.