Angular 4 Server Side Rendering ngx-bootstrap carousel makes response hang - node.js

So I'm trying to implement server side rendering on my angular 4 website and I think I'm pretty close. I have gotten all routes to render server side except for 1 specific route and the default route. The routes that do not work are /home, /anythingthatisntdefined, and (no route at all loads, but does not server side render the home page).
So the no route, just isn't getting picked up by my catch all route... the anything that isn't defined I'm assuming the server is not correctly processing the default route... and the /home route not loading, just doesn't make sense to me. Any tips?
Here is my server.ts
import 'reflect-metadata';
import 'zone.js/dist/zone-node';
import { platformServer, renderModuleFactory } from '#angular/platform-server';
import { enableProdMode } from '#angular/core';
import { AppServerModuleNgFactory } from '../dist/ngfactory/src/app/app.server.module.ngfactory';
import * as express from 'express';
import { readFileSync } from 'fs';
import { join } from 'path';
const PORT = 4000;
enableProdMode();
const app = express();
let template = readFileSync(join(__dirname, '..', 'dist', 'index.html')).toString();
app.engine('html', (_, options, callback) => {
console.log('url: ', options.req.url);
const opts = { document: template, url: options.req.url };
renderModuleFactory(AppServerModuleNgFactory, opts)
.then(html => callback(null, html));
});
app.set('view engine', 'html');
app.set('views', 'src')
app.use(express.static(join(__dirname, '..', 'dist')));
app.get('*', (req, res) => {
console.log('caught by *');
res.render('../dist/index.html', {
req: req,
res: res
});
});
app.listen(PORT, () => {
console.log(`listening on http://localhost:${PORT}!`);
});
Here is my router
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { ServicesComponent } from './services/services.component';
import { ResourcesComponent } from './resources/resources.component';
import { ChurchesComponent } from './churches/churches.component';
import { GetAQuoteComponent } from './get-a-quote/get-a-quote.component';
const routes: Routes = [
{
path: 'home',
component: HomeComponent
},
{
path: 'services',
component: ServicesComponent
},
{
path: 'resources',
component: ResourcesComponent
},
{
path: 'about',
component: AboutComponent
},
{
path: 'churches',
component: ChurchesComponent
},
{
path: 'get-a-quote',
component: GetAQuoteComponent
},
{
path: '**',
component: HomeComponent
}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

Good news, I figured out why the one route/ random routes weren't loading. The ngx-bootstrap carousel (which I had on my home route) has an interval that needs to be disabled server side. Otherwise the route will hang forever and never load.
Reference: https://github.com/valor-software/ngx-bootstrap/issues/1353
And last, the fact of the no route not working was because of my static assets route which was intercepting the request before my express.get was.

Related

ngx-socket-io cannot connect to server because of unsupported protocol version

I'm building a web app with angular frontend and a NodeJs server on backend, both run on different places. I'm trying to use ngx-socket-io on angular to connect to my server but I have an error :
{
"code": 5,
"message": "Unsupported protocol version"}
on the browser console.
This is my code :
app-module.ts (Angular)
...
import { IoRequestService } from './services/ioRequest.service'
import { SocketIoModule, SocketIoConfig } from 'ngx-socket-io';
const config: SocketIoConfig = { url: 'http://localhost:8080', options: {transports: [ "websocket" ], withCredentials: true} };
#NgModule({
...
imports: [
BrowserModule,
SocketIoModule.forRoot(config),
AppRoutingModule,
FormsModule,
GoogleChartsModule
],
providers: [
IoRequestService
],
bootstrap: [AppComponent]
})
export class AppModule { }
Io service
import { Socket } from 'ngx-socket-io';
import { Injectable } from '#angular/core';
import { map } from 'rxjs/operators';
#Injectable()
export class IoRequestService {
constructor(private socket: Socket) { }
sendMessage(msg: string) {
this.socket.emit("message", msg);
console.log("msg send");
}
getMessage() {
return this.socket
.fromEvent("message")
.pipe(map((data:any) => data.msg));
}
}
use of io service :
...
export class TransactionsComponent implements OnInit {
transactions: Transaction[] = [];
constructor(private serviceIO: IoRequestService) { }
ngOnInit(): void {
}
send() {
console.log("button clicked");
this.serviceIO.sendMessage("test");
}
}
And nodeJS backend :
var http = require('http');
var express = require('express');
var mysql = require('mysql');
var app = express();
var server = http.createServer(app);
const io = require('socket.io')(server, {
cors: {
origin: "http://localhost:4200",
methods: ["GET", "POST"],
credentials: true,
allowEIO3: true
}
});
var connection = mysql.createConnection({
host : 'localhost',
user : 'student',
password : 'std___01',
database : 'elevage'
});
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.set('views', __dirname);
app.get('/', function (req, res) {
res.sendFile(__dirname + '/index.html');
});
io.on('connection', (socket) => {
console.log('a user connected');
socket.on('disconnect', () => {
console.log('user disconnected');
});
socket.on('message', (socket)=> {
console.log('I win the game');
})
});
//handle unfound pages
app.use(function(req, res, next){
res.setHeader('Content-Type', 'text/plain');
res.status(404).send('Page introuvable !');
});
server.listen(8080, (err) => {
if (err) {
return console.log('something bad happened', err);
}
console.log(`server is on`);
});
Any Idea what the problem is? I saw that people also use socket.io-client but ngx-socket-io seems way easier, maybe that's the issue but everything seems ok when I look at the docs.
There (https://socket.io/docs/v3/migrating-from-2-x-to-3-0/index.html) I found that issue may be solved by setting allowEIO3: true, but it didn't worked.
Or maybe it comes from the browser? I tried on both firefox and safari latest version and I have the same issue.
When I install the npm package, I had to force it. It may encounter issue because of the repesitories structures? which look like that :
project
angular-app
node_module (for Angular)
nodejs-app
node_module (for NodeJs)
Are you requiered to use ngx-socket-io ?
I had the same issue and replaced it by socket.io-client version 3.1.2
It seems to work fine for me with this package.

How do I use nunjucks template in nestjs framework?

How can I use nunjucks template under NestExpressApplication in right way?
For those who would want to use Nunjucks templating engine with NestJS framework and found this question, here is a minimal example:
import { NestFactory } from '#nestjs/core';
import { NestExpressApplication } from '#nestjs/platform-express';
import * as nunjucks from 'nunjucks';
import * as path from 'path';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
const express = app.getHttpAdapter().getInstance();
const assets = path.join(__dirname, '..', 'assets'); // Directory with static HTML/CSS/JS/other files
const views = path.join(__dirname, '..', 'views'); // Directory with *.njk templates
nunjucks.configure(views, { express });
app.useStaticAssets(assets);
app.setBaseViewsDir(views);
app.setViewEngine('njk');
await app.listen(3000);
}
bootstrap();
import { join } from "path";
import { NestFactory } from "#nestjs/core";
import { NestExpressApplication } from "#nestjs/platform-express";
import * as nunjucks from "nunjucks";
import * as helmet from "helmet";
import { ApplicationModule } from "./app.module";
async function bootstrap() {
let options: any = {};
if (process.env.NODE_ENV === "product") options.logger = false;
const app = await NestFactory.create<NestExpressApplication>(
ApplicationModule,
options
);
app.use(helmet());
// app.useGlobalPipes(
// new ValidationPipe({
// whitelist: true,
// validationError: { target: false, value: false },
// }),
// );
app.useStaticAssets(join(__dirname, "..", "public"), { prefix: "/static/" });
const environment = nunjucks.configure(
[
join(__dirname, "..", "template"),
join(__dirname, ".", "system_template")
],
{
autoescape: true,
throwOnUndefined: false,
trimBlocks: false,
lstripBlocks: false,
watch: true,
noCache: process.env.NODE_ENV === "local" ? true : false,
express: app
}
);
app.engine("njk", environment.render);
app.setViewEngine("njk");
app.set("view cache", true);
await app.listen(process.env.APP_PORT);
}
bootstrap();

