I'm trying to use the WindowsAzure.MobileServiceClient within Angular to do single sign on and CRUD operations. Being an Angular noob, I'm trying to figure out the best way to do this:
Instantiate it in the $rootScope in .run and call the functions from there?
Create a service or factory and make the instantiation of the MobileServiceClient and all of the function calls in that? Would the currentUser and other information get lost when the service/factory isn't being used?
Just spool up MobileServiceClient in the controllers that need it? Seems to me if I do it that way, currentUser info would get lost?
I've tried using some of the above methods but I'm running into some problems:
Calling the login method as shown in the Azure docs sometimes works, other times it doesn't show a popup window to the authentication provider like it should. I am logged off of the authentication provider so a popup window should be shown but isn't,
No matter what I try to do, the MobileServiceClient currentUser is coming back as null, even when the popup was shown and I correctly entered my credentials.
Any ideas of what I can do to make this work smoothly? Any examples I can follow somewhere? The documentation seems sketchy.
I'm using Yeoman and the angular generator along with Grunt to do my work, if it makes any difference.
I was able to figure it out. I created a new service to handle all of the mobile services code. Since the methods from the client are async, I'm using callbacks in the methods. I also use the cookie store to save the user's credential object (currentUser) and pull it out again when needed. currentUser seems to lose the user credential object between calls, so I store it in a cookie and push it into the client when it has lost it.
'use strict';
angular.module('{appName}')
.factory('AzureMobileClient', ['$cookieStore', function ($cookieStore) {
var azureMobileClient = {};
azureMobileClient.isLoggedIn = false;
azureMobileClient.azureError = "";
azureMobileClient.azureMSC = new WindowsAzure.MobileServiceClient('{app URL from Azure}', '{app key from Azure}');
azureMobileClient.login = function(callback, socialMediaService) {
azureMobileClient.azureMSC.login(socialMediaService).then(function(user) {
azureMobileClient.isLoggedIn = user != null;
$cookieStore.put("azureUser", user);
callback(azureMobileClient.isLoggedIn);
}
, function(error){
alert(error);
azureMobileClient.azureError = error;
});
};
azureMobileClient.logout = function() {
azureMobileClient.getUser();
azureMobileClient.azureMSC.logout();
$cookieStore.remove("azureUser");
};
azureMobileClient.getStuff = function(callback) {
azureMobileClient.getUser();
var stuffTable = azureMobileClient.azureMSC.getTable("stuff");
stuffTable.read().then(function(items) {
callback(items);
});
};
azureMobileClient.addStuff = function(scope) {
azureMobileClient.getUser();
var stuffTable = azureMobileClient.azureMSC.getTable("stuff");
stuffTable.insert({ stuffname: scope.stuffname });
};
azureMobileClient.getUser = function() {
if (azureMobileClient.azureMSC.currentUser === null)
{
azureMobileClient.azureMSC.currentUser = $cookieStore.get("azureUser");
}
};
return azureMobileClient;
}]);
In the controller that does the login and logout, I do this:
'use strict';
angular.module('{appName}')
.controller('MainCtrl', function ($scope, $window, AzureMobileClient) {
$scope.authenticate = function (socialService) {
AzureMobileClient.login(function(isLoggedIn) {
if (isLoggedIn)
{
$window.location.href = "/#/play";
}
}, socialService);
};
$scope.signOut = function() {
AzureMobileClient.logout();
}
});
The view for the login/logout controller looks like this:
<button ng-click="signOut()">Sign Out</button>
<div class="span4">
<img src="images/facebooklogin.png" ng-click="authenticate('Facebook')" />
<img src="images/twitterlogin.png" ng-click="authenticate('Twitter')" />
<img src="images/googlelogin.png" ng-click="authenticate('Google')" />
</div>
And finally in a controller that gets data, I do this:
'use strict';
angular.module('{appName}')
.controller('StuffCtrl', ['$scope', '$window', 'AzureMobileClient', function ($scope, $window, AzureMobileClient) {
AzureMobileClient.getStuff(function(items) {
if (items.length == 0)
{
$window.location.href = "/#/stuff/new";
}
else
{
$scope.$apply($scope.items = items);
}
});
}]);
Just for someone who search ready-to-use solution https://github.com/TerryMooreII/angular-azure-mobile-service
This is angularjs service which implement below methods:
Azureservice.query(tableName, parameters, withFilterFn)
Azureservice.insert(tableName, obj, withFilterFn)
Azureservice.update(tableName, obj, withFilterFn)
Azureservice.del(tableName, obj, withFilterFn)
Azureservice.getAll(tableName, withFilterFn)
Azureservice.getById(tableName, id, withFilterFn)
Azureservice.read(tableName, parameters, withFilterFn)
Azureservice.login(oauthProvider, token)
Azureservice.logout()
Azureservice.setCurrentUser(userObj)
Azureservice.getCurrentUser()
Azureservice.isLoggedIn()
Azureservice.invokeApi()
Just add 'azure-mobile-service.module' in your dependency list and configure api keys:
angular.module('your-module-name').constant('AzureMobileServiceClient', {
API_URL : 'https://<your-api-url>.azure-mobile.net/',
API_KEY : '<your-api-key>',
});
and then:
angular.module.('your-module-name').controller('MainController', function ($scope, Azureservice) {
$scope.loginAction = function () {
Azureservice.login('facebook').then(function () {
console.log('login successful');
}).catch(function () {
console.log('login error');
}
}
}
Related
I'm very new to AngularJS and programming aswell, so it can be easy question for you but I'm struggling with it for plenty of hours and can't get my thinking straight.
So, my goal is simple, to have facebook login on my app (webpage), I'm using Ciul angular-facebook module, this actually works but not in the way I want it. When user loggs I want to show his name and photo, but now I have to manually reload page, then it shows, until then it won't. Also I'm storing logged user into localStorage (works ok).
The problem is that after logging in the data aren't updated. In my html code I tried to call them from controller or service, but both had old data, and I don't know how to update it without reloading the page. The most interesting part is when I try it with simple variable and it works like a charm.
Here is my Service
app.factory('mainService', ['$window', '$location', '$route', '$filter', 'Facebook', function (win, $location, $route, $filter, Facebook) {
var scope = {
fbLogin: false,
fbUid: 0,
fbAccessToken: 0,
vkLogin: false
};
var user = {};
if(localStorage.getItem('user') != null) {
user = JSON.parse(localStorage.getItem('user'));
} else {
user = null;
}
return {
scope : scope,
user : user,
fbLogin : function () {
Facebook.login(function (response) {
scope.fbLogin = response.status;
scope.fbAccessToken = response.authResponse.accessToken;
scope.Uid = response.authResponse.userID;
Facebook.api('/me?fields=id,name,email,picture', function (response) {
localStorage.setItem('user', JSON.stringify(response));
});
});
console.log('setting user');
user = JSON.parse(localStorage.getItem('user'));
},
fbLogout : function () {
Facebook.logout(function (response) {
});
user = null;
localStorage.removeItem('user');
},
removeAuth : function () {
Facebook.api({
method: 'Auth.revokeAuthorization'
}, function (response) {
Facebook.getLoginStatus(function (response) {
scope.fbLogin = response.status;
});
});
}
};
}]);
Here is my Controller
app.controller('IndexController', ['$scope', '$location', '$http', 'mainService', function ($scope, $location, $http, mainService) {
$scope.ms = mainService;
$scope.user = mainService.user;
$http({
method: 'GET',
url: 'backend/api/v1/getItems.php',
headers: { "Content-Type": 'text/json; charset="utf-8"' }
})
.success(function(data, status) {
//alert("status is : "+status);
if(status === 200) {
$scope.items = data;
}
})
.error(function(data, status) {
$scope.items = [];
});
}]);
Calling login function < li >< a href="#/" ng-click="ms.fbLogin()">Login using Facebook
Calling data in html {{user.name}} or {{ms.user.name}}
Well, the same problem is also with logging out.
Hey so it's hard for me to say this is the exact problem but from the looks of it everything you have so far is actually pretty good.
There's only one problem I spot and it just boils down to understanding objects are bound to variables by reference. If you're not sure what that means I would say do some research on that first since it's a fundamental part of javascript.
What seems to be the problem here is that you are setting 'user' to an empty object in your service. In your controller you are then assigning $scope.user to that same object empty.
However in your login function you are assigning user to the new JSON from the storage. First, this should also be inside the success callback, right after you set the localstorage.
I'm not familiar with that module but I'm going to assume that those are async functions. Therefore you're grabbing that local storage data before it's even been set.
Also, by setting 'user' to a new object you have updated it's value in the service but not in the controller.
Because it's bound by reference, 'user' in the service now points to a new object, while $scope.user is still pointing to that original empty object.
To solve this problem you can do two things:
Handle the callback differently so that you reassign $scope.user to the new data.
Or you can take advantage of object reference.
You can keep most of your code the same, but in your service, instead of assigning the data to 'user', assign it to a property on user.
Facebook.api('/me?fields=id,name,email,picture', function (response) {
localStorage.setItem('user', JSON.stringify(response));
user.data = response;
});
Since both the service and the controller are referencing the same object, you will have access to that new data property on $scope.user.
HTML:
<span>{{user.data.name}}</span>
I've used firebase simpleLogin with facebook in AngularJS. (Generated with angular and angularfire generators in yeoman)
How do I keep the user in a variable, $scope, $rootScope etc. correctly? The user is going to be used in another angular module.
The code looks like this:
'use strict';
angular.module('angularfire.login', ['firebase', 'angularfire.firebase'])
.run(function(simpleLogin) {
simpleLogin.init();
})
.factory('simpleLogin', function($rootScope, $firebaseSimpleLogin, firebaseRef, $timeout) {
function assertAuth() {
if( auth === null ) { throw new Error('Must call loginService.init() before using its methods'); }
}
var auth = null;
return {
init: function() {
auth = $firebaseSimpleLogin(firebaseRef());
return auth;
},
logout: function() {
assertAuth();
auth.$logout();
},
/**
* #param {string} provider
* #param {Function} [callback]
* #returns {*}
*/
login: function(provider, callback) {
assertAuth();
auth.$login(provider, {rememberMe: true}).then(function(user) {
if( callback ) {
//todo-bug https://github.com/firebase/angularFire/issues/199
$timeout(function() {
callback(null, user);
});
}
}, callback);
}
};
});
For instance $rootScope.user = user; is working, but I've been told that's not a clean method. Also, if I'm refreshing I'm stilled logged in, but the $rootscope.user becomes undefined.
Thanks
The best way is to directly assign the object returned by $firebaseSimpeLogin to $scope:
$scope.auth = $firebaseSimpleLogin(firebaseRef());
You can then reference this in your views directly. For example:
<span ng-show="auth.user">
{{auth.user.name}} | Logout
</span>
Login
Meant to be a comment for the question on Anant's answer but require 50 rep to comment:
$scopes in AngularJS are arranged in a hierarchical structure that mimics the DOM so they are nestable.
The problem is that when you reload, the authenticated user won't become available until the application has talked to the FireBase server. Thankfully, FireBase will trigger an event when this is done and you can capture that event to push your user information to the $scope or $rootScope.
$rootScope.$on('$firebaseSimpleLogin:login', function (e, authUser) {
var ref = new Firebase(FIREBASE_URL + '/users/' + authUser.uid);
var user = $firebase(ref).$asObject();
user.$loaded().then(function() {
$rootScope.currentUser = user;
});
$location.path('/meetings');
});
I personally don't have a problem with storing the current user's info on the $rootScope. It's not very much data and you would probably have to feed it into the scope on every page...and that's exactly what $rootScope is for.
I have a 'messages' factory that will query my database for a list of messages.
I'm using the list of messages in two different places. Once to add a message count indicator, and then once to show a list of messages. Since I'm injecting the service into two different controllers, it seems like it's creating two instances of my factory, and hitting the database twice for the list.
How would I set things up to only ask for the list once, and use the list for both display and count purposes in both controllers?
My factory looks like this:
myApp.factory('messagesService', [
'$rootScope',
function($rootScope) {
var messages = [];
function query() {
// Would actually hit the database asyncronously
messages = ['one', 'two', 'three', 'four'];
console.log('query');
$rootScope.$emit('messages.update');
}
function all() {
return messages;
}
return {
query: query,
all: all
}
}
]);
My controllers are using blocks like this to watch for changes:
$rootScope.$on('messages.update', function() {
$scope.messagesCount = messagesService.all().length;
});
But it means i need a messagesService.query(); in each controller for things to be reliable.
So here are a few jsFiddle examples of it as I have things now:
Doesn't work (only updates the header): http://jsfiddle.net/TSLfc/1/
Works but would break if I didn't load the dashboard controller:
http://jsfiddle.net/TSLfc/2/
Works every time, but queries the server twice:
http://jsfiddle.net/TSLfc/3/
Is there a better way to organize my code? Should I build out the messages factory into it's own full module?
Here (Plunkr) is how I would do it:
I have gone back and modified my previous answer, updating with what we discussed in the comments below as well as using promises instead of the timeout as an asynchronous simulation I was showing before (see revision history for reference).
I also removed every variable/function that didn't need to be returned to the controller from the service object, if it doesn't need to be accessed via the controller than it doesn't need to be included on the returned object.
var myApp = angular.module('myApp', []);
myApp.factory('messagesService', [
'$q',
'$rootScope',
'$http',
function ($q, $rootScope, $http) {
var mService = {};
mService.messages = [];
var queryInit = false;
// We don't need to access this function in the controller
// So I am not going to attach to the returned object
var getMessages = function () {
// Stops each controller from getting messages when loaded
if (!queryInit) {
queryInit = true;
// Using the $q promise library we use 'then()' to handle
// What happens after the async call is returned
// The first function parameter is the success/resolve callback
// The second function parameter is the error/reject callback
mService.query().then(function (successResults) {
// Tell all of the controllers that the data has changed
$rootScope.$broadcast('messages.update');
}, function (errorResults) {
console.error(errorResults);
});
}
};
// Used to force an update from the controller if needed.
mService.query = function () {
var deferred = $q.defer();
$http.get('path/to/file.php')
.success(function (data, status, headers, config) {
// assign the returned values appropriately
mService.messages = data;
// this callback will be called asynchronously
// when the response is available
deferred.resolve(data);
})
.error(function (data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
deferred.reject(data);
});
return deferred.promise;
};
mService.getCount = function () {
return mService.messages.length;
};
mService.all = function () {
return mService.messages;
};
// Initialize the messages
// so we don't need to get the messages in each controller
getMessages();
return mService;
}]);
In your html, on your first controller setup an init function (ng-init="init()") that instantiates the factory:
<div ng-app="myApp">
<div ng-controller="HeaderCtrl" class="header" ng-init="init()">
Messages Count: {{ messageCount }}
</div>
<div ng-controller="DashboardCtrl" class="dashboard">
<ul ng-repeat="message in messages">
<li>{{ message }}</li>
</ul>
<button ng-click="getMessages()">Check for new messages.</button>
</div>
</div>
And in your controllers you just have the $rootScope.$on('messages.update' fn) and you can call manually by calling the services query() function which returns the promise:
myApp.controller('HeaderCtrl', [
'$scope',
'$rootScope',
'messagesService',
function ($scope, $rootScope, messagesService) {
$rootScope.$on('messages.update', function () {
$scope.messageCount = messagesService.getCount();
});
// Manual call, if needed
$scope.getMessageCount = function () {
messagesService.query().then(function (successCallback) {
$scope.messageCount = messagesService.getCount();
});
};
}]);
myApp.controller('DashboardCtrl', [
'$scope',
'$rootScope',
'messagesService',
function ($scope, $rootScope, messagesService) {
$rootScope.$on('messages.update', function () {
$scope.messages = messagesService.all();
});
// Manual call, if needed
$scope.getMessages = function () {
messagesService.query().then(function (successCallback) {
$scope.messages = messagesService.all();
$rootScope.$broadcast('messages.update');
});
}
}]);
You can set cache:true on a $http request. There are numerous ways to data bind within angular without needing to use the $broadcast approach you are using. Also note, $broadcast from a scope will be receievd by all descendent scopes, so no need to inject $rootSCope just for that purpose, can listen on $scope.
Here's one approach that controllers use promise of $http to retrieve data. I used a button click to retrive data for DashControl so can see that request does get cached
myApp.factory('messagesService',function($http) {
return{
query:function query(callback) {
/* return promise of the request*/
return $http.get('messages.json',{ cache:true}).then(function(res){
/* resolve what data to return, can set additional properties of the service here if desired*/
return res.data
}).then(callback);
}
}
});
myApp.controller('HeaderCtrl',function($scope, messagesService) {
messagesService.query(function(messages){
$scope.messagesCount = messages.length;
});
});
myApp.controller('DashboardCtrl', function($scope, messagesService) {
/* use button click to load same data, note in console no http request made*/
$scope.getMessages=function(){
messagesService.query(function(messages){
$scope.messages = messages;
})
}
});
Essentially in this scenario, whatever controller calls the factory service first will generate the data cache
DEMO
I would do it like that:
myApp.factory('messagesService', function() {
var expose = {
messages: []
};
expose.query = function () {
// Would actually hit the database asyncronously
expose.messages = ['one', 'two', 'three', 'four'];
console.log('query');
};
// Initialization
expose.query();
return expose;
}
);
And in your controllers:
$scope.messagesCount = messagesService.messages.length;
Model with broadcasting and pre-hitting database looks heavy for me.
So here is code, that can be embedded in service:
var sv = this;
var deferred = sv.$q.defer();
if (sv._running) {
return sv._running;
}
sv._running = deferred;
It based on reusing promise. To make it query database once - just don't set sv._running to false and it will always return first obtained result.
I know there are other similar questions on how to pass data between Angular controllers.
What I wonder is how to deal with this in a view..
Lets say I have a UserController for login, registration etc.
And an AppController for the actual app functionallity .
The UserController would be fairly easy, its sort of standalone from the rest.
But what if the app needs to know about stuff from the user controller?
Lets say the app view needs to hide/show stuff depending on if the user is logged in or not.
Or it could be if the user is male or female etc.
Should the app model keep its own copy of the user model state?
e.g. appModel.isLoggedIn , appModel.gender etc ?
feels a bit redundant, but at the same time more testable.
So what is the correct way to do this?
Short answer
Create a service, see Creating Services for details.
Long answer
Services are - per se - application-wide singletons, hence they are perfect for keeping state across views, controllers & co.:
app.factory('myService', [ function () {
'use strict';
return {
// Your service implementation goes here ...
};
}]);
Once you have written and registered your service, you can require it in your controllers using AngularJS' dependency injection feature:
app.controller('myController', [ 'myService', '$scope',
function (myService, $scope) {
'use strict';
// Your controller implementation goes here ...
}]);
Now, inside your controller you have the myService variable which contains the single instance of the service. There you can have a property isLoggedIn that represents whether your user is logged in or not.
To further specify the answer #GoloRoden gave, this is an example of how you can share state values across all controllers taking the service as a dependency.
App.factory('formState', formState);
function formState() {
var state = {};
var builder = "nope";
var search = "nope";
state.builder = function () {
return builder;
};
state.search = function () {
return search;
};
state.set = {
'builder': function (val) {
builder = val;
},
'search': function (val) {
search = val;
}
};
return {
getStateManager: function () {
return state;
}
};
}
App.controller('builderCtrl', builderCtrl);
builderCtrl.$inject = ['formState']
function builderCtrl(formState) {
var stateManager = formState.getStateManager();
activate();
function activate() {
console.log("setting val in builder");
stateManager.set.search("yeah, builder!");
console.log("reading search in builder: " + stateManager.search());
console.log("reading builder in builder: " + stateManager.builder());
}
}
App.controller('searchCtrl', searchCtrl);
searchCtrl.$inject = ['formState']
function searchCtrl(formState) {
var stateManager = formState.getStateManager();
activate();
function activate() {
console.log("setting val in search");
stateManager.set.search("yeah, search!");
console.log("reading search in search: " + stateManager.search());
console.log("reading builder in search: " + stateManager.builder());
}
}
I started developing in AngularJS. I'm confused as to whether this is a proper design to pass data between my partial views.
Right now I have a loader page where I do some request.
function PeopleController($scope,$http,$location){
$http.get('/location/-79.18925/43.77596').
success(function(data){
$scope.savePeopleResponse(data);
$location.url('/test');
});
}
Then in the view that gets loaded for /test
I am just calling
<div ng-controller="resultController">
<div class="blueitem">{{getResultForPeople()|json}}</div>
</div>
[resultController]
function resultController($scope){
$scope.getResultForPeople = function(){
return $scope.getPeopleResponse();
}
}
and the savePeopleResponse and getResultForPeople are "cached" in the rootScope as such
app.run(function($rootScope) {
var peopleResponse = {};
$rootScope.savePeopleResponse = function(data) {
peopleResponse = data;
console.log(data);
}
$rootScope.getPeopleResponse = function(){
return peopleResponse;
}
});
Now as you can see, this will get very messy if this application grows larger and larger. What's the best way to handle data so that it's persisted across controllers?
You can persist data across controllers by creating your own service as described nicely in this blog. You can also refer to this question.
In your case you can move your savePeopleResponse and getPeopleResponse into a service and then inject the service into any controllers you would like to access it.
angular.module('myApp', [])
.factory('peopleService', function () {
var peopleResponse = {};
return {
savePeopleResponse:function (data) {
peopleResponse = data;
console.log(data);
},
getPeopleResponse:function () {
return peopleResponse;
}
};
});
With your controller something like this:
function resultController ($scope, peopleService) {
$scope.getResultForPeople = peopleService.getPeopleResponse;
}
With this code example make sure you include ng-app="myApp"