Angular2 Router and Express Integration - node.js

I'm having a problem using the Angular2 router and express, but none of the previous questions seem to have the solution I need. I'm using the latest version of the angular2-cli.
When I direct my browser to localhost:3000, I get the generic "app works" message that the angular2-cli generates. My problem arises when I try to navigate to one of my child routes, such as localhost:3000/auth/login - it redirects me back to the same "app works" page, instead of showing me a page that says "login works".
Looking at other questions on stack exchange, I figure that the issue is here:
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist/index.html'));
});
When I replace the * with a /, instead of redirecting me back to my target page, it gives me the cannot GET error.
In summary, my problem is that url navigation of my angular2 routes integrated into my express app is not working.
My express code in server.js is as follows:
var express = require("express");
var app = express();
var bodyParser = require("body-parser");
var mongoose = require("mongoose");
var path = require("path");
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended:true}));
app.use(express.static(path.join(__dirname, 'dist')));
// Catch all other routes and return the index file
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist/index.html'));
});
app.listen(3000, () => {
console.log("Server has started");
});
My application's routing module is as follows (app-routing.module.ts):
import {Route, RouterModule} from '#angular/router';
import {NgModule} from '#angular/core';
import {LoginComponent} from './login/login.component';
import {RegisterComponent} from './register/register.component';
import {FeedComponent} from './feed/feed.component';
import {FindComponent} from './find/find.component';
import {SubmitComponent} from './submit/submit.component';
import {ProfileComponent} from './profile/profile.component';
export const routeConfig = [
{
path: "auth",
children: [
{
path: "login",
component: LoginComponent
},
{
path: "register",
component: RegisterComponent
},
{
path: "",
redirectTo: "/login",
pathMatch: "full"
}
]
},
{
path: "app",
children: [
{
path: "feed",
component: FeedComponent
},
{
path: "find",
component: FindComponent
},
{
path: "submit",
component: SubmitComponent
},
{
path: "profile",
component: ProfileComponent
},
{
path: "",
redirectTo: "/feed",
pathMatch: "full"
}
]
}
];
#NgModule({
imports: [RouterModule.forRoot(routeConfig)],
exports: [RouterModule]
})
export class AppRoutingModule{
}
My application module is as follows:
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { HttpModule } from '#angular/http';
import {AppRoutingModule} from './app-routing.module';
import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './register/register.component';
import { FeedComponent } from './feed/feed.component';
import { FindComponent } from './find/find.component';
import { SubmitComponent } from './submit/submit.component';
import { ProfileComponent } from './profile/profile.component';
#NgModule({
declarations: [
AppComponent,
LoginComponent,
RegisterComponent,
FeedComponent,
FindComponent,
SubmitComponent,
ProfileComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
All the components have been defined in their respective files and are the barebones default templates generated by the angular2-cli.
I have no idea what to do at this point. I've even tried restarting and rewriting this project multiple times to see if I went wrong somewhere (this is my 3rd attempt).

For angular2 and nodejs integration you can either put this in server.js:
var cors = required('cors');
app.use(cors());
or you can make use of proxy in your web server. For example in apache2 inside virtualhost tag you can do
ProxyPass /api/ http://localhost:3000/
ProxyReverse /api/ http://localhost:3000/
Now instead of using "http://localhost:3000/" in your http request function (although you are not using it) you can use just "api/".

Related

Isomorphic Render wont get my css or styling

I am using react as the client side and node.js as the server side, and using webpack to transpile. When I start the website on local host it is missing all of its styling and css. Any Ideas?
This is my frontend webpack that handles the transpiling of my frontend code
const path = require('path');
const nodeExternals = require('webpack-node-externals');
module.exports = {
target: 'node',
externals: [nodeExternals()],
entry: './frontend/src/App',
output: {
path: path.resolve(__dirname, 'backend/build'),
filename: 'bundle.js',
libraryTarget: 'commonjs2',
libraryExport: 'default'
},
module: {
rules: [
{
test: /\.jsx$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env', '#babel/preset-react']
}
}
},
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader',
},
],
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env', '#babel/preset-react']
}
}
},
{
test: /\.css$/,
use: [ 'css-loader'],
sideEffects: true
}
]
}
};
This is my backend/server.js that handles server rendering
const express = require('express');
const React = require('react');
const { renderToString } = require('react-dom/server');
const App = require('../backend/build/bundle');
const { matchPath } = require('react-router-dom');
const app = express();
// This is the middleware that will handle all routes
app.get('*', (req, res) => {
// We create a context object that will be passed to the StaticRouter
const context = {};
// Render the app component to a string
const html = renderToString(
React.createElement(App, {
location: req.url,
context,
})
);
// If the app component set a context.url value, it means the user
// tried to access a protected route that they are not authenticated for.
// In this case, we redirect them to the login page.
if (context.url) {
res.redirect(context.url);
} else {
// Send the rendered HTML as the response
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<div id="root">${html}</div>
<script src="/build/bundle.js"></script>
</body>
</html>
`);
}
});
app.listen(3000, () =\> {
console.log('Server is listening on port 3000');
});
This is my frontend App.js
import React from "react";
import { StaticRouter } from "react-router-dom/server";
import { Footer, Contact, Who, Header } from "./containers";
import { Navbar, Background } from "./components";
import { Route, Routes } from "react-router-dom";
import "./App.css";
const App = (props) =\> {
return (
\<StaticRouter location={props.location} context={props.context}\>
\<Navbar /\>
\<Routes\>
\<Route exact path="/" element={\<Header /\>} /\>
\<Route path="/about" element={\<Who /\>} /\>
\<Route path="/contact" element={\<Contact /\>} /\>
\</Routes\>
\<Footer /\>
\</StaticRouter\>
);
};
export default App;
And this is my index.js
import React from 'react';
import ReactDom from 'react-dom';
import './index.css';
import App from './App';
ReactDom.render(
\<BrowserRouter\>
\<App /\>
\</BrowserRouter\>,
document.getElementById('root')
)
I have tried to add the styling-loader to the webpack config for the frontend but I end up getting an error where the document is not defined. The routing works fine, just no css or images, just basic html is being rendered`

React SSR express,webpack,babel babel doesn't like the CSS in my components

I have been trying to learn how to SSR with React(without using Next.js) I've gotten to the point that I'm seeing the HTML rendered on the page but once I add the css imports to style the component babel start throwing errors (
C:\Users\Frozen\Desktop\ssr stuff\ssr4\src\App.css:1
body {
^
SyntaxError: Unexpected token '{'
)
.babelrc
{
"presets": ["#babel/preset-env", "#babel/preset-react"],
}
webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './build'),
filename: "bundle.js",
publicPath: "/"
},
devServer: {
port: 3010,
static: true,
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ['#babel/preset-env', "#babel/preset-react"]
}
},
},
{
test: /\.(css|scss)$/,
use: ["style-loader", "css-loader", "sass-loader"],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
publicPath: "/"
})
]
};
server.js
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './src/App.js';
import path from 'path';
import fs from 'fs';
const app = express();
app.get('/', (req, res) => {
console.log('in /');
fs.readFile(
path.resolve('./build/index.html'),
'utf8',
(err, data) => {
if (err) {
console.log(err);
return res.status(500).send('Internal Server Error');
}
const html = data.replace(
'<div id="root"></div>',
`<div id="root">${ReactDOMServer.renderToString(<App />)}</div>`
)
console.log(html)
return res.send(html);
}
);
});
app.use(express.static(path.resolve(__dirname,'build')));
app.listen(3000, () => {
console.log('server listening on port 3000')
})
I also fail to understand why do I have to put the app.use(static) below the app.get('/') aren't middlewares supposed to go one after another in order top to bottom (if I move the app.use(static) above the app.get('/') where I return the component I don't see the console.log('in /');
index.js
import React from "react";
import ReactDOM from 'react-dom'
import App from "./App";
const appElement = document.getElementById('root');
ReactDOM.hydrate(<App/>, appElement)
I run
npx webpack
npx babel-node server.js
And I get the css error (if I remove the css import I don't get it but I don't have css in the component)
Also is there a way to rebuild and run server.js once I change something ( like when you use create-react-app ) Also any other suggestions on what I can improve in the current setup will be much appreciated.
Install babel-plugin-css-modules-transform npm install --save-dev babel-plugin-css-modules-transform.Then include it in .babelrc "plugins": ["css-modules-transform"]

