У меня странная проблема в Angular Universal, где все маршруты работают правильно, когда к ним обращаются с полностраничной записью, кроме одной! Вот моя текущая конфигурация:
Маршруты приложений:
const routes: Routes = [{
path: '',
pathMatch: 'full',
redirectTo: '/home'
}, {
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
}, {
path: 'home',
loadChildren: () => import('./home/home.module').then(m => m.HomeModule)
}, {
path: 'foo',
loadChildren: () => import('./foo/foo.module').then(m => m.FooModule)
}, {
path: '**',
loadChildren: () => import('./not-found/not-found.module').then(m => m.NotFoundModule)
}];
server.ts:
import 'zone.js/dist/zone-node';
import * as express from 'express';
import {join} from 'path';
import * as proxy from 'http-proxy-middleware';
// Express server
const app = express();
const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist/browser');
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap} = require('./dist/server/main');
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
app.engine('html', (_, options: { req, res }, callback) => {
const engine = ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP),
{provide: 'headers', useFactory: () => options.req.headers, deps: []}
]
});
engine(_, options, callback);
});
app.set('view engine', 'html');
app.set('views', DIST_FOLDER);
app.use(`/app/`, createRoutes());
function createRoutes() {
const router = express.Router();
router.get('*.*', express.static(DIST_FOLDER, {
maxAge: '1y'
}));
router.get('*', (req, res) => {
res.render('index', {req, res}, (error, html) => {
return res.send(html);
});
});
return router;
}
const apiProxy = proxy('/api', { target: 'http://localhost:8000'});
app.use('/api', apiProxy);
// Start up the Node server
app.listen(PORT, () => {
console.log(`Node Express server listening on http://localhost:${PORT}/app`);
});
HomeRoutingModule:
import { HomeComponent } from './home.component';
const routes: Routes = [{ path: '', component: HomeComponent }];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class HomeRoutingModule { }
HomeComponent имеет две зависимости от некоторых служб обработки HTTP, а также от PrimeNg MessageService
.
Поэтому, когда я захожу на http://localhost:4200/app/query
, все работает нормально, и я могу правильно перемещаться по приложению. Но когда я захожу на http://localhost:4200/app
или http://localhost:4200/app/home
, ничего не происходит, я даже не получаю ответ, и даже обратный вызов, который я пытался добавить в index
рендеринг маршрута в экспрессе, не вызывается.
Я немного покопался в dist/server/main.js
и сузил вопрос до следующей функции:
function _render(platform, moduleRefPromise) {
return moduleRefPromise.then((/**
* @param {?} moduleRef
* @return {?}
*/
(moduleRef) => {
/** @type {?} */
const transitionId = moduleRef.injector.get(platform_browser["ɵTRANSITION_ID"], null);
if (!transitionId) {
throw new Error(`renderModule[Factory]() requires the use of BrowserModule.withServerTransition() to ensure
the server-rendered app can be properly bootstrapped into a client app.`);
}
/** @type {?} */
const applicationRef = moduleRef.injector.get(core["ApplicationRef"]);
return applicationRef.isStable.pipe((Object(first["a" /* first */])((/**
* @param {?} isStable
* @return {?}
*/
(isStable) => isStable))))
.toPromise()
.then((/**
* @return {?}
*/
() => {
/** @type {?} */
const platformState = platform.injector.get(PlatformState);
/** @type {?} */
const asyncPromises = [];
// Run any BEFORE_APP_SERIALIZED callbacks just before rendering to string.
/** @type {?} */
const callbacks = moduleRef.injector.get(BEFORE_APP_SERIALIZED, null);
if (callbacks) {
for (const callback of callbacks) {
try {
/** @type {?} */
const callbackResult = callback();
if (Object(core["ɵisPromise"])(callbackResult)) {
asyncPromises.push(callbackResult);
}
}
catch (e) {
// Ignore exceptions.
console.warn('Ignoring BEFORE_APP_SERIALIZED Exception: ', e);
}
}
}
/** @type {?} */
const complete = (/**
* @return {?}
*/
() => {
/** @type {?} */
const output = platformState.renderToString();
platform.destroy();
return output;
});
if (asyncPromises.length === 0) {
return complete();
}
return Promise
.all(asyncPromises.map((/**
* @param {?} asyncPromise
* @return {?}
*/
asyncPromise => {
return asyncPromise.catch((/**
* @param {?} e
* @return {?}
*/
e => { console.warn('Ignoring BEFORE_APP_SERIALIZED Exception: ', e); }));
})))
.then(complete);
}));
}));
}
Что здесь происходит, так это то, что для маршрута /home
мне никогда не удается войти вобратный вызов, который начинается с const platformState = platform.injector.get(PlatformState);
. Я не уверен, почему это происходит, подумал.
У кого-нибудь есть какие-нибудь идеи и они могут помочь?
Обновление № 1:
Iудалось сузить проблему до оператора RxJS, который я использую в моем ngOnInit
из HomeComponent
. Для этого компонента я делаю некоторые опросы, поэтому у меня есть что-то вроде:
timer(0, 15000).pipe(
mergeMap(() => this.dataService.fetchData())
).subscribe(...)
Пытаясь обеспечить минимальный нерабочий компонент, я удалил все из своего компонента и добавил следующее к ngOnInit
:
timer(0, 20000).subscribe(
(...args) => console.log(args),
(...args) => console.error(args)
);
Теперь, когда я пытаюсь посетить маршрут, он все равно не загружается (как и ожидалось), но я вижу в консоли сервера значения из подписки.