Angular Universal page loading/refreshing issue - node.js

I have added Angular Universal in my project(upgraded to angular 8). Which is comparatively a big project. After solving all the window, jquery and localstorage issue I have successfully able to run the app. But the problem is, the server runs on localhost:4000. when I type localhost:4000 in browser nothing happens. But if I put localhost:4000/index.html, It starting to load fine and return to base localhost:4000. Then every angular route works fine. But if I try to refresh any page it doesn't load anything.
my server.ts
import 'zone.js/dist/zone-node';
// ssr DOM
const domino = require('domino');
const fs = require('fs');
const path = require('path');
const template = fs.readFileSync(path.join('dist/browser', 'index.html')).toString();
const win = domino.createWindow(template);
global['window'] = win;
Object.defineProperty(win.document.body.style, 'transform', {
value: () => {
return {
enumerable: true,
configurable: true,
};
},
});
// mock documnet
global['document'] = win.document;
// othres mock
global['CSS'] = null;
// global['XMLHttpRequest'] = require('xmlhttprequest').XMLHttpRequest;
global['Prism'] = null;
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';
// The Express app is exported so that it can be used by serverless Functions.
export function app() {
const server = express();
const distFolder = join(process.cwd(), 'dist/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
// 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
// app.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() {
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;
if (mainModule && mainModule.filename === __filename) {
run();
}
export * from './src/main.server';
I think i need some change with this file to load correctly.

Related

Angular + Node + Universal remove # with working refresh

I am using Angular 13 + Angular Universal + Node.js.
I have deployed app on server, but I have big problem with removing # from URL.
I have built app using
npm run build:ssr
And I am serving it using node.js app on my server using :
app.use(express.static(__dirname + '/dist/app-name/browser'));
app.get('/', (req, res) => res.sendFile(path.join(__dirname)));
When I use HashLocationStrategy, then everything works fine. Problem appears when I am trying to remove hash from link.
I changed it to PathLocationStrategy and I am getting error 404 on refresh on other path than '/'.
I was trying to add different routes in node.js file to sendFile (f.e.)
app.get('/subpage', (req, res) => res.sendFile(path.join(__dirname)));
But it didn't worked for me.
I was also trying to overwite # with node.js, but as it comes from angular app it's impossible to do it from node.js code.
My files :
app.server.module.ts
import { NgModule } from '#angular/core';
import { ServerModule } from '#angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
import {UniversalInterceptor} from './universal-interceptor.service';
import {HTTP_INTERCEPTORS} from '#angular/common/http';
import {NgxSsrTimeoutModule} from '#ngx-ssr/timeout';
#NgModule({
imports: [
AppModule,
ServerModule,
NgxSsrTimeoutModule.forRoot({ timeout: 5000 }),
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: UniversalInterceptor,
multi: true,
}
],
bootstrap: [AppComponent],
})
export class AppServerModule {}
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';
// 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/app-name/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
// 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) => {
res.status(404).send('data requests are not yet supported');
});
// 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';
How to to deploy app without '#' in url with working refresh?
Okay, so I finally found solution for this. My problem was, that I needed to give absolute path to my index.html file for all angular routes. So finally i changed
app.get('/', (req, res) => res.sendFile(path.join(__dirname)));
to
app.get('/**', (req, res) => {
res.sendFile(path.join(__dirname + '/dist/app-name/browser/index.html'))
});
Try this:
app.get('*', (req, res) => res.sendFile(path.join(__dirname)));
put it near the end middleware chain

How to do an app logout using nodejs and okta

