I need to fetch some data before allowing the page to render so that the page isn't empty for a second before showing the info. But I'm unable to get my resolve to work.
The issue is that the uid line throws an error because states.getAuth() is undefined. states.getAuth() should (and does) return authentication info about the user when using it from my controllers but when using it in this resolve it doesn't for some reason.
Am I going about this completely wrong? I have never had to do a resolve like this before so I wouldn't know so some guidance would be great.
Let me know if I have to include any of my services or if this route snippet is enough to figure out a solution.
.when('/programs/:program', {
templateUrl: 'views/pages/single-program.html',
resolve: {
'isAuth': ['fbRefs', function(fbRefs) {
return fbRefs.getAuthObj().$requireAuth();
}],
'programData': ['$route', 'fbRefs', 'states', function($route, fbRefs, states) {
// Get our program key from $routeParams
var key = $route.current.params.program;
// Get unique user id
var uid = states.getAuth().uid; // Throws error
// Define our path
var path = uid + '/programs/' + key;
// Fetch the program from Firebase
var program = fbRefs.getSyncedObj(path).$loaded();
return program;
}]
}
})
Added states service code by request:
auth.service('states', [function() {
var auth;
return {
getAuth: function() {
return auth;
},
setAuth: function(state) {
auth = state;
}
};
}]);
You are using the 'Service Recipe' to create the states service, but returning like a 'Factory Recipe'.
According to the doc:
https://docs.angularjs.org/guide/providers#service-recipe
You should either use this:
auth.factory('states', [function() {
var auth;
return {
getAuth: function() {
return auth;
},
setAuth: function(state) {
auth = state;
}
};
}]);
Or this:
auth.service('states', [function() {
var auth;
this.getAuth = function() {
return auth;
};
this.setAuth = function(state) {
auth = state;
};
}]);
Related
I'm currently working on a project to help me better understand angularjs! I am currently stuck on how to pass a parameter from the controller to service.
In my program, I have created a function called "GetForecastByLocation" when a user types in an input clicks on a button. From there I want to take their input and then pass it to the http call in service.js.
Originally, $http.get was in a long giant string of the API url, but I googled around and it seems that I'm supposed to use parameters when trying to change a portion of the string. As of right now, I know parameter is hardcoded to a specific city, but I want to take new input and pass the value of vm.city to the $http.get call.
If any one can help I would greatly appreciate it. Thank you!
controller.js
var app = angular.module('weatherApp.controllers', [])
app.controller('weatherCtrl', ['$scope','Data',
function($scope, Data) {
$scope.getForecastByLocation = function(myName) {
$scope.city = myName;
Data.getApps($scope.city);},
Data.getApps(city)
.then(function(data)){
//doing a bunch of things like converting units, etc
},
function(res){
if(res.status === 500) {
// server error, alert user somehow
} else {
// probably deal with these errors differently
}
}); // end of function
}]) // end of controller
service.js
.factory('Data', function($http, $q) {
var data = [],
lastRequestFailed = true,
promise;
return {
getApps: function() {
if(!promise || lastRequestFailed) {
promise = $http.get('http://api.openweathermap.org/data/2.5/weather?',{
params: {
q: Tokyo,
}
})
.then(function(res) {
lastRequestFailed = false;
data = res.data;
return data;
}, function(res) {
return $q.reject(res);
});
}
return promise;
}
}
});
Passing arguments to a factory method is no different than passing arguments to a plain old function.
First, set up getApps to accept a parameter:
.factory('Data', function($http, $q){
// ...
return {
getApps: function(city){
promise = $http.get(URL, {
params: {q: city}
}).then( /* ... */ );
// ...
return promise;
}
};
});
Then pass it your argument:
$scope.getForecastByLocation = function(myName) {
$scope.city = myName;
Data.getApps($scope.city);
}
It's just like setting a value to a function's context variable.
Services.js
Simple example of a service.
.factory('RouteService', function() {
var route = {}; // $Object
var setRoute_ = function(obj)
{
return route = obj;
};
var getRoute_ = function()
{
if(typeof route == 'string')
{
return JSON.parse(route);
}
return null;
};
return {
setRoute: setRoute_,
getRoute: getRoute_
};
})
Controllers.js
Simple example of Service usage:
.controller('RoutesCtrl', function ($scope, RouteService) {
// This is only the set part.
var route = {
'some_key': 'some_value'
};
RouteService.setRoute(route);
})
I'm working on a very modularized project and currently I'm building an Element Directive which changes templateUrl based on user login/logout.
To do that, I'm trying to execute a Factory's Function inside templateUrl. That particular functions calls another method from a JWT Factory and returns true if the user is logged or false if not.
Then, If in my templateUrl I receive true, I pick a certain url, if false another one.
But, sadly, I receive the following error:
[$http:badreq] Http request configuration url must be a string. Received: {}
All $log.log() print the correct result.
Of course, it won't render nor page1 nor page2
Directive
(function () {
'use strict';
angular
.module('myApp')
.directive('myDirective', ['SessionCheckerFactory', function (SessionCheckerFactory) {
return {
restrict: 'E',
templateUrl : function(){
return SessionCheckerService.checkSession().then( function (res) {
console.log(res);//true
return res ? 'app/page1.html' : 'app/page2.html';
});
},
controller : 'MyController',
controllerAs : 'myCtrl',
bindToController : true
};
}]);
})();
SessionCheckerFactory
(function () {
'use strict';
angular
.module('myApp')
.factory('SessionCheckerFactory', function (AuthTokenFactory) {
function checkSession() {
return AuthTokenFactory.isAuth();
}
return {
checkSession: checkSession
}
});
})();
AuthTokenFactory
(function() {
'use strict';
angular.module('myApp')
.factory('AuthTokenFactory', function AuthTokenFactory(store, $cookies) {
//Takes user's info from LocalStorage, if not empty returns a String with encoded string informations
function getToken() {
if (store.get(key)) {
return store.get(key);
}
//Takes user's info from cookie
var token = $cookies.get('token', {path: '/'});
store.set(key, token);
return token;
}
//If getToken is empty returns false, else true
function isAuth() {
return Promise.resolve(Boolean(getToken()));
}
return {
isAuth : isAuth,
getToken : getToken
}
});
})();
I read around that this problem is usually generated by $http requests, but that's not my case. I didn't find any solution to that so far.
How can I fix this?
Thanks in advance.
Then, If in my templateUrl I receive true, I pick a certain url, if false another one.
Actually you don't. If you receive true, you pick one url, if some truthy value, another url, and if something falsy then you don't pick any url:
if (res) {
if (res === true) {
return resolve('app/page1.html');
} // else
return resolve('app/page2.html');
}
// else return undefined;
You probably want
templateUrl : function(){
return SessionCheckerFactory.checkSession().then(function (res) {
if (res) {
return 'app/page1.html';
} else {
return 'app/page2.html';
}
})
},
I managed to fix the issue using a link function and $templateRequest
Directive
link: function (scope, element) {
SessionCheckerService.renderTemplate().then(function (temp){
$templateRequest(temp).then(function (requestedTemplate) {
element.html(requestedTemplate);
$compile(element.contents())(scope);
});
});
}
Factory
var templateConfig = './app/config/templates.config.json';
function getTemplate(){
return $http.get(templateConfig)
.then(function(templates) {
return templates.data;
});
}
function checkSession() {
return Promise.resolve(AuthTokenFactory.isAuth());
}
function whichTemplate(template, result) {
var myTemplate = '';
if(result){
myTemplate = template.logIn;
} else {
myTemplate = template.logOut;
}
if(myTemplate){
return Promise.resolve(myTemplate);
}
}
//Chaining the methods and returning the correct template
function renderTemplate() {
return new Promise(function (resolve) {
checkSession().then(function(isAuth){
getTemplate().then( function(templates){
whichTemplate(templates, isAuth).then( function (temp) {
return resolve(temp);
});
});
});
});
}
return {
renderTemplate : renderTemplate
}
Templates Config
{
"logOut" : "app/page1.html",
"logIn" : "app/page2.html"
}
I hope It'll be helpful.
i used a tutorial to create a angularfire chat app. it is a standalone app that uses ui-router. I integrated it succssfully as a view in my app but that is not practical. I need to be able to use the chat on any view I am at. I am stuck at moving a resolve function to a controller. I have read some docs and I believe it is returning a promise that I need to resolve in the controller. the link to the tutorial is here.
tutorial
here is the ui-router I am trying to get away from
.state('channels.direct', {
url: '/{uid}/messages/direct',
templateUrl: 'views/chat/_message.html',
controller: 'MessageController',
controllerAs: 'messageCtrl',
resolve: {
messages: function ($stateParams, MessageService, profile) {
return MessageService.forUsers($stateParams.uid, profile.$id).$loaded();
},
channelName: function ($stateParams, UserService) {
return UserService.all.$loaded().then(function () {
return '#' + UserService.getDisplayName($stateParams.uid);
});
}
}
})
The message service
var channelMessagesRef = new Firebase(AppConstant.FirebaseUrl + 'channelMessages');
var userMessagesRef = new Firebase(AppConstant.FirebaseUrl + 'userMessages')
return {
forChannel: function (channelId) {
return $firebaseArray(channelMessagesRef.child(channelId));
},
forUsers: function (uid1, uid2) {
var path = uid1 < uid2 ? uid1 + '/' + uid2 : uid2 + '/' + uid1;
return $firebaseArray(userMessagesRef.child(path));
}
};
the user service
var usersRef = new Firebase(AppConstant.FirebaseUrl + 'users');
var connectedRef = new Firebase(AppConstant.FirebaseUrl + '.info/connected');
var users = $firebaseArray(usersRef);
return {
setOnline: function (uid) {
var connected = $firebaseObject(connectedRef);
var online = $firebaseArray(usersRef.child(uid + '/online'));
connected.$watch(function () {
if (connected.$value === true) {
online.$add(true).then(function (connectedRef) {
connectedRef.onDisconnect().remove();
});
}
});
},
getGravatar: function (uid) {
return '//www.gravatar.com/avatar/' + users.$getRecord(uid).emailHash;
},
getProfile: function (uid) {
return $firebaseObject(usersRef.child(uid));
},
getDisplayName: function (uid) {
return users.$getRecord(uid).displayName;
},
all: users
};
here is what I have so far in the controller
$scope.directMessage = function (uid) {
UserService.all.$loaded().then(function () {
$scope.selectedChatUser = '#' + UserService.getDisplayName(uid);
});
$scope.selectedChatUserMessages = MessageService.forUsers(uid, profile.$id).$loaded();
};
I am returning the
$scope.selectedChatUser
fine. the issue is with the Message Service
this is the what i am currently returning from the message service
$$state: Object
__proto__: Promise
how do i resolve this?
You're trying to return from inside a promise in your channelName function.
The object you're getting back is an unresolved promise. You want the resolved data from the promise injected into your controller.
You need to create a to return from this function.
.state('channels.direct', {
url: '/{uid}/messages/direct',
templateUrl: 'views/chat/_message.html',
controller: 'MessageController',
controllerAs: 'messageCtrl',
resolve: {
messages: function ($stateParams, MessageService, profile) {
return MessageService.forUsers($stateParams.uid, profile.$id).$loaded();
},
channelName: function ($stateParams, UserService, $q) {
// create a deferred for this function
var deferred = $q.defer();
// load async data with UserService.all's promise
UserService.all.$loaded()
.then(function () {
var name = UserService.getDisplayName($stateParams.uid);
deferred.resolve(name);
});
// return promise
return deferred.promise;
}
}
})
However in getDisplayName I would just recommend returning back the object rather than just the name, as the entire set is synchronized by Firebase.
I'm trying to cache response from $http into an object for a session in angular, so once the initial call has been made, every other call to service.getCategories() (e.g), will get the data from the object rather than to the api.
The service is being resolved at the route, but there is authentication, which will redirect to another route - calling service.getCategories() again.
I'm attempting this by setting an init variable on call, then all other calls will direct to the populated object - but it seems to reset the service somehow, and the returned object gets populated twice, so there's double of everything. See below:
var $ = require('jquery');
module.exports = angular.module('app.common.services.category', [])
.factory('categoryService', ['$http', '$q', '$rootScope', function($http, $q, $rootScope) {
// API Parameters
var deferred = $q.defer();
// Services
var Categories = {
init: false,
categories: [],
index: 0,
// Retrieve all data on load.
// Loaded into an array for all other services
// to use after init.
getCategories: function(page) {
if(!Categories.init) {
$http.get('api/core/get_category_index')
.then(function(result) {
var data = result.data.categories;
$.each(data, function(i, category) {
category.index = Categories.index;
Categories.categories.push(category);
Categories.index++;
});
Categories.init = true;
return deferred.resolve(Categories.categories);
});
// Return promise once catgories is resolved
return deferred.promise;
} else {
return Categories.categories;
}
},
allCategories: function() {
return Categories.categories;
}
}
return Categories;
}]);
A problem with your approach is when the service function getCategories is called for the second time, the first time server request may not is resolved, causing a second call to the server. So you should move the init flag directly after the function call getCategories.
An other problem is that in your case you don't know whether the function will return a promise or an Array. I Suggest always returning an Array
module.exports = angular.module('app.common.services.category', [])
.factory('categoryService', ['$http', '$q', '$rootScope', function($http, $q, $rootScope) {
// API Parameters
var deferred;
// Services
var Categories = {
categories: [],
index: 0,
// Retrieve all data on load.
// Loaded into an array for all other services
// to use after init.
getCategories: function(page) {
if(!deferred) {
// replacement for intit flag
deferred = $q.defer();
$http.get('api/core/get_category_index')
.then(function(result) {
var data = result.data.categories;
$.each(data, function(i, category) {
category.index = Categories.index;
Categories.categories.push(category);
Categories.index++;
});
deferred.resolve(Categories.categories);
});
}
// always return a promise
return deferred.promise;
},
allCategories: function() {
return Categories.categories;
}
}
return Categories;
}]);
Maybe you can return the service itself with the promise. Then you could write everywhere something like:
myService.load().then(
function success(theService) {
theService.allCategories()
}
);
Now it doesn't matter anymore whether the service was loaded before or not
I have a function inside a directive that makes a query (and gets results, according to the console). The problem is that I can't seem to be able to store those results into a factory, and pass them to a controller.
This is the directive:
scope.getVersions = function(release) {
if (angular.isUndefined(release)) return;
musicInfoService.getReleaseVersions(release.id)
.success(function(data) {
dataService = data.versions;
console.log(dataService);
});
};
The console shows that dataService contains an array with the results.
Then, I try to store it into a factory:
app.factory('dataService', [function(){
return { items: [] };
}]);
And I call it in a controller:
function VersionController($scope, dataService) {
$scope.versions = dataService.items;
console.log($scope.versions);
}
But both items and $scope.versions come back an empty array. Did I miss something?
You should really use a backing field to store that data, and use setter and geter functions for writing and reading respectively:
app.factory('dataService', [function(){
var _items={};
return {
setItems:function(value){
_items=value;
},
getItems:function(){
return _items;
}
};
}]);
And for setting the data:
musicInfoService.getReleaseVersions(release.id)
.success(function(data) {
dataService.setItems(data.versions);
console.log(dataService);
});
and reading:
function VersionController($scope, dataService) {
$scope.versions = dataService.getItems();
console.log($scope.versions);
}
See demo plunk.
There's a misunderstanding of angular factories going on here. You're trying to set dataService = , which will never work.
As Mohammad mentioned, you need to have a variable set outside of your return value in the factory and the return value is basically an object with functions that allow you to manipulate your constant. So what you need is a getter "getItems()" for getting the items, and a setter "addItem(item)" for adding an item to your items array.
So you're never directly injecting your "items" into a controller, you're injecting a bunch of functions that can get or manipulate your "items".
scope.getVersions = function(release) {
if (angular.isUndefined(release)) return;
musicInfoService.getReleaseVersions(release.id)
.success(function(data) {
dataService.addItem(data.versions);
console.log(dataService.getItems());
});
};
app.factory('dataService', [function(){
var items = [];
return {
addItem: function(item) {
items.push(item);
},
getItems: function() {
return items;
}
};
}]);
function VersionController($scope, dataService) {
$scope.$watch(dataService.getItems, function(items) {
$scope.versions = items;
console.log($scope.versions);
});
}