AngularJS - Best Way to Handle State on Manually Entered URLs - javascript

I currently have a set-up based on the meanjs stack boilerplate where I can have users logged in this state of being 'logged-in' stays as I navigate the URLs of the site. This is due to holding the user object in a Service which becomes globally available.
However this only works if I navigate from my base root, i.e. from '/' and by navigation only within my app.
If I manually enter a URL such as '/page1' it loses the global user object, however if I go to my root homepage and navigate to '/page1' via the site. Then it's fine, it sees the global user object in the Service object.
So I guess this happens due to the full page refresh which loses the global value where is navigating via the site does not do a refresh so you keep all your variables.
Some things to note:
I have enabled HTML5Mode, using prefix of '!'.
I use UI-Router
I use a tag with '/'
I have a re-write rule on express that after loading all my routes, I have one last route that takes all '/*' to and sends back the root index.html file, as that is where the angularjs stuff is.
I'm just wondering what people generally do here? Do they revert the standard cookies and local storage solutions? I'm fairly new to angular so I am guessing there are libraries out there for this.
I just would like to know what the recommended way to deal with this or what the majority do, just so I am aligned in the right way and angular way I suppose.
Update:
If I manually navigate to another URL on my site via the address bar, I lose my user state, however if I manually go back to my root via the address bar, my user state is seen again, so it is not simply about loosing state on window refresh. So it seems it is related to code running on root URL.
I have an express re-write that manually entered URLs (due to HTML5 Location Mode) should return the index.html first as it contains the AngularJs files and then the UI-Route takes over and routes it properly.
So I would have expected that any code on the root would have executed anyway, so it should be similar to navigating via the site or typing in the address bar. I must be missing something about Angular that has this effect.
Update 2
Right so more investigation lead me to this:
<script type="text/javascript">
var user = {{ user | json | safe }};
</script>
Which is a server side code for index.html, I guess this is not run when refreshing the page to a new page via a manual URL.
Using the hash bang mode, it works, which is because with hash bang mode, even I type a URL in the browser, it does not cause a refresh, where as using HTML5 Mode, it does refresh. So right now the solution I can think of is using sessionStorage.
Unless there better alternatives?
Update 3:
It seems the best way to handle this when using HTML5Mode is that you just have to have a re-write on the express server and few other things.

I think you have it right, but you may want to look at all the routes that your app may need and just consider some basic structure (api, user, session, partials etc). It just seems like one of those issues where it's as complicated as you want to let it become.
As far as the best practice you can follow the angular-fullstack-generator or the meanio project.
What you are doing looks closest to the mean.io mostly because they also use the ui-router, although they seem to have kept the hashbang and it looks like of more of an SEO friendly with some independant SPA page(s) capability.
You can probably install it and find the code before I explained it here so -
npm install -g meanio
mean init name
cd [name] && npm install
The angular-fullstack looks like this which is a good example of a more typical routing:
// Server API Routes
app.route('/api/awesomeThings')
.get(api.awesomeThings);
app.route('/api/users')
.post(users.create)
.put(users.changePassword);
app.route('/api/users/me')
.get(users.me);
app.route('/api/users/:id')
.get(users.show);
app.route('/api/session')
.post(session.login)
.delete(session.logout);
// All undefined api routes should return a 404
app.route('/api/*')
.get(function(req, res) {
res.send(404);
});
// All other routes to use Angular routing in app/scripts/app.js
app.route('/partials/*')
.get(index.partials);
app.route('/*')
.get( middleware.setUserCookie, index.index);
The partials are then found with some regex for simplicity and delivered without rendering like:
var path = require('path');
exports.partials = function(req, res) {
var stripped = req.url.split('.')[0];
var requestedView = path.join('./', stripped);
res.render(requestedView, function(err, html) {
if(err) {
console.log("Error rendering partial '" + requestedView + "'\n", err);
res.status(404);
res.send(404);
} else {
res.send(html);
}
});
};
And the index is rendered:
exports.index = function(req, res) {
res.render('index');
};

In the end I did have quite a bit of trouble but managed to get it to work by doing few things that can be broken down in to steps, which apply to those who are using HTML5Mode.
1) After enabling HTML5Mode in Angular, set a re-write on your server so that it sends back your index.html that contains the Angular src js files. Note, this re-write should be at the end after your static files and normal server routes (e.g. after your REST API routes).
2) Make sure that angular routes are not the same as your server routes. So if you have a front-end state /user/account, then do not have a server route /user/account otherwise it will not get called, change your server-side route to something like /api/v1/server/route.
3) For all anchor tags in your front-end that are meant to trigger a direct call to the server without having to go through Angular state/route, make sure you add a 'target=_self'.

Related

Node URL gets "undefined" added to URL path

