I am testing my Angular application that uses ui.router and authentication token. There is a partial page that is available only to authorized users. Here is the steps to reproduce:
1) Login
2) Navigate to partial user profile page
3) Logout
it deletes authentication header like this:
delete $http.defaults.headers.common['x-access-token'];
then navigates to a different page
4) In the browser address bar manually navigate back to the profile page.
browser sends http request with the token I just removed! I can see it in Chrome Network view.
5) Subsequent requests to the profile page are issued without the token and gets rejected
This problem does not happen in Safari.
This is actually a known problem with Angular JS caching templates. Sadly there seems to be no solution for it. '$templateCache.removeAll()' does not work for me. There are numerous posts about it:
AngularJS disable partial caching on dev machine
Remove Template cache on logout Angular.js
How to refresh / invalidate $resource cache in AngularJS
P.S. Actually, there is a solution that worked:
http://opensourcesoftwareandme.blogspot.com/2014/02/safely-prevent-template-caching-in-angularjs.html
Slightly modified version looks like this:
$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams) {
if (typeof(fromState.templateUrl) !== 'undefined'){
$templateCache.remove(fromState.templateUrl);
}
});
After some more research I found even better solution. '$templateCache.removeAll()' actually does work, except that templates are still kept somewhere and to make sure they are updated I need to reload current state:
$templateCache.removeAll();
$state.transitionTo($state.current, $stateParams, {
reload: true,
inherit: false,
notify: true
});
This way I do not need to turn off the cache completely, but can just wipe it out whenever a user is logged in or out.
Related
I'm having trouble getting a response from server side NodeJS code after checking out using PayPal In-Context via the paypal-rest-sdk.
I have a PayPal form defined in html, and purposefully did not define the action. I only defined the id and method (get). In my Angular code, I define the action when the PayPal button is clicked:
var payPalForm = document.getElementById('payPalForm');
payPalForm.addEventListener("click", function(e) {
payPalForm.setAttribute('action', "https://10.0.0.25:8001/checkout/" +
$scope.user._id + "/" + $scope.viewEvent._id.valueOf() + "/" +
$scope.viewEvent.eventName + "/" + $scope.viewEvent.price);
});
By doing this, I am able to successfully checkout via PayPal In-Context.
In my NodeJS code, after successful checkout, instead of return res.end(); if successful or res.sendStatus(400, 'bad paypal payment'); if failure, I would like to pass the status back to the Angular, and handle it there.
To reiterate: I do not want to define the post PayPal In-Context route in NodeJS, I would like to do it in Angular.
I need to do this being I am using Onsen UI, and am using a splitter to navigate between pages.
Has anyone successfully done this?
UPDATE AFTER ATTEMPTING THE FIRST ANSWER
I have tried this before, but after attempting to implement Ilia Sky's Answer, the result is the PayPal In-Context checkout does not execute correctly. The script runs correctly (as determined by output in the terminal), but the In-Context window loads with "?" as a parameter and then immediately closes itself. Here are screenshots on how I updated my code. The commented out code is what I had when I was able to successfully checkout, but unable to redirect properly:
I think this is an issue with PayPal, and I'm not sure how to solve it.
This is why I'm wondering if I can listen for a redirect in Angular, and then load a different page when a certain redirect is identified.
I tried this in my Angular controllers, but it only ever executed when the initial page loaded:
I may be wrong, but from what I understand what you want to do is:
Send data to the server (to https://10.0.0.25:8001/checkout/...)
Do something on the server and give a response (res.send etc)
Handle the response on the client
Right?
To do this the easiest solution would be just to send an ajax request to the server and then handle the response.
This means instead of changing the action attribute you can directly make the request via js. With angular that would be done using the $http service.
$http.post('https://10.0...').then(successCallback, errorCallback);
And in success|errorCallback you can do whatever you want such as load a page in the splitter or whatever you want :)
To change what happens when you try to submit the form you can check out ng-submit.
And to get $http you can just add it to your list of arguments for your controller.
Example
app.controller('ExampleController', function($scope, $http) {
$scope.submit = function() {
$http.post('your/long/url').then(function(response) {
mySplitter.content.load('success-page.html');
}, function(response) {
mySplitter.content.load('error-page.html');
});
};
}]);
<form ng-submit="submit()" ng-controller="ExampleController">
...
</form>
I encourage everyone to check out this link.
One of the PayPal developers has posted a sample Kraken with Angular example of the PayPal In-Context Checkout.
Well, I come from an ngRoute background, and I just started to learn uiRouter.
I used to write two different angular app one for login (login.html), and one for the main app (index.html). In he back-end, I just check if the user is authenticated then redirect them to index.html, else to login.html.
Now I want to encapsulate login into the main app. I have a lot of states requiring user to be logged in. In a lot of them some parts like header and footer are fixed. I'm looking for a concrete solution to handle it with these considerations:
I don't want to use resolve property for all of the states which need authentication.
I don't want my routes to be like /some-prefix-for-authed-paths/... for states that require authentication.
If somebody navigates to a route which requires authentication, I want to redirect them to login page and after login I want to redirect them back into where ever they were.
for 4xx errors, I want to display a custom error page(s) (probably route-dependent). For example if someone navigates to /users/3, and there is no user 3, (which will be determined by the resolve property of the state which will be rejected in case of not-existing user) I want to show them error page, but I DON'T want to change route to something like /error or something, because it actually doesn't make sense at all! Plus, I don't want a state push in the browser history.
You could try to implement an httpInterceptor that catches all non 2xx requests and redirect to some sort of error/login page.
Also to check for authentication you could use uiRouter onStateChange event by adding an access level to your states
.state('someState', {
url: '/someUrl',
isPrivate: true
})
$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams){
event.preventDefault();
// Check if state is private
if(toState.isPrivate) {
// Do something
}
});
Note! This is just somewhat pseudo code
In my angularjs mobile app, I need the certain requirements. Initially, the app is in the home page. If the user needs to go to the next page in that app, he has to click on that certain content. And it will redirect them to the next page of that particular content. After that, If he wants to go to the previous(back) page, he has to click on the back button.
What I need is, when user is going back to the previous page where he was in, that page is reloading. Instead of reloading that previous page, he should be taken to the same place where he was before. So, how to disable the reload. And I think it can be achieved using 'ui.router' and its $stateChangeStart. For url routing I use '$routeProvider'. So how to achieve this one? Please help me out.
For homepage the url is -
'/myapp/landingpage'
For the next page (when he clicks on the particular content) the url is -
'myapp/content/:contentId'
If you home page is a plain static page - then nothing is required - the template for the home page is already cached in the $templateCache.
Else if your home page has some data that is fetched from some service, I would suggest you to cache the data in your angularjs service layer itself.
You can explore $cacheFactory for more details.
If you do not want to use $cacheFactory directly, you can simply use 'cahce:true' property in yout $http.get request :-
$http.get(url, { cache: true}).success(...);
From the $http docs:-
cache – {boolean|Cache} – If true, a default $http cache will be used
to cache the GET request, otherwise if a cache instance built with
$cacheFactory, this cache will be used for caching.
The idea is not to mess up view/states for caching and move that responsibility to your service layer.
When a use visits a private page unauthorized, say profile, my backend 302 redirects to a controller action that serves up the login partial in place of the profile partial. Since it 302 redirects to an action that returns a partial, the url address bar doesn't change from the page the user was trying to access ("/profile").
I was going to "fix" that but actually I think it makes a good user experience instead of dealing with return urls as query params.
The idea is once they log in I just want to reload the current route aka do a GET request for the profile partial via "/profile" and switch it back in instead of the login partial.
However, I can't get this "reload current route" to work. I tried all of the following:
$location.$$compose();
$location.absUrl($location.path());
$location.url($location.path());
$location.path($location.path())
$route.reload();
But none work. $route.reload() seems to be the definite way but it also doesn't work. It goes through the route cycle, reinstantiates the controller, but does not do GET request to reload the template
The only thing that works is a hard refresh via location.reload() but that is not ideal.
How can I force angular to reload the template for the current route?
Ok I found the solution provided by lgalfaso on Github (exact paste):
Templates are
cached, if a user does not have the permissions to be in a page, then
this check should be done before it reaches the controller or after,
within the controller, but not on the template retrieval
If this is the way you want to follow, then you need to remove the
template from the $templateCache before you call reload
So that worked for me because login template actually gets cached as the template the user was trying to access. So removing it and letting angular re-fetch the correct one for the current route worked like a charm.
var currentPageTemplate = $route.current.templateUrl;
$templateCache.remove(currentPageTemplate);
$route.reload();
I have noticed that $route.reload() method re-instantiates everything that is setup on your $routeProvider.when("/someUrl",{controller:'SomeController',templateUrl:'SomeView.html'}) template,controller and/or any resolved promises you may have passed within the .when() method.
Therefore; when you build your app, if you want $route.reload() to reload all the page and re-instantiate controllers you must put everything under your <div ng-view></div> container and include any menus or footers inside the templateUrl file.
I have a login form and now i would like to make an integration test to see if user is redirected to the other page after successful login.
Behind the curtains there is authorization service that fires a page reload via $window.location.href = 'home';
First i had this:
it('should redirect to /home if credentials are correct', function(){
browser().navigateTo('/login');
input('credentials.username').enter('testuser');
input('credentials.password').enter('testpass');
element('button.login').click();
expect(browser().location().path()).toBe("/home");
});
but AngularJS test failed both - run as Karma and as runner in browser. Then i thought that maybe expect is too soon and i added sleep(1) before it. And then it was fine and in test runner I could see the page refresh before assertion.
I assume it might work better if i had location().path('/home') but i prefer full reload at this step.
Is there any design/test pattern that should be used in such cases so i don't have to put sleep() before expected location change?
You can try this code;
it('should redirect to /home if credentials are correct', function(){
browser().navigateTo('/login');
input('credentials.username').enter('testuser');
input('credentials.password').enter('testpass');
element('button.login').click();
browser().navigateTo('/home');
});