Angular Universal, infinite loading error

Summary: Long question so here's the quick summary. I can run the commands ng build && ng run app-name:server and npm run dev:server successfully and see no errors on console whenever I try to connect to server. But page is loading infinitely. I have tried to apply NgxSsrTimeoutModule.forRoot({ timeout: 500 }) nothing changed. Made other changes but saw no progress. Really stuck at this point having headaches.
Hello,
I'm trying to convert a SPA Angular application to an SSR-compatible one. Application is a little bit chunky, so I couldn't create a new SSR rendered application and move the modules to that one, one by one. First, my versions are:
#angular-devkit/architect 0.1301.2
#angular-devkit/build-angular 13.1.2
#angular-devkit/core 13.1.2
#angular-devkit/schematics 13.1.2
#angular/cli 13.1.2
#angular/platform-server 13.1.3
#nguniversal/builders 13.1.1
#nguniversal/express-engine 13.0.2
#schematics/angular 13.1.2
rxjs 6.6.7
typescript 4.5.4
I have made some changes to get development server working. First change was on angular.json. I had error Angular Universal Configuration 'development' is not set in the workspace:
"serve-ssr": {
"builder": "#nguniversal/builders:ssr-dev-server",
"configurations": {
"production": {
"browserTarget": "appName:build:production",
"serverTarget": "appName:server:production"
},
"development": ...
},
"defaultConfiguration": "development"
},
First change was on angular.json. Referring to this issue
I have changed as this:
"serve-ssr": {
"builder": "#nguniversal/builders:ssr-dev-server",
"options": { "browserTarget": "appName:build", "serverTarget": "appName:server"},
"configurations": {
"production": {
"browserTarget": "appName:build:production",
"serverTarget": "appName:server:production"
}
}
},
Second change was on ServerModule.
We had an application starter basically an Angular service requesting an appConfig.json to make application configurations. I had Could not load file '../../assets/config/appConfig.json' which is an error thrown from that service. So basic ServerModule changed as this;
import { NgModule, Inject } from '#angular/core';
import { INITIAL_CONFIG, PlatformConfig, ServerModule } from '#angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
import { NgxSsrTimeoutModule } from '#ngx-ssr/timeout';
#NgModule({
imports: [
AppModule,
ServerModule,
NgxSsrTimeoutModule.forRoot({ timeout: 500 })
],
bootstrap: [AppComponent],
})
export class AppServerModule {
constructor(#Inject(INITIAL_CONFIG) private config: PlatformConfig) {
this.config.useAbsoluteUrl = true;
}
}
Also solved the "window not defined" error by adding domino library
to the server code.
After changing this, I could successfully run the comman npm run dev:ssr. Take note that, before doing this, I could run the command ng build && ng run app-name:server but not npm run dev:ssr. And after that, I had a running Node server on my localhost:4200 but page is always in loading state, I could see nothing on network tab (Chrome Dev Tools), no client side application taking action and nothing on console (just says Messages received, value changed...). After all these, I implemented *appShellRender and *appShellNoRender directives, and just decided to render a loading animation in the server, nothing changed;
app.component.ts:
<div *appShellRender>
<app-loading-animation></app-loading-animation>
</div>
<div *appShellNoRender>
<ng-container appCardQuery appSystemIdTracker>
<mat-sidenav-container>
<mat-sidenav #sidenav>
<app-side-nav (closeSideNav)="sidenav.close()"></app-side-nav>
</mat-sidenav>
<mat-sidenav-content (scroll) = "onScroll($event)">
<ng-container *ngIf="isToolbarOn">
<app-toolbar (SideNavToggle)="sidenav.toggle()"></app-toolbar>
</ng-container>
<main>
<!-- <div style="background-color: black; width: 250px;height: 250px;" (click)="testClick()"></div> -->
<router-outlet></router-outlet>
</main>
<div class="spacer30"></div>
<ng-container *ngIf="isAppAboutOn">
<app-about></app-about>
</ng-container>
</mat-sidenav-content>
</mat-sidenav-container>
</ng-container>
</div>
server.ts:
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '#nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '#angular/common';
import { existsSync } from 'fs';
const domino = require('domino');
// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
const server = express();
const distFolder = join(process.cwd(), 'dist/appName/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
const win = domino.createWindow(indexHtml);
global['window'] = win;
global['document'] = win.document;
global['navigator'] = win.navigator;
global['screen'] = win.screen;
// Our Universal express-engine (found # https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
server.set('view engine', 'html');
server.set('views', distFolder);
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
// All regular routes use the Universal engine
server.get('*', (req, res) => {
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});
return server;
}
function run(): void {
const port = process.env['PORT'] || 4000;
// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}
export * from './src/main.server';
Error Possibilities
I see no errors on console but there's obviously something wrong.
We're using AgmCoreModule which is an Angular component to use Maps on application. Also leaflet.
index.js:
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin="" />
<script src="https://unpkg.com/leaflet#1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script>
These might be causing errors?

Custom 404 page using Angular and Express

I am using a MEAN stack and I am trying to build a custom 404 page for my project using Angular.
In Angular app.module.ts I have written this:
const appRoutes: Routes = [
{
path: 'students',
component: StudentComponent,
data: {title: 'Student List'}
},
{
path: 'student-details/:id',
component: StudentDetailComponent,
data: {title: 'Student Details'}
},
{
path: 'student-create',
component: StudentCreateComponent,
data: {title: 'Create a new Student'}
},
{
path: 'student-edit/:id',
component: StudentEditComponent,
data: {title: 'Edit Student'}
},
{
path: '',
redirectTo: '/students',
pathMatch: 'full'
},
{
path: '**',
component: PageNotFoundComponent,
data: {title: 'Page Not Found'}
}
];
#NgModule({
declarations: [
AppComponent,
StudentComponent,
StudentDetailComponent,
StudentCreateComponent,
StudentEditComponent,
PageNotFoundComponent,
],
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
RouterModule.forRoot(
appRoutes,
{enableTracing: true}
)
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
}
But in fact I think that when I write an URL that doesn't exist Express make a response, not Angular. I know that Express is answering because, when I removed this code from server.js:
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
When I write an url that doesn't exist, I get the answer "Cannot GET /urlThatDoesntExist".
The matter is that the other routing urls set in Angular are working but the 404 page is not.
I am using Express to serve the Angular app using:
app.use('/', express.static(path.join(__dirname, 'dist')));
How can I set the Angular 404 page working above Express? Thanks!
If you want every request to reach angular (so you can handle 404s on the client side) you can use an express route like this:
// all routes lead to to index.html
const router = express.Router();
router.get('*', (req, res) => {
res.sendFile(path.join(DIR, 'index.html'));
});
this.express.use('*', router);