Angular Universal meta tags not being seen by Facebook

I'm trying to get the relevant meta tags to show on my article pages for Facebook sharing. I'm using Angular Universal and it's Server-side rendering. Google Indexing works, and the meta tags appear in the page source, so I know SSR is working, However, the Facebook Crawler for reason can't see them.
Solutions I've tried are using the Meta module from #angular/platform-browser
import { Meta } from '#angular/platform-browser';
this.meta.updateTag({ property: 'og:type', content: 'article' });
this.meta.updateTag({ property: 'og:site_name', content: 'AdriaOffer' });
this.meta.updateTag({ property: 'og:title', content: config.title });
this.meta.updateTag({ property: 'og:description', content: config.description });
this.meta.updateTag({ property: 'og:image', content: config.image });
this.meta.updateTag({ property: 'og:url', content: config.slug });
I also found ngx-meta - https://github.com/fulls1z3/ngx-meta/
Which worked if you added the meta data within the route, e.g (taken from their npm page)
...
import { MetaGuard } from '#ngx-meta/core';
...
export const routes: Routes = [
{
path: '',
canActivateChild: [MetaGuard],
children: [
{
path: 'home',
component: HomeComponent,
data: {
meta: {
title: 'Sweet home',
description: 'Home, home sweet home... and what?'
}
}
},
{
path: 'duck',
component: DuckComponent,
data: {
meta: {
title: 'Rubber duckie',
description: 'Have you seen my rubber duckie?'
}
}
},
{
path: 'toothpaste',
component: ToothpasteComponent,
data: {
meta: {
title: 'Toothpaste',
override: true, // prevents appending/prepending the application name to the title attribute
description: 'Eating toothpaste is considered to be too healthy!'
}
}
}
]
}
...
];
But this solution doesn't work for me, as I need the data to be added dynamically, which seems to be a known issue - https://github.com/fulls1z3/ngx-meta/issues/118, or at least someone else has reported the same problem.
I've spent far too long on this, and an interim hack solution would also be fine, thinking my server.ts (which was automatically generated) could be updated somehow?
server.ts file for reference
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import {enableProdMode} from '#angular/core';
import {ngExpressEngine} from '#nguniversal/express-engine';
import {provideModuleMap} from '#nguniversal/module-map-ngfactory-loader';
import * as express from 'express';
import * as bodyParser from 'body-parser';
import * as cors from 'cors';
import * as compression from 'compression';
import {join} from 'path';
enableProdMode();
export const app = express();
app.use(compression());
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// const DIST_FOLDER = join(process.cwd(), 'dist');
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main');
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', './dist/browser');
app.get('/redirect/**', (req, res) => {
const location = req.url.substring(10);
res.redirect(301, location);
});
app.route('/sitemap.xml')
.get((req, res) => {
const DIST_FOLDER = join(process.cwd(), 'dist');
res.sendFile(join(DIST_FOLDER,'sitemap.xml'));
});
app.get('*.*', express.static('./dist/browser', {
maxAge: '1y'
}));
app.get('/*', (req, res) => {
res.render('index', {req, res}, (err, html) => {
if (html) {
res.send(html);
} else {
console.error(err);
res.send(err);
}
});
});
ngx-meta work only for root urls not for dynamics urls, like you isn't work for facebook linkedin, ... .
But Meta of #angular/platform-browser work better when inspecting my générated page i find my metas.