I am currently using Okta with OIDC and Node Express to log into a web app that I made. I want my users to be able to logout of just my app and not okta itself as the okta is for my company and that would log them out of all the other websites that use the company okta. Okta recommends doing this on their website.
app.get('/local-logout', (req, res) => {
req.logout();
res.redirect('/');
});
(https://developer.okta.com/docs/guides/sign-users-out/nodeexpress/sign-out-of-your-app/)
I have tried implementing this but it doesn't work. I have also tried using req.session.destroy, res.clearCookie("connect.sid"), req.session = null and many different combinations of these with or without callbacks. Whenever I redirect, it just goes back to the homepage and the same user is logged in. Whenever I try deleting the connect.sid cookie it deletes and then is reinitialized when the user is redirected. I'm not really sure what to do. My code with irrelevant endpoints removed is below.
require('dotenv').config();
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '#nguniversal/express-engine';
import { join } from 'path';
import * as express from 'express';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '#angular/common';
import { existsSync, read } from 'fs';
// 30 minutes
const sessionMaxAge = 1800000;
import * as e from 'cors';
import { resolveForwardRef } from '#angular/core';
import { User } from '#okta/okta-sdk-nodejs';
const session = require('express-session');
const { ExpressOIDC } = require('#okta/oidc-middleware');
const cookieParser = require("cookie-parser");
/** Creates the OpenID Connect Middleware and configures it to work with Okta */
const oidc = new ExpressOIDC({
appBaseUrl: process.env.HOST_URL,
issuer: `${process.env.OKTA_ORG_URL}/oauth2/default`,
client_id: process.env.OKTA_CLIENT_ID,
client_secret: process.env.OKTA_CLIENT_SECRET,
redirect_uri: process.env.REDIRECT_URL,
scope: 'openid profile',
routes: {
loginCallback: {
path: '/authorization-code/callback'
},
}
});
// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
var root_folder: string = process.env.ROOT_FOLDER || "dist/angular-app/browser"
const server = express();
const distFolder = join(process.cwd(), root_folder);
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
// Our Universal express-engine (found # https://urldefense.proofpoint.com/v2/url?u=https-3A__github.com_angular_universal_tree_master_modules_express-2Dengine&d=DwIGAg&c=-35OiAkTchMrZOngvJPOeA&r=03NdPO1x-l0QAZ_R9TNGwA&m=WF5ia-YADjCituVWMV5vLoZ5wg7d_W1qhCYDTbJNGT0&s=WPOkeRsetPDQ6TrD26RKLo1m9_zxBfQXGhUUSkth0Ew&e= )
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
server.set('view engine', 'html');
server.set('views', distFolder);
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
// Configure Session Support
server.use( session({
secret: process.env.APP_SECRET,
resave: false,
saveUninitialized: false,
cookie: {maxAge: sessionMaxAge}
})
);
server.use(cookieParser());
server.use(oidc.router);
// Log the user out of the local session
server.get('/local-logout',(req:any, res:any) => {
req.logout();
res.redirect('/');
});
// All regular routes use the Universal engine
server.get('*', oidc.ensureAuthenticated({ redirectTo: '/login' }), (req: any, res: any) => {
if(req.session && req.session.username != null) {
console.log(process.env);
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
}
else {
console.log("getting user info");
getUserInfo(req)
.then(userInfo => {
req.session.username = userInfo.username;
req.session.group = userInfo.group;
if (userInfo.group=="unauthorized") {
res.sendFile('./403.html', {root: "."})
}
else{
console.log(process.env);
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 https://urldefense.proofpoint.com/v2/url?u=http-3A__localhost-3A-24-257Bport-257D&d=DwIGAg&c=-35OiAkTchMrZOngvJPOeA&r=03NdPO1x-l0QAZ_R9TNGwA&m=WF5ia-YADjCituVWMV5vLoZ5wg7d_W1qhCYDTbJNGT0&s=PUb7XMS4uP9ICqUY28QgXNRxoWk6sGatPdmZMqmgbJs&e= `);
});
}
// 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';
After an exhaustive search, I don't think it is possible. I ended up doing req.logout() and then redirecting to a page that says "you have been logged out of the app but may still be logged into your single sign on provider."

Angular Universal Server Side Rendering (SSR) res.render not loading

I am trying to add SSR to my web site but the content is not loaded. I am using Angular Universal, I followed this guide for the initial configuration. http://localhost:4200/ it is not finishing loading and no errors been shown. http://localhost:4200/index.html is returning a empty view.
The build process successfully.
server.ts
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '#nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
const path = require('path');
const fs = require('fs');
const domino = require('domino');
const templateA = fs.readFileSync(path.join('dist/web/browser', 'index.html')).toString();
const win = domino.createWindow(templateA);
global['window'] = win;
global['document'] = win.document;
// Express server
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '#angular/common';
import { existsSync } from 'fs';
// 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/web/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index.html';
// 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';
tsconfig.server.json
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.app.json",
"compilerOptions": {
"outDir": "./out-tsc/server",
"target": "es2016",
"types": [
"node"
],
"module": "commonjs"
},
"files": [
"src/main.server.ts",
"server.ts"
],
"angularCompilerOptions": {
"entryModule": "./src/app/app.server.module#AppServerModule"
}
}
I found a solución.
I followed this steps to realize what was wrong:
Comment all imports and routings from app.module.ts, I just leve the app.component.html with a simple tag just to show something. In this step the view start loading.
Add module per module and see where stops loading.
Where stops I comment line per line to see where is the mistake.
The main mistakes that I found was the order of the routing imports on app.module.ts and sub-modules,
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule.withServerTransition({ appId: 'serverApp' }),
FormsModule,
ReactiveFormsModule,
BrowserAnimationsModule,
HttpClientModule,
AppRoutingModule, // <-- here
],
providers: [{ provide: HTTP_INTERCEPTORS, useClass: RequestInterceptorService, multi: true }],
bootstrap: [AppComponent]
})
export class AppModule {}
another was that in some constructor components I was doing an api call but fails because the authentication. So I need to add a validation for only do it if is a platform browser:
constructor(#Inject(PLATFORM_ID) private platformId: Object) {
if (isPlatformBrowser(this.platformId)) {
// api call
}
}
Fortunately my project it isn't very big yet.

WebSocket with angular universal

