我正在将SSR应用程序与Content Route CMS和Route Resolver一起使用,以在加载组件之前获取数据。当我构建并提供应用程序时,没有错误,并且可以在客户端看到内容,但是当我查看视图源时,将呈现除带有路由解析器的初始组件之外的所有内容。当我移除解析器并将一些静态元素放置在组件内部时,便会在视图源中看到它。
我已经为绝对URL实现了HTTP拦截器,并正确配置了server.ts,但仍然找不到未呈现它的原因。
路线:]
import { NgModule } from '@angular/core';
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';
import { PageComponent } from './page/page.component';
import { PageResolver } from './page/page.resolver.service';
const routes: Routes = [
{ path: ':slug', component: PageComponent, resolve: { page: PageResolver } },
{ path: '', component: PageComponent, resolve: { page: PageResolver } },
{ path: '**', component: PageComponent, resolve: { page: PageResolver } }
];
@NgModule({
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules,
scrollPositionRestoration: 'top',
enableTracing: false,
anchorScrolling: 'enabled'
})
],
exports: [RouterModule],
providers: [PageResolver]
})
export class AppRoutingModule { }
resolver:]
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
import { ContentfulService} from '../core/contentful.service';
import { PageModel } from '../models/page.model';
@Injectable()
export class PageResolver implements Resolve<PageModel> {
constructor(private contentService: ContentfulService) { }
resolve(route: ActivatedRouteSnapshot): Promise<PageModel> {
const slug = this.getSlug(route);
const page = this.contentService.getContentBySlug<PageModel>(slug, 'page', 10);
return page;
}
private getSlug(route: ActivatedRouteSnapshot): string {
const routeLength = route.url.length;
if (routeLength === 0) {
return 'home';
}
if (route.data.slug === 'error') {
return route.data.slug;
}
return route.url.map((urlFragment) => urlFragment.path).join('/');
}
}
page.component.ts] >>
] >>import { Component, OnInit, OnDestroy } from '@angular/core'; import { ContentfulService } from '../core/contentful.service'; import { PageModel } from '../models/cms/page.model'; import { ActivatedRoute } from '@angular/router'; import { untilDestroyed } from 'ngx-take-until-destroy'; @Component({ selector: 'app-page', templateUrl: './page.component.html', styleUrls: ['./page.component.scss'] }) export class PageComponent implements OnInit, OnDestroy { page: PageModel; constructor(private route: ActivatedRoute, public contentful: ContentfulService) { } ngOnInit() { console.log('HIT 1'); this.route.data.pipe(untilDestroyed(this)).subscribe(({ page }) => { this.page = page; }); } ngOnDestroy(): void { } }
server.ts
import 'zone.js/dist/zone-node'; import 'reflect-metadata'; require('source-map-support').install(); import express from 'express'; import compression from 'compression'; import {join} from 'path'; import domino from 'domino'; import fs from 'fs'; import path from 'path'; process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; const template = fs.readFileSync(path.join(process.cwd(), 'dist/my-site', 'index.html')).toString(); console.log(template); const win = domino.createWindow(template); global['window'] = win; // not implemented property and functions Object.defineProperty(win.document.body.style, 'transform', { value: () => { return { enumerable: true, configurable: true, }; }, }); global['document'] = win.document; // othres mock global['CSS'] = null; import {enableProdMode} from '@angular/core'; import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens'; // Faster server renders w/ Prod mode (dev mode never needed) enableProdMode(); // Express server const app = express(); app.use(compression()); // redirects! const redirectowww = false; const redirectohttps = false; const wwwredirecto = true; app.use((req, res, next) => { // for domain/index.html if (req.url === '/index.html') { res.redirect(301, 'https://' + req.hostname); } // check if it is a secure (https) request // if not redirect to the equivalent https url if ( redirectohttps && req.headers['x-forwarded-proto'] !== 'https' && req.hostname !== 'localhost' ) { // special for robots.txt if (req.url === '/robots.txt') { next(); return; } res.redirect(301, 'https://' + req.hostname + req.url); } // www or not if (redirectowww && !req.hostname.startsWith('www.')) { res.redirect(301, 'https://www.' + req.hostname + req.url); } // www or not if (wwwredirecto && req.hostname.startsWith('www.')) { const host = req.hostname.slice(4, req.hostname.length); res.redirect(301, 'https://' + host + req.url); } next(); }); const PORT = process.env.PORT || 4000; const DIST_FOLDER = join(process.cwd(), 'dist/my-site'); // * 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', ngExpressEngine({ bootstrap: AppServerModuleNgFactory, providers: [ provideModuleMap(LAZY_MODULE_MAP) ] })); app.set('view engine', 'html'); app.set('views', DIST_FOLDER); // Example Express Rest API endpoints // app.get('/api/**', (req, res) => { }); // Serve static files from /browser app.get('*.*', express.static(DIST_FOLDER, { maxAge: '1y' })); // All regular routes use the Universal engine // dynamic render app.get('*', (req, res) => { // mock navigator from req. global['navigator'] = req['headers']['user-agent']; const http = req.headers['x-forwarded-proto'] === undefined ? 'http' : req.headers['x-forwarded-proto']; const url = req.originalUrl; // tslint:disable-next-line:no-console console.time(`GET: ${url}`); res.render( '../my-site/index', { req: req, res: res, // provers from server providers: [ // for http and cookies { provide: REQUEST, useValue: req, }, { provide: RESPONSE, useValue: res, }, // for absolute path { provide: 'ORIGIN_URL', useValue: `${http}://${req.headers.host}`, }, ], }, (err, html) => { if (!!err) { throw err; } // tslint:disable-next-line:no-console console.timeEnd(`GET: ${url}`); res.send(html); }, ); }); // Start up the Node server app.listen(PORT, () => { console.log(`Node Express server listening on http://localhost:${PORT}`); });
感谢任何帮助或建议。
编辑:
content.service.ts:
import { Injectable } from '@angular/core'; import { createClient, ContentfulClientApi, EntryCollection, Entry, Asset } from 'contentful'; import { AppConfig } from './config/app-config.service'; @Injectable() export class ContentfulService { private readonly contentfulClient: ContentfulClientApi; constructor() { this.contentfulClient = createClient({ host: AppConfig.settings.contentfulHost, space: AppConfig.settings.contentfulSpace, accessToken: AppConfig.settings.contentfulAccessToken }); } public async getContent<T>(contentId: string, include: number = 1, localeCode = AppConfig.settings.locale): Promise<T> { return this.contentfulClient.getEntries({ 'sys.id': contentId, include, locale: localeCode }) .then(res => { return this.parseModel(res.items[0], res) as T; }); } public async getContentBySlug<T>(slug: string, contentType: string, include: number = 1, localeCode = AppConfig.settings.locale): Promise<T> { console.log('CONTENTFUL STARTS.'); return this.contentfulClient.getEntries({ content_type: contentType, 'fields.slug': slug, include, locale: localeCode }) .then(res => { console.log('CONTENTFUL ENDING.'); return this.parseModel(res.items[0], res) as T; }); } public parseModel(model: Entry<any> | Asset, collection: EntryCollection<any>): any { if (!model) { return model; } console.log('PARSING STARTS.'); const parsedModel = { sys: model.sys }; for (const property in model.fields) { if (model.fields.hasOwnProperty(property)) { let value = model.fields[property]; if (value instanceof Array) { const arrayValue: any[] = []; for (const item of value) { arrayValue.push(this.parseValue(item, collection)); } value = arrayValue; } else { value = this.parseValue(value, collection); } parsedModel[property] = value; } } console.log('PARSING ENDING.'); return parsedModel; } private parseValue(value: any, collection: EntryCollection<any>) { if (value && value.sys) { switch (value.sys.type) { case 'Entry': value = this.parseModel(value, collection); break; case 'Asset': value = this.parseModel(value, collection); break; } } return value; } public isContentOfType(contentItem: any, contentId: string): boolean { if (!contentItem || !contentItem.sys.contentType) { return false; } return contentItem.sys.contentType.sys.id === contentId; } }
我正在将SSR应用程序与Content Route CMS和Route Resolver一起使用,以在加载组件之前获取数据。当我构建并服务该应用程序时,没有错误,并且可以看到...
因此,这是我花了几天时间尝试不同的方法后解决此问题的方法。某种程度上,用于角度的内容丰富的库在SSR和使用路由解析器上无法正常工作。因此,我将逻辑和调用内容移到了我们的代理API(已经在处理其他服务调用)中了。通过这种方式,我可以使用SSR和路由解析器通过代理API进行有条件的调用。代理API内置于.NET核心中,并部署在Azure上。
我不知道为什么会这样,但是我希望这能给遇到与此库或其他类似库类似问题的人带来启发。