NodeJS compiler server/client static class inheritance error

I have a few issues.
I'm currently coding a CMS using React for client side with Webpack and Express for server side with Babel.
What I am trying to do is setup a common X-CSRF-Token header on the axios module on my client side that gets a token generated from a shared file that the server side uses to verify the token.
The problem is that, when I use two different compilers, the static class inheritance doesn't work, so instead of verifying the existing generated token inside the class, it instead generates a whole new.
Would it work if I somehow managed to use the same Webpack config for both server and client side?
When I start up my server, I use this npm script:
nodemon --watch server --exec babel-node -- server/index.js
For the client side, I use Webpack HMR with this config:
import path from 'path'
import webpack from 'webpack'
const clientPath = path.join(__dirname, 'client')
//const bundlePath = path.join(__dirname, 'static', 'js')
export default {
devtool: 'eval-source-map',
entry: [
'webpack-hot-middleware/client',
clientPath
],
output: {
filename: 'bundle.js',
path: '/',
publicPath: '/'
},
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin()
],
module: {
loaders: [{
test: /\.js$/,
include: clientPath,
loaders: ['react-hot-loader', 'babel-loader']
}]
},
resolve: {
extensions: ['.js']
}
}
server/index.js
So in my server index file, I send the index.html file that has the bundle.js required.
import express, {app} from './app'
import config from '../config.json'
import path from 'path'
import './database'
const staticPath = path.join(__dirname, '..', 'static')
app.use((err, req, res, next) => {
if (err) {
res.status(err.statusCode || err.status || 500)
.send(err.data || err.message || {})
} else {
next()
}
})
app.use('/api', require('./api'))
app.use(express.static(staticPath))
app.get('*', (req, res) =>
res.sendFile(path.join(staticPath, 'index.html'))
)
app.listen(config.port, () =>
console.log(`Listening on port: ${config.port}`)
)
server/app.js
And in my server app file I initialize the Webpack and other middleware:
import webpackHotMiddleware from 'webpack-hot-middleware'
import webpackMiddleware from 'webpack-dev-middleware'
import expressSession from 'express-session'
import bodyParser from 'body-parser'
import webpack from 'webpack'
import express from 'express'
import cors from 'cors'
import path from 'path'
import webpackConfig from '../webpack.config'
const compiler = webpack(webpackConfig)
export const router = express.Router()
export const app = express()
.use(cors())
.use(bodyParser.urlencoded({ extended: false }))
.use(bodyParser.json())
.use(expressSession({
secret: 'test',
resave: false,
saveUninitialized: true,
cookie: { secure: true }
}))
.use(webpackMiddleware(compiler, {
hot: true,
publicPath: webpackConfig.output.publicPath
}))
.use(webpackHotMiddleware(compiler))
export default express
utils/csrf.js
This is the CSRF file that's shared between both the client and server side.
import CryptoJS from 'crypto-js'
import randomString from 'crypto-random-string'
import config from '../config.json'
export default class CSRF {
static secret: String
static csrf: String
static tokenCounter: Number
static generate(counter: Number = 10): String {
if (!this.csrf || this.tokenCounter >= (config.csrf.counter || counter)) {
// after 10 or defined times the token has been used, regenerate it
this.secret = config.csrf.secret !== '' ? config.csrf.secret : randomString(8)
let token = randomString(12)
this.csrf = CryptoJS.AES.encrypt(token, this.secret).toString()
this.tokenCounter = 0
}
return this.csrf
}
static verify(req: Object): Boolean {
const csrf = req.headers['x-csrf-token']
if (!csrf) return false
this.tokenCounter++
//const bytes = CryptoJS.AES.decrypt(csrf, this.secret)
//const decrypted = bytes.toString(CryptoJS.enc.Utf8)
return csrf === this.csrf
}
}
So in my client/index.js file, I setup the axios header like so:
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import axios from 'axios'
import Routes from './routes'
import store from './store'
import config from '../config.json'
import CSRF from '../utils/csrf'
if (config.production) process.NODE_ENV = "production"
axios.defaults.headers.common['X-CSRF-Token'] = CSRF.generate()
ReactDOM.render(
<Provider store={store}>
<Routes />
</Provider>,
document.querySelector('#root')
)
Then from a Redux action I send a post request:
import axios from 'axios'
export function userSignUpRequest(data) {
return (dispatch) => {
return new Promise((resolve, reject) => {
axios.post('/api/users/signup', data)
.then(res => res.data)
.then(data => {
if (data.status) {
resolve(data.user)
} else {
reject(data.errors)
}
})
})
}
}
And in server/api/users/signup I verify the CSRF token:
import Users from '../../database/models/users'
import Validate from '../../../utils/validate'
import CSRF from '../../../utils/csrf'
export default async function (req, res) {
if (!CSRF.verify(req)) {
return res.status(405).json({ status: false, reason: 'Invalid CSRF token' })
}
const {validationErrors, isValid} = Validate.registerInput(req.body)
if (!isValid) {
res.json({ status: false, errors: validationErrors })
} else {
const {email, username, password} = req.body
let errors = {}
try {
await Users.query().where('mail', email)
} catch(err) {
errors.email = 'This email is already taken'
}
try {
await Users.query().where('username', username)
} catch(err) {
errors.username = 'This username is already taken'
}
if (Validate.isObjEmpty(errors)) {
Users.query().insert({
mail: email, username, password
})
.then(user => res.status(200).json({ status: true, user }))
.catch(errors => res.json({ status: false, errors }))
} else {
res.json({ status: false, errors })
}
}
}
Whole source code: Github