I have a site that exists on a dev, staging and production server. On the dev and staging server the functionality is 100% fine, however on the production server the strangest thing happens - "undefined" gets added to the URL path.
Here is the short example of what is happening:
In the index.html I have an anchor tag to logout of a session with passport: Logout.
It goes to this route on my node server:
// passport oauth logout
routes.get('/auth/logout', (req, res) => {
req.session.destroy((e) => {
req.logout();
res.redirect(config.redirects.env.prod);
});
});
On dev and staging this destroys the session and redirects you to /. On production, when you click the button it takes you to this URL randomly https://somesite.com/auth/undefined.
Any ideas on how to debug this? It is making no sense to me and there's nothing I'm finding serverside or in the markup that would cause this, especially since it is functional on dev and staging. All servers are Ubuntu servers set up exactly the same way.
I was able to resolve this. Oddly enough, 400 lines down in a completely unrelated route used for file uploads, I had a line of code that referenced config.redirects.env.production instead of config.redirects.env.prod. I wasn't even looking at that route because it wasn't even part of functionality i was testing at the moment and I saw no errors spit out (again, since the route wasnt being referenced/used yet).
Fixing that typo resolved this bizarre issue of "undefined" being inserted into the URL. Still not sure how it managed to bubble up like that.

expressJS router planning - how to set up an effective router

I realize that this may be a fairly simple question but bear with me. I am really new to node/express.
My directory structure looks like:
auth
index.html
pub
index.html
login.html
the idea here is that the resources in pub are publicly available but the resources in auth are only available after a user is authenticated.
However, at this point, I am just trying to get these pages to come back properly from the server. Ideally, my routing engine would be able to serve these pages up based on some parameter. So:
site.com -> pub/index.html
site.com/login/ -> pub/login.html
site.com/dashboard/ -> auth/index.html
I tried something like this:
router.get('/dashboard/', function(req, res, next) {
res.sendFile(__dirname + "/src/auth/index.html");
});
router.get('/login/', function(req, res, next) {
res.sendFile(__dirname + "/src/pub/login.html");
});
router.get('*', function(req, res, next) {
res.sendFile(__dirname + "/src/pub/index.html");
});
However, the problem I quickly found was that these pages are requesting resources relative to their own position in the directory structure and all requests were being returned the default index.html. So, for example if I type site.com in the browser index.html loads. Here is part of index.html:
<script src="js/jquery.min.js"></script>
naturally then, the browser makes another request for /js/jquery.min.js which the router can't find so it responds with index.html again.
How do I design a routing engine that is smart enough to find the correct view based on the url and then understand that it needs to serve all requests from that page relative to that pages position in the directory structure? Or is there another standard way of handling this kind of problem?
To complicate matters, the auth/index.html is an angular page. So, once it loads it will be requesting all kinds of html pages relative to its position in the directory structure (depending on routes and included templates etc.)
Thanks in advance.
Those are a lot of questions but I think I can at least get you pointed in the right direction :)
However, at this point, I am just trying to get these pages to come back properly from the server.
To do this with express, you can use express.static to designate a public directory whose assets get made available to web requests. For example, if you had a directory sturcture like this:
public/
templates/
index.html
stylesheets/
js/
jquery.min.js
In express, you would do this:
app.use(express.static(__dirname + '/public'));
in order to expose those files as static assets, relative to the public dir, eg http://yourserver.com/templates/index.html
To complicate matters, the auth/index.html is an angular page. So, once it loads it will be requesting all kinds of html pages relative to its position in the directory structure
I think part of your confusion here is knowing the difference between client side routing and server side routing in an AngularJS/node.js app.
AngularJS is a framework for building single page apps (SPA). What this means is your browser requests one HTML file at the start (eg an index.html served from the route '/' on your server) to get things started, which loads some bootstraping javascript. From then on, client side javascript and AJAX calls will handle all of the rest to facilitate rendering additional HTML, user interaction, and navigation to other parts of your app. The URL in the browser will change, but you'll notice that no further page reloads will take place as you navigate. This is the client side routing that you can use AngularJS to build. If you've looked at the AngularJS tutorial, step 7 goes over how this works.
Your server side routes are typically not involved in this page navigation. Instead, your server should provide an API for the AngularJS client side will mae AJAX calls to for creating, reading, updating, deleting (CRUD) application data. For login for example, you could have a server side /api/login route that doesn't return an HTML page, but rather accepts a username and password via a POST request, establishes some session state, and then returns the result to be dealt with on the client side.
In addition to the AngularJS tutorials, I would invite you to take a look at mean.js for an end to end example of what a node.js + angularJS app looks like.

AngularJS html5mode and hard 404

