I'm writing some small exercises to teach myself AngularJS and I'm trying to write some simple user Authorisation tasks. I have a form to collect/input a username and password, these are then sent to a rest service using $http and CORS (as my REST service is running on a different port), they are checked and if there is a match I return a UUID and create a token and I $broadcast a loggedIn value to true that is on the $rootScope, something like this.
// this is in a service I call 'authService'
this.login = function (user) {
return $http({method: 'POST', url: 'http://127.0.0.1:3000/login', data: user})
.then(function (response) {
// set up a local storage token
storageService.setLocalStorage('token', response.data[0].uuid);
// broadCast is loggedIn - we have a match
$rootScope.loggedInUser = true; // this is set to false at the .run() of the app
$rootScope.$broadcast('LoggedIn');
return 1;
}, function () {
// return http code later
return 0;
});
};
this.getLoggedIn = function () {
return $rootScope.loggedInUser;
};
Now in a separate menu view I have the following condition (the authService is added as a dependancy on the menu controller):
<div id="logIn" ng-show="!authService.getLoggedIn()">...</div>
Now when I load the app for the first time the condition is correct, however I want this condition to update should a user log in correctly (so the div) isn't shown. In the menu controller I have the following code, none of it seems to do anything?
$scope.$on('LoggedIn', function () {
authService.getLoggedIn(); // doesn't update the view?
console.log($rootScope.loggedInUser); // returns true
console.log(authService.getLoggedIn()); // returns true
});
$scope.$watch('loggedInUser', function () {
console.log('loggedInUser has changed ' + $rootScope.loggedInUser);
// This runs once when we set $rootScope.loggedInUser in the .run() of the app, output is: 'loggedInUser has changed false'
// then when we have successfully logged in again, output is 'loggedInUser has changed true'
});
Okay, so the condition on the <div> in my menu view doesn't update when I changed the $rootScope.loggedInUser, I'm doing something wrong in my approach, can someone give me some advice or correct my approach to this. Thanks
You don't have to do anything special in Angular to update view when some prop is updated, provided it is in the correct scope. I provided a Plunkr for you which demonstrates that you don't have to do anything special to refresh the view.
http://plnkr.co/edit/B6kUwJdA4lRKkjAItSFO?p=preview
You don't have to do watches, you don't have to do anything. That is the power of Angular. Also, it is weird that you set stuff in rootscope. My advice for you is to look at my example and revise/restructure your code. Also, having stuff like this:
ng-show="!authService.getLoggedIn()"
Is not the recommended way of doing things. You can have a controller, in which you say:
$scope.userLoggedIn = autService.getLoggedIn();
and then in your view:
ng-show="userLoggedIn"
You can also take a look at this plunkr:
http://plnkr.co/edit/aRhS0h7BgpJJeRNvnosQ?p=preview
Related
https://cask.scotch.io/2014/10/V70cSEC.png
^ According to this flow, I have to make a change in the store, which is then picked up by the view.
More specifically, I am trying to delete a user, but when the store gets an error from the DB, I want to show a modal saying the error occurred. Would the right way of transmitting the message be done through the store variables and then picked up in the view on the getStateFromFlux method?
userStore = {
initialize: function(options) {
// other variables
this.userDeletionError = false;
},
deleteUser: function(payload) {
Axios.delete(DBURL)
.then((response) => {
// succeeds
})
.catch((error) => {
// other error handling
this.userDeletionError = true;
});
}
}
If I understand well you are using an ajax call in a store, this is an antipattern. The right way to do is make the call in the action file then transmit it in the store.
To answer your question the flux-pattern should look like that (not sure if it match 100% your use case).
View => User want to delete a 'user', he clicks on the delete button
View triggers an action.
Action triggers an ajax call 'delete this user'
Action receives the answer and transmit it to the store (here you are using the react dispatcher , example below:
MyAjaxCall.then(function(answer) {
Dispatcher.handleViewAction({
actionType: Constants.ActionTypes.DELETE_USER,
result: answer
});
});
5.Your store is catching the ajax answer still through the dispatcher (example below:
MyStore.dispatcherIndex = Dispatcher.register(function(payload) {
var action = payload.action;
var result;
switch(action.actionType) {
case Constants.ActionTypes.USER_DELETE:
registerAnswer(action.result);
MyStore.emitChange();
break;
}
return true;
});
You can see that your store will trigger registerAnswer(), in this function you can check if the ajaxcall has been executed (I mean is the user deleted or not) and accordingly build the object. Here there is two way to tell your view about the answer status 1. you build an dataAnswer object with a field message for example and then your view can check it 2. you emit a special event.
I prefer the first way if find it more generic.
Store emitChange and your view catch the event (example below:
componentDidMount: function() {
MyStore.addChangeListener(address, this._onDeleteUser;
},
Then your view check the 'message' field you filled in the store accordingly to the answer and you can render whatever is appropriate.
I hope it's clear. Here is an example of store in case you need it. https://facebook.github.io/flux/docs/todo-list.html#creating-stores
To resume, your approach is good except doing the ajax call in the store. Don't do that it's really bad.
Hope it helps
So this is my problem.
I can successfully login from my angularJS app using the auth factory i made that communicates to my pp rest API.
lets say Auth.login(user) -> (POST) myapi.com/user/login: the response is the user object that Auth saves locally. Thus Auth.getCurrentUser() returns local user object.
my rest API, i also have a myapi.com/user/get_loggedin_user which returns the current logged in user (using the php session). So if Auth.getCurrentUser should actually check if local user exists, if not do an ajax to myapi.com/user/get_loggedin_user and check if logged in before responding with null. One problem here is, ajax is annoying like this, you would then need to put in a success callback function and have all your code execute inside the success callback.
Now lets say im on the Angular App (website), mydomain.com/user/dashboard (already logged in), and then i refresh my browser. Now, when the page reloads, angular does not know my current user, so before it redirects me to mydomain/login, i want it to check if the user is logged in. i can obviously do a 1 time call within the controller, but is there a more easy way where i can register within a controller with some access restrictions (Eg: logged_in == true), and when you visit any page with logged in requirement, it checks local user (gets the user if does not exist), and redirects to login page if null, or display the page once it matches the requirements?
Different common page requirements: null, logged_in, admin, function: haveAccess(user, object).
NOTE: im using stateProvider
If I understood your question correctly, you are asking about how to check whether the user is logged in before the controller is invoked, and to avoid the check for a logged-in status in each controller that needs it.
If so, you should look into the resolve parameter - this exists both in $routerProvider and $stateProvide.
Essentially you could "resolve" your loggedInUser variable (by doing whatever you need to do via your MyAuth service.
Here's an example of what I mean with $routeProvider:
$routeProvider
.when("/someSecuredContent", {
templateUrl: 'someSecuredContent.html',
controller: 'SecuredController',
resolve: {
loggedInUser: function(MyAuth){
return MyAuth.loggedIn(); // MyAuth.loggedIn() should return a $q promise
}
}
});
Then in the controller, loggedInUser will be injected.
Here's a site with more examples.
Correct me if im wrong:
Do this within the Main Controller (make sure you inject the dependancies like rootScope, state, and your own Authfactory)
$rootScope.$on('$stateChangeStart', function (event, next, toParams) {
if (needToBeLoggedIn()) { //use the next object to read any data, and you can set on the state some flag
event.preventDefault()
MyAuth.loggedIn(function success(){ $state.go(next,toParams); }, function err (){/*send somewhere else*/});
}
})
Put logged_in = true to cookieStore in your login method after authentication as below.
$cookieStore.put('logged_in',true);
$rootScope.logged_in = true;
and in your Controller, do
$rootScope.logged_in = $cookieStore.get('logged_in');
Now you can use this logged_in variable anywhere in the UI to check if the user is logged in.
Make sure to use 'ngCookies' module in your app. and pass the $cookieStore dependency to your controller. You can even keep the user object itself similar to logged_in variable in cookies and retrieve it from cookies.
Make sure to do logged_in = false and clear other variables in cookies and set it to blank in your logout method.
I have following code in a controller. This code get the current user's task list.
I want to pack this into a angular service so I can call this easily whenever I want in any controller. example Task.getList()
In Controller
$scope.tasks = {};
$firebaseSimpleLogin(instance).$getCurrentUser().then(function(user) {
$firebase(instance).$child('users/' + user.uid + '/tasks/incomplete').$on('child_added', function(taskId) {
$scope.tasks[taskId.snapshot.name] = $firebase(instance).$child('todos/' + taskId.snapshot.name);
});
});
but I facing a problem here. When I call it in controller. it always returns undefined
In services
getList: function() {
$firebaseSimpleLogin(instance).$getCurrentUser().then(function(user) {
$firebase(instance).$child('users/' + user.uid + '/tasks/incomplete').$on('child_added', function(taskId) {
return $firebase(instance).$child('todos/' + taskId.snapshot.name);
});
});
}
You can simplify this problem by breaking things down into smaller pieces. You're trying to return from an inner callback, which is really tricky to deal with. Rather than trying to get everything all loaded in at once, we can separate out these responsibilities.
A nice way of handling this is to separate out the auth code ($getCurrentUser), and the task code (getList).
Here's a sample to a Plunker app I wrote. It will load the tasks for each user who logs in.
http://plnkr.co/edit/M0UJmm?p=preview
I have an Auth factory that handles my Auth code.
.factory('Auth', function($firebaseSimpleLogin, Fb, $rootScope) {
var simpleLogin = $firebaseSimpleLogin(Fb);
return {
getCurrentUser: function() {
return simpleLogin.$getCurrentUser();
},
// see plunker for the rest
};
})
I also have a TaskStore that handles adding, removing, and syncing of my user's tasks. The store takes in a user's id to initialize. It will then know how to grab the tasks listed by the user.
Then in my controller I can use these together to load a user's tasks. Inside of the promise returned by the $getCurrentUser function, the TaskStore with the user's id can get initialized.
This way my TaskStore is re-useable and independent of my Auth code and a bit easier to manage.
I have a simple SPA with two views: a list view and a detail view. I use a service called StateService to pass data between the two controllers.
I am trying to handle the case where the user refreshes the browser page--when this happens, the StateService gets reinitialized and the detail view can no longer work. I want to detect when this happens and return the user to the list view.
Here is a simplified version of my State Service. The idea is that I would set isInitialized to true when I switch to the detail view so that I can detect when the service has not been properly initialized.
var StateService = function () {
var isInitialized = false;
};
This is what I have tried in the first few lines of my controller. The StateService is being successfully injected into the controller.
//always returns [Object], on refresh or navigating from list page
alert(StateService);
// this next line always returns undefined. Should be false since I am initializing
// the value to false?
alert(StateService.isInitialized);
//One of the many combinations I have tried . . .
if (!StateService.isInitialized | StateService.isInitialized == false) {
$location.path('/');
}
I don't know if this is a gap in my understanding of javascript or angular, but any thoughts on how I can get the above code to work, or better ideas on what to do when a user refreshes the page?
Edit
Using console.log as recommended by nycynik I see the following:
c {} [StateService]
undefined [StateService.isInitialized]
So it seems that StateService itself is just an empty object when this code gets hit. I get the same results from my other controller (the one that handles the list view).
As noted in the comments, the service seems to otherwise work as expected.
I think you have a problem with scoping. variables in javascript have function scope.
isInitialized is scoped only to your StateService Function, so you can't get at it outside of your StateService Function.
not sure exactly how you're getting this thing into your controller, but maybe these help:
if you're using an angular's module.service() to use StateService as a constructor to inject a (new StateService) into your controller then you need to set isInitialized on the instance
var StateService = function () {
this.isInitialized = false;
};
This way (new StateService).isInitialized === false
If you are just using module.factory() or something else that doesn't use new, then you need to put your isInitialized value somewhere else you can actually get at it.
var StateService = function () {
};
StateService.isInitialized = false
Hope that helps.
I have a simple app that lists contact reports,
in it i made a list view that fetches data from Mongolab.
On that i also made an input form that makes a new contact report in the list when submitted
the function i use in the controller is modelled from angular's example on their site :
app.factory('Contact',function($mongolabResource){
return $mongolabResource('contacts');
});
function ContactCreateCtrl($scope,$location,Contact) {
Contact.save(contact,function(){
$location.path('/');
});
};
the $location.path() is the callback that reloads the page.
how do i rewrite this so that when the data has been submitted ( .save() is successful ) the view reloads without the page reloading?
i tried deleting and then redefining the array but doesnt seem to work :
Contact.save(contact,function(){
delete $scope.contacts;
$scope.contacts = Contact.query();
});
i would like to implement this on the delete function as well. Can somebody point me to where i can learn this?
Much thanks for any help
Okay, I updated your fiddle to fetch the value from the database: http://jsfiddle.net/joshdmiller/Y223F/2/.
app.controller( 'MainCtrl', function ( $scope,Contact ) {
$scope.updateContacts = function () {
Contact.query( function( data ) {
$scope.contacts = data;
});
};
$scope.save = function( newContact ) {
Contact.save( newContact, function() {
$scope.updateContacts();
});
};
// The initial data load
$scope.updateContacts();
});
Two things to note:
(1) I moved your Mongo query into a function so that it can be called again when the new record is created;
(2) the $mongolabResource expects a callback to be executed on success; your app flickered because you didn't provide one. In other words, from the time you called the query to the time fetch was complete, your list was empty. Instead, we want it to change only when we get the new data. I changed that too.
In terms of adding the item manually or fetching from the database, best practice is based on the use case and there are trade-offs. But for small data such as this, just fetch from the DB.
got this to work, but still not sure about pushing into the array at the scope, would be better if we could fetch from database
function ContactCreateCtrl($scope,$location,Contact) {
Contact.save(contact,function(){
$scope.contacts.push(contact);
});
also i would need the _id object thats automatically generated by the db, for linking purposes. this method doesnt give me the _id, any insights?
I'm sharing my answer for how I cleared data in the view after sign out from firebase using the firebase auth service. The data was still persisting after calling $scope.currentUser = null; on the signout method. Had to reload to see the data change. Not best UX.
$scope.getCurrentUser = function() {
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
$scope.currentUser = user;
} else {
// No user is signed in.
$scope.currentUser = null;
console.log('user not signed in.');
}
});
}
$scope.getCurrentUser();
$scope.signout = function() {
firebase.auth().signOut().then(function() {
// Sign-out successful.
console.log('signed out success');
$scope.getCurrentUser();
}, function(error) {
// An error happened.
console.log(error);
});
}
so calling the getUserData method and making the currentUser = null there updated the view without a reload. this is an example using firebase but with a few adjustments it might apply to your needs for clearing data from view without a full page reload. Firebase does the heavy lifting here of clearing out the user object but my view doesn't care until I check again in my getCurrentUser method to see if there is still a user and if not then clear it from the $scope without reloading the view.