Angular adding extra logic to 404 handling on non angular routes - angularjs

I have an angular site hosted in S3, and I have a 404 route set up (I use hash), if someone for example does
mysite/#/gibberish
goes to
mysite/#/404
and on the s3 bucket we have a redirect rule in place for
mysite/gibberish
goes to
mysite/404.html
all is well
Now I just want to add an extra logic on top that if someone types in
mysite/customerid
which is a 404 to somehow redirect this to an angular controller so I can send this request to right page.
So somehow in my redirect in S3 rule add a reg exp for some incoming request and rather than serve 404.html send it i.e. mysite/#/handlethis
Is this possible ?

Depending on the router of your choice, you could do something like the following (which is what we've done (well, not precisely this, but close)):
ui-router
app.config(function ($urlRouterProvider) {
var regx = /\/#\//; // match against /#/
$urlRouterProvider.otherwise(function ($state, $location) {
if (!regx.test($location.path()) { // if no match
$state.go('customHandlingState', /** params **/, /** configuration **/ });
// Transition to your custom handler state, with optional params/config.
}
});
});
You could pair this up with custom stateChange[Start|Stop|Error|Success] handlers in the run block of your app to customise it to your liking.
I would supply an example of how to do this with ngRoute, but I gave up on ngRoute two years ago and haven't looked back since. As such I have no suggestion to give, nor was I able to find a solution to the problem you present.
I would strongly suggest you scrap the S3 portion of this recipe as it will make your life a lot easier when it comes to client side routing (speaking from personal experience here, it's my opinion on the matter - not fact) and handle your 404's/500's on the client with custom state handlers.
If need be you could hook into some logging service and store some data whenever a client/person ends up in an erroneous state.
I suppose my 'counter question' is; What do you gain from using S3 redirect rules? So as to get a better understanding for the needs and goals here.
Some reference material to go along:
ui-router#$state.go
ui-router#$urlRouterProvider.otherwise

I would suggest using routeParams
https://docs.angularjs.org/api/ngRoute/service/$routeParams
the route would look like this:
/mysite/:cid
then access the id with the controller:
$routeParams.cid
I hope this could help

You can manually configure your server to always serve your index.html(your main html file which includes reference to angular script) on all incoming http requests. Client routing will be handled by Angular

Related

AngularJS and NodeJS http-server: rewrite URL

I'm using NodeJS http-server to code an AngularJS App.
I'm having a problem. When I try to access directly the url in the browser the Angular does not intercept the URL to show me the content.
If I type the URL manually like: http://127.0.0.1:8080/#!/clients it works, but not when I type directly: http://127.0.0.1:8080/clients
I want the http://127.0.0.1:8080/#! as default in the http-server.
I'm using in AngularJS the html5 mode and the hash prefix:
$locationProvider.html5Mode(true);
$locationProvider.hashPrefix('!');
Is there a way to rewrite the url from http-server with the default /#!/ before the address?
Note: Below is an example of a more complex Express.js URL rewriting situation, where you may not wish to "catch all" routes, but instead discern between routes for views and routes for the server-side api. All solutions I could find only showed a generic catch-all approach, which did not fit practical application for an app whom requires access to server-side routes. If you do simply want the "catch all", see the other yellow note at the bottom of this answer for links on how to set that up.
If you turn off html5 mode, by default, you should be in hashbang (#!) mode...Turning on html5Mode allows you to remove the hashbang (#!), if you wish.
Here's more about the different modes:
https://docs.angularjs.org/guide/$location
HTML5 mode being enabled, gives you normal looking, non-hashbang URLs. Normally, when HTML5 mode is enabled, you'll hit the issue you described where pages within your app will load OK internally, but if you try and enter the URL directly into your browser (or access it via a bookmark), you'll get a Cannot GET error.
First, be sure you've set a <base> tag in the <head> your primary index file, like this:
<head>
<!-- all your script tags, etc etc -->
<base href="/">
<!-- rest of your front-end dependencies etc -->
</head>
That way Angular will know which is your primary index to load partials within.
Secondly, I will try and tell you how I approached re-writing my URLs in Express to solve the issue you have described. My answer may be somewhat incomplete, as I am still learning, and in truth, I do not fully understand why, once HTML5 mode is enabled in Angular, that the routing does not work properly. There also may be better ways to approach the problem as opposed to how I solved mine.
It seemed that once I switched to HTML5 mode, Angular intercepted my routes and was causing an issue when I was trying to use the $http service to request server-side api routes.
It seemed like turning on HTML5 mode basically took over all of my routing, and I had to find a way to tell Express to either pass a route to Angular or to continue the route (away from angular) using next(). This was my best assessment.
What I Did:
Enabled HTML5 mode [as you have done in your example], and set a
<base> in my index file (as noted above).
Rewrote my routing in ExpressJS using the native express.Router():
See: https://expressjs.com/en/guide/routing.html
At the very bottom of that page are instructions for express.Router()
I'll show you how I did it below
My Approach/Pseudo-code:
I setup a method in my Router so that if the incoming request contained /api/ (checked via regex), I would invoke ExpressJS's next() method and continue along in the route, which would hit the server controller. Otherwise, if the URL did not contain /api/, I the appropriate view page was delivered, in which Angular took over.
How I setup my express.Router():
I created a middleware folder in my app and created a file called api-check.js.
// Setup any Dependencies:
var express = require('express'),
router = express.Router(), // very important!
path = require('path');
// Setup Router Middleware:
/*
Notes: The `apiChecker` function below will run any time a request is made.
The API Checker will do a regex comparison to see if the request URL contained
the `/api/` pattern, to which instead of serving the HTML file for HTML5 mode,
it will instead `next()` along so the API route can reach the appropriate
server-side controller.
*/
router.use(function apiChecker (req, res, next) {
console.log('/// ROUTER RUNNING ///'); // will print on every route
console.log('URL:', req.originalUrl); // will show requested URL
var regex = /(\/api\/)/g; // pattern which checks for `/api/` in the URL
if (regex.test(req.originalUrl)) { // if the URL contains the pattern, then `next()`
console.log('THIS IS AN API REQUEST'); // api detected
next();
} else { // if the URL does not contain `/api`:
res.sendFile(path.join(__dirname, './../../client/index.html')); // delivers index.html which angular-route will then load appropriate partial
}
})
module.exports = router; // exports router (which now has my apiChecked method attached to it)
How I added my express.Router() to my Express App:
Depending upon how your code is modularized, etc, just go ahead and in the right place require your module (you will have to adjust the direct path depending upon your project), and then app.use() the module to intercept all of your routes and direct them to your Router:
// Get Router:
var apiCheck = require('./../middleware/api-check');
// Use Router to intercept all routes:
app.use('/*', apiCheck);
Now, any route (thus the /*) will go through the express.Router() apiChecker() function, and be assessed. If the requesting URL contains /api, then next() will be invoked and the server-side controller will be reached. Otherwise, if the /api slug is not detected in the URL, then the primary index.html base file will be sent, so that Angular can deliver the appropriate view via $routeProvider.
Note: If you don't need to discern between incoming routes, and just want to "catch all" incoming routes and hand back your <base> index file, you can do as outlined in another stackoverflow answer here. That answer uses app.get() to catch all GET requests to hand back your index. If you also need to catch POST requests or others, you may want to instead use app.all(), in place of app.get() in the aforementioned example. This will catch all routes, whether GET, POST, etc. Read more in the Express documentation here.
This was my personal solution, and there may be better, but this solved my problem! Would be interested to know what others recommend! Of course the downside to this, is that I have to build all of my internal api routes to include /api/ in them, however that seems to be OK in design overall, and maybe even useful in keeping me from confusing my routes from front-side views.
Hope this at least helps somewhat, let me know if you need any clarifications :)

How to deal with extra hash in route? (AngularJS 1.5 + new/component router)

We're attempting to build an app using Angular 1.5 with the new component router bits. We've run into a bit of an edge case and we're wondering if there's any way around it.
The Key Players
IdentityServer v2: our client uses this for OAuth currently. It causes a part of this problem. It's legacy, and we don't have control over its usage.
AngularJS 1.5 as our front-end framework.
The new angular router, called ngComponentRouter now I believe? We figured this style would help us bridge between Angular v1.5 and Angular v2, and it was easy enough to port.
oauth-ng as a wrapper for our OAuth implicit flow..
Older browsers: In the sense that we have to support IE9+, meaning we can't use Angular's HTML5 mode.
The Goal
We'd like to take a URL such as http://mysite/#!/auth/#auth_token=xyz123 (structure not under our control, e.g. can't remove second hash) and:
Get it into an actual auth controller
Have the auth_token value available, either through parameters or directly through $location. (it currently is scrubbed before it ever gets to the controller).
Background / Problem
Our client has a central login system where they're using IdentityServer v2. As far as I understand, when we request a token from IdSrv v2, it responds by appending #auth_token=xyz123 to your redirect URL. It was written back when it thought you'd have my.com/login.html, thus resulting in login.html#auth_token=xyz123.
With an Angular app that uses a hash already, though, it becomes a problem, as the URL ends up along the lines of mysite.com/#/auth#auth_token=xyz123.
This, as you might expect, makes Angular angry. We have yet be able to find a way to get this to work under the component router.
How it Would Work With the Older Routers
Per the oauth-ng docs, if we were using the older router without html5 enabled, we'd do something like the following:
angular.module('app').config(function ($routeProvider) {
$routeProvider
.when('/access_token=:accessToken', {
template: '',
controller: function ($location, AccessToken) {
var hash = $location.path().substr(1);
AccessToken.setTokenFromString(hash);
$location.path('/');
$location.replace();
}
})
What We've Tried
Defining a component route in a similar way. This didn't work, because /access_token=:accessToken contains an =, which doesn't appear to be allowed by component router.
Seeing if we can get IdentityServer v2 to change the format of the response. It doesn't seem like it's possible; the response seems to be hard-coded to [URL we define]#auth_token=xyz123.
Faking out the URL using other hashes, etc. Generally wound up with bad / inconsistent behavior.
What We Think our Options Are
Use a catch-all / not found controller. If we let the route fall all the way through to /**, we can retrieve the token value from $location. That's sort of gross though; we'd like to avoid it.
Find a way to get the full URL into the controller. We can capture the route and put it through to a controller, but the URL isn't available at that point.
Go back to using the older router or ui-router (which we'd like not to do at this point).
Anything that could point us in the right direction would be greatly appreciated!
To follow up on this: in the end, we decided that this was a strange enough edge-case that it warranted returning to ui-router.
While we think the component router makes the most sense, the deciding factor here is that, unfortunately, we don't have 100% control over our routes. The route constraint included the edge case that component-router did not seem capable of handling at the current time.
For those who are working with older oauth server systems, I hope this will serve as a warning / some background as you're making your choice of router.
Hopefully ngComponentRouter will better support this edge case in the future, though I wouldn't blame them for leaving it out.

Angularjs with subdomain wildcards (best way)

guys.How is it going? I am having a hard time trying to figure out some stuff here. No success so far. The thing is...
How does Angularjs work with subdomain wildcards?
Since I can't inject location service in the app.config, I needed some way to track the url I am accessing in every request, without having to check when every single angularjs controller is loaded, using $routeProvider and locationProvider
* UPDATE *
I have a domain for a Rails 4 application(as an API), and I am using angularjs to render the views and everything else. For the app, I will have a subdomain for a specific purpose. But users will only be able to access the subdomain after logged in, and this will create a subsubdomain for them. So, I need to do something like this:
http://domain.com (rendering the regular app)
http://subdomain.domain.com (only to log in)
http://user1.subdomain.domain.com
http://user2.subdomain.domain.com (both rendering something completely different from the regular app rendering)
All this using routeParams. I used angular-devise to login.
Any ideas?
Thanks in advance!
If someone is trying to figure out the same thing, here is what I did:
In the routes.rb file, I pointed routes with subdomains to a rails controller:
get '', to: 'controller#action', constraints: { subdomain: /.+/ }
I created to angular modules. One for the regular app, and the other for to the app with the subdomain
In the application.html.erb file, I make the ng-app dynamic, so angular will be able to know which app I am talking about. angular_app returns the string to one of the angular_modules:
ng-app="<%= angular_app %>"
After that, angularjs routeParams is in charge to render the app.
Thank was it.

Passport.js, Express.js, and Angular.js routing: how can they coexist?

I apologize this question turned out a bit long, but I have worked on this for some time and really needed to explain all the story.
Background: App based on MEAN stack, trying to authorize Facebook logins using Passport.js.
Following Passport.js guide I implemented something similar to:
// HTML
Add a Facebook login
// send to facebook to do the authentication
app.get('/connect/facebook',isLoggedIn, passport.authorize('facebook',
{ scope : 'email' })
);
// handle the callback after facebook has authorized the user
app.get('/connect/facebook/callback',
passport.authorize('facebook', {
successRedirect : '/profile',
failureRedirect : '/profile'
}));
Notice the target=_self in the html in order to skip Angular routing.
Clearly, authorization works fine. However, redirection does not work, as the routing is handled by Angular. After authorization I never land on /profile (but on the default Angular route).
Therefore, I tried with a custom callback as suggested by Passport.js here, with the hope of passing json data to Angular, and let Angular do the routing. I ended up doing something like:
// In the controller
$http.get("/connect/facebook").success(function(data){
// here I wait for json data from the server and do the routing
});
// I call this route from Angular
app.get('/connect/facebook',isLoggedIn,passport.authorize('facebook',
{ scope : 'email' })
);
// But Facebook lands here!
app.get('/connect/facebook/callback',function(req, res, next) {
passport.authorize('facebook', function(err, user, info) {
res.json({something:smtg});
...
Clearly custom callbacks work for local-login, as Passport.js explains. But here do you see the problem? I call /connect/facebook from Angular, but I should receive some json from /connect/facebook/callback.
I am about to give up Passport, but before this, do you see any solution which would allow landing on /profile after FB authorization, perhaps with a custom message? Many thanks for reading through.
EDIT:
The same question had been reported as an issue on the Passport-Facebook GitHub account. Some additional attempts have been posted there, but not quite the fix yet.
This is a bit more in depth than can be described in one answer, but I'll try to start pointing you in the right direction.
Essentially, Angular.js routes are not really HTML routes at all, but an internal route structure that happens to use the URL for use of the end user. Remember that Angular.js is a client script, and that a full page reload is not desired, as this will reload the entire script. Therefore, /# is used to trick the browser into jumping to a specific bit of code within the already loaded script. (as opposed to the traditional anchor location in the HTML document). Unfortunately (or fortunately), HTML 5 mode allows you to hide the /# part of the url, so instead of seeing http://somesite.com/#/someroute you just see http://somesite.com/someroute. Rest assured, however, that the /# is still there. Angular.js uses the HTML5 pushState (AKA HistoryAPI) to perform the magic replacement.
Given this, if you have called a server route, you are outside the Angular.js script, and any call to load the angular script again will start from the very beginning. You can't actually call your Angular.js route from the server without a full reload. Therefore, you are really doing a double route redirect here. Your server should be calling it's default route for angular, appending /#/someroute to the call. The angular.js page will load, parse off the /#, and redirect to the correct angular route. Keep in mind, however, that if there was any dependency on already loaded objects, those are no longer in memory. Therefore, any route accessed this way should operate as if it is an entry point to your application.
Effectively, you should try using successRedirect : '#/profile', keeping in mind that the profile route in angular should be treated as an app entry point.
Hopefully this gets you started.
If #Claies's way is not working, is it possible you have not get rid of the #= fragment from the facebook callback.
Have a read of this post

AngularJs - Determining the next location's route in $locationChangeStart

Recently I found out about the undocumented $locationChangeStart event in AngularJS when trying to determine where in the framework I should be redirecting users to the login page if their session is invalid.
I'm listening for the event using the following:
$scope.$on("$locationChangeStart", function (event, nextLocation, currentLocation) {
// Logic goes here
});
where nextLocation and previousLocation are absolute urls to locations within the app.
My question is this: Does AngularJS expose the ability to be able to match a url against a route, much like its internal routing engine does to see if an absolute URL matches a defined route?
If not, what would be the best way to detect a route based on an absolute URL?
I located the relevant source code lines in the AngularJS github project. Here is the function that it uses to match the URL to a route:
Since this functionality is not exposed by Angular I decided to come up with a rudimentary way of matching the URL to a route since the routes I was matching were relatively simple compared to implementing the same route matching that Angular does.
It ain't pretty, but it works..

Resources