Node/Express angular 5 routing

I am creating an application using angular5 and express 4.16. I have anchor tag on home page on click of which another component should be rendered (it should be redirected to that URL) but only URL changes.
Required code is below.
app-routing.module.ts
import { NgModule } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
import { AppComponent } from './app.component';
import { TestComponent } from '../test/test.component';
const routes: Routes = [
{ path: '', redirectTo: '/', pathMatch: 'full' },
{ path: 'Test', component: TestComponent }
];
#NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {}
app.component.ts
import { Component } from '#angular/core';
// Import the DataService
import { DataService } from './data.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
// Define a users property to hold our user data
employees: Array<any>;
// Create an instance of the DataService through dependency injection
constructor(private _dataService: DataService) {
// Access the Data Service's getUsers() method we defined
this._dataService.getEmployees()
.subscribe(res => this.employees = res);
}
}
app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { AppComponent } from './app.component';
import {TestComponent } from '../test/test.component';
import { AppRoutingModule } from './app-routing.module';
import{DataService} from './data.service'
import {HttpModule} from '#angular/http'
#NgModule({
imports: [
BrowserModule,
FormsModule,
AppRoutingModule,
HttpModule
],
declarations: [
AppComponent,
TestComponent
],
providers: [DataService],
bootstrap: [AppComponent]
})
export class AppModule { }
out put of folder through ng build /dist/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>IndexV3</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
<test-html></test-html>
<script type="text/javascript" src="inline.bundle.js"></script>
<script type="text/javascript" src="polyfills.bundle.js"></script>
<script type="text/javascript" src="styles.bundle.js"></script>
<script type="text/javascript" src="vendor.bundle.js"></script>
<script type="text/javascript" src="main.bundle.js"></script>
</body>
</html>
Node/Express server.js
const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const http = require('http');
const app = express();
// API file for interacting with MongoDB
const api = require('./server/routes/api');
// Parsers
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false}));
// Angular DIST output folder
app.use(express.static(path.join(__dirname, 'dist')));
// API location
app.use('/api', api);
// Send all other requests to the Angular app
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist/index.html'));
});
//Set Port
const port = process.env.PORT || '3000';
app.set('port', port);
const server = http.createServer(app);
server.listen(port, () => console.log(`Running on localhost:${port}`));
Output of home page as desired
Downoad screenshot Output after running localhost:3000 as desired anchor tag rendered
After click on anchor tag url changed but test component didnt rendered on browser
Downoad screenshot
test.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'test-html',
templateUrl: './test.component.html'
})
export class TestComponent {
title = 'app';
}
app.component.html
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
<h1>
</h1>
</div>
<a [routerLink]="['/Test']" >test</a>
<ul>
<li *ngFor="let employee of employees">{{employee.name}}</li>
</ul>
test.component.html
<span>yeeee</span>
Project Structure
download project structure if required
You are missing the router-outlet. Take the template you have for app.component and create a new component with that template. Then make your app.component.html contain only
<router-outlet></router-outlet>
Then in your app.routing.module.ts replace the object for your home route with:
{ path: '', component: YourNewHomeComponent },
more about router-outlet: https://angular.io/guide/router#router-outlet

Resources