How to handle angular2 route path in Nodejs? - node.js

I am working on a NodeJS app with Angular2. In my app, I have a home page and search page. For home page I have an HTML page that will render for the localhost:3000/ and from home page user navigate to search i.e localhost:3000/search page that I handled by angular2 routes.
I don't have the page for the search page its view render by the angular2. But when I directly hit localhost:3000/search as I don't have this routing in my node app it gives the error.
I don't know How to handle this in node app?

If you enter localhost:3000/search directly in the browser navigation bar, your browser issues an request to '/search' to your server, which can be seen in the console (make sure you check the 'Preserve Log' button).
Navigated to http://localhost:3000/search
If you run a fully static server, this generates an error, as the search page does not exist on the server. Using express, for example, you can catch these requests and returns the index.html file. The angular2 bootstrap kicks-in, and the /search route described in your #RouteConfig is activated.
// example of express()
let app = express();
app.use(express.static(static_dir));
// Additional web services goes here
...
// 404 catch
app.all('*', (req: any, res: any) => {
console.log(`[TRACE] Server 404 request: ${req.originalUrl}`);
res.status(200).sendFile(index_file);
});

You need to use HashLocationStrategy
import { LocationStrategy, HashLocationStrategy } from "angular2/router";
bootstrap(AppComponent, [
ROUTER_PROVIDERS,
provide(LocationStrategy, { useClass: HashLocationStrategy })
]);
In your bootstrap file.
If you want to go with PathLocationStrategy ( without # ) you must setup rewrite strategy for your server.

I've been digging this topic for quite a time , and try a lot of method that don' work .
If you are using angular-cli and with express , I found a solution if the chosen answer doesn't works for you .
Try this node module : express-history-api-fallback
[ Angular ] Change your app.module.ts file , under #NgModule > imports as following :
RouterModule.forRoot(routes), ...
This is so called : " Location Strategy "
You probably were using " Hash Location Strategy " like this :
RouterModule.forRoot(routes, { useHash: true }) , ...
[ Express & Node ]
Basically you have to handle the URL properly , so if you want call "api/datas" etc. that doesn't conflict with the HTML5 Location Strategy .
In your server.js file ( if you used express generator , I've rename it as middleware.js for clarity )
Step 1. - Require the express-history-api-fallback module.
const fallback = require('express-history-api-fallback');
Step 2 . You may have a lot of routes module , sth. like :
app.use('/api/users, userRoutes);
app.use('/api/books, bookRoutes); ......
app.use('/', index); // Where you render your Angular 4 (dist/index.html)
Becareful with the order you are writing , in my case I call the module right under app.use('/',index);
app.use(fallback(__dirname + '/dist/index.html'));
* Make sure it is before where you handle 404 or sth. like that .
This approach works for me : )
I am using EAN stack (Angular4 with cli , Express , Node )

Related

Angular app navigation not applying when serving app in expressjs

I have an Angular 7 app, that is server over expressjs.
The app serving part of the server is like so:
app.get('/', (req, res) => {
console.log(`sending...`);
res.sendFile(path.join(__dirname, 'index.html'));
});
It's very simple: When I navigate to localhost:PORT I get the base page of the angular app, which is a login page.
Angular's routing is like so:
const routes: Routes = [
{ path: '', component: LoginComponent},
{ path: 'query', component: QueryComponent, canActivate:[AuthGuard] },
{ path: '**', pathMatch:'full', redirectTo: '' }
];
When I serve angular via ng serve, and I try to navigate to any unauthorized page, like: http://localhost:4200/anyPage or http://localhost:4200/query and I get redirected to the login page or to the authGuard I created.
This is the behaviour that I want - any user how try to navigate to the query part of my app and dosen't pass the authGuard, will be redirected to the login page, and any user that is lost and enters a weird url, will be redirected back to the base page.
Where the issue?
When I serve my app with express, It works only when I enter the root url: http://localhost:PORT, and I got my base page served.
When I try to navigate to any other url, like: http://locahost:PORT/query or http://localhostPORT/anyPage I get an GET error: Cannot GET /query.
How do I make angular's routing take place when I serve it via expressjs?
I read This answer regarding nagular routes and nodejs route, and I must ask:
Is the only way to solve my issue is to define authGuard to each page in angular so that nodejs/express will allways redirect to angular's base page and angular must hande everything from there (and get errors whenever I try to navigate to any url that is not defined in express)?
Is there is no way that some of the navigation will be handeled by express?
If express "knows" only to navigate to the base url in angular, what is the point of defining routing in angular? When the route that I defined in angular, like: path:'/query' comes into effect?
You have to use app.get('*') to direct all request to index.html. This will also direct static assets, so you have to add a app.use():
const app = express();
app.use(express.static(__dirname + '/dist'));
app.get('*', (req, res) => {
console.log(`sending...`);
res.sendFile(path.join(__dirname, 'index.html'));
});
app.listen(port);

How to pass routing control from Node server to client?

Let's say this is the part where all routes are handled on a Node server with Angular front end. When the user enters the site URL, the server sends all the static files for the browser to render. However, I would like the client to handle certain routes instead of it going directly to the server.
For example, if I enter www.exemple.com/page2, a Get request is sent to the server but the route doesn't exist so the request just hangs there and ultimately resulting an error.
I want Angular to handle the routing instead of it going automatically to the server. I've only successfully got this to work on localhost where the Angular app is served from a different port than the one that the server listens to. Can anyone one tell me how to achieve this? Thanks a lot.
module.exports=function(app,dir){
app.use(express.json());
app.use(express.static(dir+'/dist/probeeshq'));
app.get('/',(req,res)=>{res.sendFile(path.join(dir));});
app.use('/auth', userAuth);
app.use('/api/me',userData);
app.use('/api/org',organization);
app.use('/api/messaging',messaging);
app.use('/api/search',search);
app.use(error);
}
This is what I have in Angular
const routes: Routes = [
{ path:'', component: HomeComponent },
{ path:'project_gaea', component:ProjectGaeaComponent},
{ path:'dashboard', component: DashboardComponent ,canActivate:[AuthGuardService]},
{ path:'explore', component:ExploreComponent, canActivate:[AuthGuardService]},
{ path:'create', component: CreateComponent },
{ path:'user/:id', component:UserProfileComponent},
{ path:'**', component: PageNotFoundComponent}
];
You can achieve this by implementing the Route feature that Angular has out of the box. After you implement this, you can then just use your back-end as an API.
So it turns out that I was supposed to serve the application like this:
app.use('/', express.static(dir));
And Express will let Angular handle all the routing after failing to match all the routes at the server side. dir is just the path were the Angular app is.
I have the same issue, with wildcard routes we can fix this. And client routes handling any unrecognised urls. Good from the user experience perspective no issues on that
like so
app.get("*", (req, res) => {
// send HTML files
});
But what about auditing wise, like a simple question like "if I send a unrecognised URL to server, it should give a 404 status code, instead of redirecting me to client and showing a 404 page or something"
Made a valid point, which doubted my knowledge on the web. But to resolve this we need to manually whitelist the client URL's in server, still figuring out myself, if any better solution please let me know.

Node.js / Express.js + Angular router - server overwriting client view with response object when using direct link

I am building a node.js app with express, I am hosting an Angular SPA in the public folder.
The app runs and the hosting works fine when I use the angular router for navigation around the website, but when I directly try to access the link, for example: http://192.168.1.4:3000/posts, the entire body of the website is just the JSON response object, without the app
this is the Node.js code handling the get request
postRouter.route('/')
.options(cors.corsWithOptions, (req, res) => {
res.sendStatus(200);
})
.get(cors.cors, (req, res, next) => {
posts.find({})
.then((post) => {
res.status(200);
res.setHeader('Content-Type', 'application/json')
res.send(post);
}, (err) => next(err))
.catch((err) => next(err));
})
this is my angular service sending out the get request
getPosts(): Observable<Post[]> {
return this.http.get(baseURL + 'posts')
.catch(error => { return this.processHttpService.handleError(error); });
}
Post Component .ts file
ngOnInit() {
this.postService.getPosts()
.subscribe(posts => { this.posts = posts, console.log(this.posts); },
errmess => this.errMess = <any>errmess);
}
Again, when i use my Angular 5 client app hosted in the public folder, built with ng build --prod, the JSON object is retrieved from the mongodb database and is displayed correctly on my website, along with the rest of the app, the header, the body, and the footer.
it might also be worth noting that the console.log on the ngOnInit() is not displayed on the browser when using the direct link.
Any advice/fix is greatly appreciated
You have a clash of routes between angular and your express application. Angular is served up on one route (I'm guessing the / route) and then it sort of "hijacks" the users navigation. It does this by not actually changing web pages, instead it just changes the URL in the navigation bar, but never actually makes a web request to get to that resource.
You've then got endpoints on a web server listening on those endpoints. This means the moment you visit the /posts page, you're not asking angular to do anything. In fact, angular isn't even loaded because that only gets loaded on the / route. Instead you're going straight to your API.
There are ways around this, to start with many people put their API fairly separately, either on a subdomain or mounted on /api (such as /api/posts). Then your angular app can be served up on the / route. There are other techniques you can use to then allow a user to go to /posts and still get your angular app loaded.
You can use a few approaches for this such as the hash location strategy, or you can serve up your angular application from any route on the application (* in express) and load the angular app which will then take over. This second approach is most comment, it usually results in hosting your api on a sub domain and then serving your angular app on the * route of the normal domain name. For example: api.myapp.com will serve only JSON responses, but any route on myapp.com will serve the angular app, such as myapp.com/posts.

Ember and Express: let Ember handle routes instead of Express?

This might be a dumb question, but I'm serving an Ember app I made using ember-cli on an Express server, but when I try to access various routes, my Express app errors, saying that no route exists (which is true, because I defined the routes in Ember, not Express). How should I resolve this, and is this normal behavior?
My Ember router:
Router.map(function() {
this.route('index', {path: '/' });
this.route('portkey');
this.route('login');
});
My Express routes are just an API that do not serve any of the Ember routes, since localhost:1234 will automatically load index.html.
I've never had a problem using the Ember Router instead of the Express router. All I do is have 1 express route (for '/') which displays my Ember application index.html (well actually index.ejs) page. Not promising this is the right way to do it, but it's how I do it and it works for me.
So start with this.
app.get('/', function(req, res) {
res.render('index', {});
});
That's your express route. Now your ember routing.
App.Router.map( function() {
this.route("about", { path: "/about" });
this.route("favorites", { path: "/favorites" });
});
So as of now you have a routing structure that looks like the following:
yourdomain.com/ --> index.ejs displayed via express routing
/#/ --> this is the ember index route
/#/about --> this is the ember about route
/#/favorites --> this is the ember favorites route
Within the index.ejs file you have the basic ember file linking to your ember application.
Now onto your linking problems...
If you use the ember router, then make sure you are linking to your different routes the correct way. (Remember, ember routes start with /#/someroute).
So your links in handlebars should be something like:
{{#link-to 'some_page'}}Go to some page{{/link}}
NOT
Go to some page
Using the second, express would be trying to handle the routing but by using the first, ember is handling the routing.
So if you really think about it, you can have as many ember applications as your little heart disires because each ember application is linked to that current page in the express routing.
For example on my website, I use two routes (plus a bunch of REST routes obviously): login.ejs and index.ejs.
So for my site, I have the following routes:
mysite.com/
/#/
/#/budget
/#/history
/#/profile
/#/logout
mysite.com/login#/
#/register
#/forget
I hope this helps you a little bit.
EDIT
/#/ is a convention to tell ember you are routing via its router.
Think of it like this: Ember is a single-page framework. So when you link from page to page in ember, you aren't truely changing pages. You are just removing dom elements and replacing them with new ones. But if you go to /budget on the server, you are now going to a whole new page, not just the /#/budget section of the ember application.
I think you are just confusing what the ember router really is.
I had similar issues when trying to directly access any part of my Ember project other than index.html. From there I could easily navigate where I wanted, but it meant that providing someone a link or refreshing the page would fail.
Example: /accounts would fail.
/#/accounts would successfully redirect to /accounts however refreshing still would not work.
Solution:
Router.map(function() {
this.route('accounts');
});
Router.reopen({
location: 'hash'
});
Now all of my links are prefixed with # such as /#/accounts, refreshing and direct-linking works as expected.

Angular and Sails routing configuration

Is there any Sails.js (or Node) configuration which can prevent Angular routing from working?
No matter what approach I take, every rout apart from ones in sails' routes.js return 404.
I've tried both 1.2 and 1.3 Angular versions, and I'm using Sails v 0.9.15.
All scripts are loaded in correct order (for example):
<script src="/linker/js/libs/angular/angular.js"></script>
<script src="/linker/js/libs/angular/angular-resource.js"></script>
<script src="/linker/js/libs/angular/angular-route.js"></script>
<script src="/linker/js/libs/angular/angular-sanitize.js"></script>
...
<script src="/linker/js/app.js"></script>
I'm using the ngRoute correctly:
var myApp= angular.module('myApp', ['ngRoute']);
Here are my routes in Angular's app.js:
myApp.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/profile',
{ templateUrl: 'linker/views/profile.html', controller: 'MainCtrl' })
.when('/profile-detail',
{ templateUrl: 'linker/views/profile_detail.html', controller: 'MainCtrl' });
}]);
And I'm also using location provider:
myApp.config(function ($locationProvider) {
$locationProvider.html5Mode(true);
});
Additional details:
ng-app and ng-view are correctly placed, and my paths are correct. I can correctly show profile.html with Angular (and also to include data from Sails' Restful API).
Problem is, that I can only do that for routes defined in Sails' routes.js.
Example:
module.exports.routes = {
'/' : {
controller: 'main',
action: 'index'
},
'/signup' : {
controller: 'main',
action: 'signup'
},
'/login' : {
controller: 'main',
action: 'login'
},
'/profile': {
controller: 'users',
action: 'profile'
} //......
So basically, in order to show some html content with Angular, I have to define EXACTLY THE SAME route in Sails' configuration, which makes no sense.
Any ideas? P.S. I'll provide additional data if needed.
Try removing the html5 mode to see what happens:
$locationProvider.html5Mode(false);
If you are using your sails application only to provide an API for your Angular app, but you are using the same backend to serve your angular code, then you could prefix all API routes with 'api' in order to prevent having conflicts with angular routes.
Instead of /profile you would have /api/profile
EDIT:
I've taken a look into the Sails.js framework and made a small app to test it.
I was able to successfully have routes in angular work that were not defined by sails.
I think there is a misunderstanding of how angular routing works.
If you change the path with window.location or type the url manually, the browser will send a get request to the server. So in your case, there will be a request for /profile or /profilee and the server will look at the available routes and will throw a 404 if nothing matches.
To prevent that, you should actually change the path using angular methods. Angular uses the '#' symbol in the path to prevent the browser of sending requests to the server when the url changes. Browsers ignore changes after the '#' symbol. Or in your case, a similar effect is achieved using the html5 mode. Beware though that using html5 mode can cause troubles when users refresh the page, since then a request will be made to the server (more on how to fix that below).
So, what you should be using to change the paths with javascript is the $location service. In your angular views, you can also use normal anchor tags like, because angular parses those:
Go to profile
Since what you have is a single page application, alls views are handled by the client. All the paths beyond the root (/) are virtual paths created by angular. They usually don't exist in the server. Only the root is available. When using html5 mode that can be a problem.
A way to fix that is to rewrite the routing of the server to serve everything else as if it was a request to the root path. In sails they even suggest how to do that in the config/routes.js:
// What about the ever-popular "vanity URLs" aka URL slugs?
// (you might remember doing this with `mod_rewrite` in Apache)
//
// This is where you want to set up root-relative dynamic routes like:
// http://yourwebsite.com/twinkletoez
//
// NOTE:
// You'll still want to allow requests through to the static assets,
// so we need to set up this route to ignore URLs that have a trailing ".":
// (e.g. your javascript, CSS, and image files)
'get /*(^.*)': 'UserController.profile'
Regarding the API in sail, you can configure a prefix inside the config/controllers.js file:
/**
* `prefix`
*
* An optional mount path for all blueprint routes on a controller, including `rest`,
* `actions`, and `shortcuts`. This allows you to continue to use blueprints, even if you
* need to namespace your API methods.
*
* For example, `prefix: '/api/v2'` would make the following REST blueprint routes
* for a FooController:
*
* `GET /api/v2/foo/:id?`
* `POST /api/v2/foo`
* `PUT /api/v2/foo/:id`
* `DELETE /api/v2/foo/:id`
*
* By default, no prefix is used.
*/
prefix: '',

Resources