System is not defined with Router Parameter - Angular 2

I am trying to implement routing with id parameter on the url such as localhost:1000/register/id
Using that path will always trigger system is not defined while other urls without parameters are working fine. I even tried following the guide from angular.io's routing format doesn't seems to fix the problem. What am I missing in my code?
app.routing.ts
import { ModuleWithProviders } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import { RegisterComponent } from './registration/register.component';
import { CodeComponent } from './registration/code.component';
const appRoutes: Routes = [
{
path: 'register/:id',
component: RegisterComponent
},
{
path: '',
component: CodeComponent
}
];
export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes);
app.module.ts
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { HttpModule } from '#angular/http';
import { FormsModule } from '#angular/forms';
import { routing } from './app.routing';
import { AppComponent } from './app.component';
import { CodeComponent } from './registration/code.component';
import { RegisterComponent } from './registration/register.component';
#NgModule({
imports: [BrowserModule, HttpModule, FormsModule, routing],
declarations: [AppComponent, CodeComponent, RegisterComponent],
bootstrap: [AppComponent]
})
export class AppModule { }
server.js
var express = require('express'),
app = express(),
mongoose = require('mongoose'),
bodyParser = require('body-parser'),
path = require('path'),
passport = require('passport'),
session = require('express-session'),
port = process.env.PORT || 1000;
db = require('./config/db');
mongoose.Promise = global.Promise;
mongoose.connect(db.url);
app.use(session({
secret: 'test',
saveUninitialized: true,
resave: true
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
require('./config/passport')(passport);
require('./routes/main')(app, passport);
app.use(express.static(path.join(__dirname)));
app.use('/node_modules', express.static(path.join(__dirname+ '/node_modules')));
console.log(path.join(__dirname+ '/node_modules'))
app.all('/*', function (req, res, next) {
res.sendFile('/view/index.html', { root: __dirname });
});
app.listen(port);
console.log('Magic happens on port ' + port);
UPDATE:
By using this.router.navigate(['/register', '1']); works perfectly, but by typing on the url localhost:1000/register/1 is not working
From the picture above, there is mouse hover showing the url to be
localhost:1000/register/node_modules/core-js....
- I think there is something I've missed in my server NodeJS side.
I've also added
app.use('/node_modules', express.static(__dirname + '/node_modules'));
But no changes
Note:
Server side (NodeJS,Express)

Resources