Angular app navigation not applying when serving app in expressjs - node.js

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);

Related

Angular Universal app can't make post requests to my Heroku node API

My website is a SPA built with Angular, but it uses SSR with Angular Universal to provide crawlable and social media sharing content.
All GET requests in my server are handled by Universal like this:
app.engine(
'html',
ngExpressEngine({
bootstrap: ServerAppModuleNgFactory,
providers: [provider]
})
)
app.set('view engine', 'html')
app.set('views', __dirname)
app.use('/', express.static('./dist', {index: false}))
app.use('/', expressStaticGzip('./dist', {
enableBrotli: true
}))
app.get('/*', (req, res) => {
res.render('./dist/index', {
req: req,
res: res
})
})
and my pages contents are provided by Angular Services POST requests built with the same queryParams of the requested url.
One example:
If the user visits the url https://mywebsite.com/products?page=1&itemsPerPage=12 (GET request by default), the Angular Universal app and the Angular Router dynamically build my page template and the products list is provided by a Service that triggers a POST request to this URL: https://mywebsite.com/request-products with the following params in body:
{
page: 1,
itemsPerPage: 12
}
Then the Universal App builds the template with some *ngFor directives to populate it before serving it to the client.
This approach makes all my pages visible to webcrawlers and I also get the benefits of a Single Page Application.
When I'm testing my app, I build my Angular app, both Browser and Server builds, and set my environment like this:
export const environment = {
production: true,
apiUrl: 'http://localhost:7070/'
}
and serves my app in localhost, it works perfectly, without errors. My POST requests, like mentioned before, are all handled perfectly. But when I try to set my apiUrl to 'https://mywebsite.com/' and serve my app also in localhost, to access directly my API hosted in Heroku, I just can't access my POST routes.
My node express server app in Heroku is configured to accept requests from other domains, I can access it normally in my localhost server, but when I try to access it through my Angular Universal server build, it just won't work.
I know that I have to use absolute URLS in my Universal Apps, and I'm doing it already, but it's not working.
Does anyone know what I have to do to access external APIs in my Angular Universal Apps via https?
Thanks!
I've found the problem, and it's something really simple.
It turns out that I must use 'www' in my absolute url, like this:
'https://www.mywebsite.com/'
Now everything works perfectly, both from my localhost and my heroku servers.
Thanks to everyone that took some time to read my question!

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.

Angular 2.0 deep-linking does not work. Same from the address bar

Everything works OK when I build and navigate links in the app but typing in the address bar does not!
Eventually I want to be able to send a link to a specific app page/path via email and I am not sure how to accomplish that.
I have followed the angular router guide documentation. Correctly I think...
I'm using NodeJS (with express) and in the server I have redirected all traffic to the app root.
app.get("*", function(req, res) {
console.error("in get *")
res.redirect("/");
});
In my index.html I have set base
<base href="/">
I have my client routs/paths set as follows
const appRoutes: Routes = [
{ path: 'vendor/registration', component: VendorRegistrationComponent },
{ path: 'vendors', component: VendorListComponent },
{ path: '', redirectTo: 'vendor/registration', pathMatch: 'full' },
{ path: '**', component: PageNotFoundComponent }
];
If I type http://localhost:8080/vendors in the browser address bar I get to http://localhost:8080/vendor/registration which kind of makes sense because that's where the server tells the browser to redirect to.
How then should I deep link to my app?
Edit.
The Angular tutorial - "TOUR OF HEROES" - also exhibits the same behavior. i.e. entering urls in the browser's address bar directly does not work. The app shows a "loading..." text and does not route to the controller.
Generally you would not need to redirect on the server since all of your routing is handled by the angular router on the client.
Instead make your catch-all express route simply always serve your index.html for all requested urls. When the client app bootstraps, angular will take care of routing to the appropriate component based on the requested url. Make sure to keep your <base href="/"> tag so that angular can be aware of what part of the url is routed.
const path = require('path');
app.get("*", function(req, res) {
res.sendFile(path.join(__dirname, '/path/to/your/index.html'));
});
The response at your question is explicitly describe in the angular2 documentation

How to get React Router Browser history to work with the rest of my API endpoints

I am following the React Router Tutorial
Section 11 - Productionish Server.
I had to add this code to my server to get the browserHistory to work for the Router:
// send all requests to index.html so browserHistory in React Router works
app.get('*', function (req, res) {
res.sendFile(path.join(__dirname, 'index.html'))
})
Now all my other API endpoints are unnaccessable because of the *, but if I change it to serve on \ the browser history breaks.
Server is written in Node. Any advice on how I can tell it to handle my api routes as well?
I usually mount my routes like so:
app.use('/api/v1/projects', ProjectsRouter);

How to use AngularJS routes with Express (Node.js) when a new page is requested?

I'm using Express, which loads AngularJS from a static directory. Normally, I will request http://localhost/, in which Express serves me my index.html and all of the correct Angular files, etc. In my Angular app, I have these routes setup, which replace the content in an ng-view:
$routeProvider.when('/', {
templateUrl: '/partials/main.html',
controller: MainCtrl,
});
$routeProvider.when('/project/:projectId', {
templateUrl: '/partials/project.html',
controller: ProjectCtrl,
});
$locationProvider.html5Mode(true);
On my main page, I have a link to <a href="/project/{{project.id}}">, which will successfully load the template and direct me to http://localhost/project/3 or whatever ID I have specified. The problem is when I try to direct my browser to http://localhost/project/3 or refresh the page, the request is going to the Express/Node server, which returns Cannot GET /project/3.
How do I setup my Express routes to accommodate for this? I'm guessing it will require the use of $location in Angular (although I'd prefer to avoid the ugly ?searches and #hashes they use), but I'm clueless about how to go about setting up the Express routes to handle this.
Thanks.
with express 4, you probably want to catch all requests and redirect to angularjs index.html page.
app.use(app.router); doesn't exist anymore and res.sendfile is deprecated, use res.sendFilewith an uppercase F.
app.post('/projects/', projectController.createProject);
app.get('/projects/:id', projectController.getProject);
app.get('*', function (req, res) {
res.sendFile('/public/index.html');
});
put all your API routes before the route for every path app.get('*', function (req, res){...})
I would create a catch-all handler that runs after your regular routes that sends the necessary data.
app = express();
// your normal configuration like `app.use(express.bodyParser());` here
// ...
app.use(app.router);
app.use(function(req, res) {
// Use res.sendfile, as it streams instead of reading the file into memory.
res.sendfile(__dirname + '/public/index.html');
});
app.router is the middleware that runs all of your Express routes (like app.get and app.post); normally, Express puts this at the very end of the middleware chain automatically, but you can also add it to the chain explicitly, like we did here.
Then, if the URL isn't handled by app.router, the last middleware will send the Angular HTML view down to the client. This will happen for any URL that isn't handled by the other middleware, so your Angular app will have to handle invalid routes correctly.
I guess I should have clarified that I wasn't interested in using a template engine, but having Angular pull all of the HTML partials on it's own, Node is functioning completely as a static server here (but it won't be for the JSON API. Brian Ford shows how to do it using Jade here: http://briantford.com/blog/angular-express.html
My app is a single-page app, so I created an Express route for each possible URL pattern, and each of them does the same thing.
fs.readFile(__dirname + '/public/index.html', 'utf8', function(err, content) {
res.send(content);
});
I was assuming I would have to pass some request variables to Angular, but it looks like Angular takes care of it automatically.

Resources