I'm trying to add websockets to a server that's using angular universal. As far as I can tell, express is consuming my request before it gets to my sockets, but I could be wrong about that.
I get this error in chrome:
WebSocket connection to 'ws://localhost:4200/socket' failed: Connection closed before receiving a handshake response
and I get this error in firefox:
Firefox can’t establish a connection to the server at ws://localhost:4200/socket.
when I run a separate nodejs server without the angular code, the websockets work fine.
Here is the relevant part of my 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'
import * as WebSocket from 'ws'
// The Express app is exported so that it can be used by serverless Functions.
export function app() {
const server = express()
const distFolder = join(process.cwd(), 'dist/CAHClone/browser')
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index'
// 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)
server.use(express.json())
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}))
// All regular routes use the Universal engine
server.get('*', (req, res, next) => {
if (req.url === '/socket') return next() // leave '/socket' open for the websockets
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] })
})
return server
}
function run() {
const port = process.env.PORT || 4000
// Start up the Node server
const expressApp = app()
const server = expressApp.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`)
})
// Websockets
const wss = setupWebsockets(server)
}
function setupWebsockets(server) {
const wss = new WebSocket.Server({ server, path: '/socket' })
wss.on('connection', ws => {
console.log('Client connected.')
ws.send({message: 'Hi there!'})
})
wss.on('message', msg => {
console.log('Client said: ' + msg.toString())
})
return wss
}
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'
And here is a server.js file that works without angular related code
const express = require('express')
const WebSocket = require('ws')
const { join } = require('path')
const { existsSync } = require('fs')
// The Express app is exported so that it can be used by serverless Functions.
function app() {
const server = express()
server.use(express.json())
server.use('*', (req, res, next) => {
if (req.url === '/socket') return next()
res.end('Hello, world!')
})
return server
}
function setupWebsockets(server) {
const wss = new WebSocket.Server({ server, path: '/socket' })
console.log('set up websocket server');
wss.on('connection', ws => {
console.log('Client connected.');
ws.send('Hi there!')
})
wss.on('message', msg => {
console.log('Client said: ' + msg.toString());
})
}
function run() {
const port = process.env.PORT || 4000
const expressApp = app()
const server = expressApp.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`)
})
// Websockets
const wsServer = setupWebsockets(server)
}
run()
Could anyone help me understand what part of angular universal is breaking my websockets, and how to fix it?
I ran into this issue with the 'ws' module and passing in an express server. Try creating your server with the 'http' module and pass it into both express and 'ws'. I also recommend taking care of any authorization that you are going to do in the 'upgrade' event if necessary.
Did you solve the matter with dev:ssr ?
It is indeed working fine with serve:ssr but the only way I made it work with dev:ssr in local development is by using a proxy from ws://localhost:4200/ws to ws://localhost:4000
{
"/ws": {
"target": "ws://localhost:4000",
"secure": false,
"ws": true
}
}
Se my question here : Angular Universal (ssr-dev-server) Setting or getting the random port of the express app in dev:ssr

Error: ENOENT: no such file or directory, open 'dist/index.html'

So, I've been trying for days to get a angular universal app running but I keep getting this issue when I try to run the server like
npm run dev:ssr
I have set my server.ts file as in the below link
https://github.com/Angular-RU/angular-universal-starter/blob/master/server.ts
My server.ts file is as below
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';
// ssr DOM
const domino = require('domino');
const fs = require('fs');
const path = require('path');
// index from browser build!
const template = fs.readFileSync(path.join('.', 'dist', 'index.html')).toString();
// for mock global window by domino
const win = domino.createWindow(template);
// from server build
const files = fs.readdirSync(`${process.cwd()}/dist-server`);
// mock
global['window'] = win;
// not implemented property and functions
Object.defineProperty(win.document.body.style, 'transform', {
value: () => {
return {
enumerable: true,
configurable: true,
};
},
});
// mock documnet
global['document'] = win.document;
// othres mock
global['CSS'] = null;
// global['XMLHttpRequest'] = require('xmlhttprequest').XMLHttpRequest;
global['Prism'] = null;
// The Express app is exported so that it can be used by serverless Functions.
export function app() {
const server = express();
const distFolder = join(process.cwd(), 'dist');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
// 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() {
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';
my app.server.module.ts file:
import { NgModule } from '#angular/core';
import { ServerModule } from '#angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
#NgModule({
imports: [
AppModule,
ServerModule,
],
bootstrap: [AppComponent],
})
export class AppServerModule {}
I am using Angular 9 and at a stage where I am thinking of dropping the idea of using angular universal at all. Seems it its way too unstable to be used at the moment.
Contents of dist folder:
Does anyone here have a solution to this?
You incorrectly set the distFolder variable. The distFolder from server.ts must point to the files containing the client side app, which is dist\YourProjectName\browser in your app. Note that this configured in angular.json file.
To correct your error, try changing the distFolder path in your server.ts
const distFolder = join(process.cwd(), 'dist','YourProjectName', 'browser');

Resources