I have a AngularJS app working with html5mode set to true.
Currently, the app shows a soft 404 page, with the .otherwise setting in the router.
Is there a way I could serve actual 404 HTTP response, for the sake of SEO while using html5mode?
If I understand correctly what you want, you have to do the following:
hard redirect the browser (bypassing the angular routing) on the otherwise path, with something like this:
$routeProvider
.otherwise({
controller: function () {
window.location.replace('/404'); // your custom 404 page
// or a non existing page
}
});
if you have a regular single-page-application where all the server request are redirected to the same SPA entry point, you have to configure on your server to make a routing exception for your custom 404 page, which will should also be served with a 404 status.
Otherwise, I can't see how you would do that with just History API, without an external request, because it's whole point in angular routing is to bypass external requests.
If you just want non-existing routes to return 404, then you must configure your server to match all your angular routes, and return 404 otherwise.
Seach engines works with SPA applications through prerendered pages, using _escaped_fragment_ . And you can use Prerender.io (or simply PhantomJS) to generate any status codes for search engines, like this https://prerender.io/documentation/best-practices
But this schema is deprecated by Google: http://googlewebmastercentral.blogspot.ru/2015/10/deprecating-our-ajax-crawling-scheme.html At this moment Google tries to understand your JS with usual crawling schema.
Hard redirection to 404.html page is not a good practice: url must stay the same, like https://stackoverflow.com/somepage
You can try Angular2 with server rendering feature: https://docs.google.com/document/d/1q6g9UlmEZDXgrkY88AJZ6MUrUxcnwhBGS0EXbVlYicY/edit
You have to make your server issue 404s. Angular cannot help in anyway here.

Angular - UI Router Routes - HTML5 Mode

I'm trying to use HTML5 push state links with my Angular app. What I have is a series of routes similar to the following
$stateProvider.state('product', {
url: '/product/:productCode',
templateUrl: 'product/product.html',
controller: 'ProductCtrl'
}
});
This works when I navigate to [host]/#/product/ABC123 - It displays the url in the browser as /product/ABC123, then when I start clicking through to my other routes (using ui-sref) everything works as expected.
However - I'd like the ability to both refresh the browser, and remain in the same state, as well as be able to copy and paste that link and route to the right state.
eg. If I got to [host]/product/ABC123 - I want to display the content from the #/product/ABC123 route. Currently, this will give me a not found.
I'm using nginx as my app server. I believe I'll have to add something to handle it at that level, but I'm not sure where to start.
The issue you have is that your server does not know how to respond to /product/ABC123.
I am currently using node.js for my backend with angular, and to solve this I return the angular app for all routes, not just the usual root route for example.
So you might have used something like this in the past:
app.get('/', ...);
Which would have returned the angular app just for the root route. Now I use something like:
app.get('*', ...);
Which will return the angular app for all routes.
I should have mentioned that this can act as a catch all placed after other routes such as for static files. Another alternative is to mark all the routes you want specifically for the angular app, eg:
app.get('/', ...); app.get('/product/:productId', ...); etc

Multipage Node.js Server Without Routing Bloat?

Can I set up a multipage Node.js web server that does not require a unique route for every page?
I have a simple HTTP server set up using Node and Express, using EJS for view engine. My routing currently looks like this:
// routing
app.get('/', routes.index);
app.get('/hig', routes.hig);
app.get('/proto', routes.proto);
app.get('/design', routes.design);
app.get('/process', routes.process);
app.get('/demo', routes.demo);
app.get('/api', api.index);
app.get('/api/rules', api.list);
app.get('/api/rules/:id', api.ruleid);
I'd like to be able to easily update my site to have pages such as /hig/section1 and /hig/section2 (and so on) without having to update the route table each time and restart the server. More importantly, I'd like to be able to quickly and easily make multiple versions of a demo and be able to link to them.
For example, create a new demo and link a user to /demo/version23 while linking someone else to /demo/version 35, allowing me to illustrate different functionality without breaking previous demo sites. It would not be long until /demo/version108 and beyond exist, so having a sane way to create these without having 108+ routes is preferable.
The only method I've been successful at so far is updating route tables. Is there another way I can point to different pages in the route table that will allow me to more easily add new pages?
You should consider making part of url variable for ex as /hig/:section.
You should then get section as a parameter which you can use to map to different content, page or do any other logic that you want with that.
In my express api, I have a wildcard get. The endpoint var parses the keyword and then whatever you decided to do with that is up to you. In mine I have some if statements to change the database model etc but you don't need that... I would suggest keeping the 404 send, so if somebody hits an undesired url you can just give them whatever status code.
app.get('/:endpoint', function (req, res) {
var endpoint = req.params.endpoint;
if( endpoint == 'something' ){
} else if( endpoint == 'something' ){
} else { return res.send(404, { erorr: "That resource doesn't exist" }); }
// Display the results
});
I implemented simple demo project to achieve multi-app structure.
https://github.com/hitokun-s/node-express-multiapp-demo
With this structure, you can easily set up and maintain each app independently.
I hope this would be a help for you.

Categories