Angular 角度通用不';使用路由解析器时,请不要在视图源中加载我的组件

Angular 角度通用不';使用路由解析器时,请不要在视图源中加载我的组件,angular,express,server-side-rendering,angular-universal,contentful,Angular,Express,Server Side Rendering,Angular Universal,Contentful,我正在使用SSR应用程序和带有路由解析器的内容CMS在加载组件之前获取数据。当我构建并服务于应用程序时,没有错误,我可以在客户端看到内容,但是当我查看视图源时,除了带有路由解析器的初始组件之外,所有内容都被呈现。当我移除解析器并在组件内部放置一些静态元素时,我会在视图源代码中看到它 我已经为绝对URL实现了http拦截器,并正确地配置了我的server.ts,但仍然找不到它未被呈现的原因 路线: 解析器: server.ts 感谢任何帮助或建议 编辑: contentful.service.ts

我正在使用SSR应用程序和带有路由解析器的内容CMS在加载组件之前获取数据。当我构建并服务于应用程序时,没有错误,我可以在客户端看到内容,但是当我查看视图源时,除了带有路由解析器的初始组件之外,所有内容都被呈现。当我移除解析器并在组件内部放置一些静态元素时,我会在视图源代码中看到它

我已经为绝对URL实现了http拦截器,并正确地配置了我的server.ts,但仍然找不到它未被呈现的原因

路线:

解析器:

server.ts

感谢任何帮助或建议

编辑:

contentful.service.ts:

从'@angular/core'导入{Injectable};
从“contentful”导入{createClient,ContentfulClientApi,EntryCollection,Entry,Asset};
从“./config/app config.service”导入{AppConfig};
@可注射()
导出类内容服务{
私有只读contentfulClient:ContentfulClientApi;
构造函数(){
this.contentfulClient=createClient({
主机:AppConfig.settings.contentfulHost,
空间:AppConfig.settings.contentfulSpace,
accessToken:AppConfig.settings.contentfulAccessToken
});
}
公共异步getContent(contentId:string,include:number=1,localeCode=AppConfig.settings.locale):承诺{
返回此.contentfulClient.getEntries({'sys.id':contentId,include,locale:localeCode})
。然后(res=>{
将此.parseModel(res.items[0],res)返回为T;
});
}
公共异步getContentBySlug(slug:string,contentType:string,
包括:number=1,localeCode=AppConfig.settings.locale):承诺{
log('CONTENTFUL start');
返回此.contentfulClient.getEntries({content\u type:contentType,'fields.slug':slug,include,locale:localeCode})
。然后(res=>{
log('CONTENTFUL end');
将此.parseModel(res.items[0],res)返回为T;
});
}
公共模型(模型:入口|资产,集合:入口集合):任意{
如果(!模型){
收益模型;
}
log('解析开始');
const parsedModel={sys:model.sys};
for(model.fields中的const属性){
if(model.fields.hasOwnProperty(属性)){
让value=model.fields[property];
if(数组的值实例){
常量数组值:任意[]=[];
用于(常量值项){
arrayValue.push(this.parseValue(项,集合));
}
值=数组值;
}否则{
value=this.parseValue(值,集合);
}
parsedModel[属性]=值;
}
}
log('解析结束');
返回解析模型;
}
私有parseValue(值:any,集合:EntryCollection){
if(value&&value.sys){
开关(值系统类型){
“输入”案例:
value=this.parseModel(值,集合);
打破
案例“资产”:
value=this.parseModel(值,集合);
打破
}
}
返回值;
}
public isContentOfType(contentItem:any,contentId:string):布尔值{
如果(!contentItem | |!contentItem.sys.contentType){
返回false;
}
返回contentItem.sys.contentType.sys.id==contentId;
}
}

在花了几天时间尝试不同的方法之后,我是如何解决这个问题的。不知何故,angular的内容库在SSR和使用路由解析器时不能正常工作。因此,我将逻辑和调用移到contentful,并转移到我们的代理API(它已经在处理其他服务调用)。通过这种方式,我能够使用SSR和路由解析器通过代理API调用contentful。代理API内置于.NET内核中,并部署在Azure上


我不知道为什么会发生这种情况,但我希望这能为任何使用此库或其他类似库遇到类似问题的人带来启示。

显示您的解析程序代码(您包含了两次路由代码)@David my bad,使用正确的解析程序进行了更新。控制台服务器端有错误吗?否则,请在代码中添加一些
console.log
,以确定确切的调用内容。你能提供
getContentBySlug
的代码吗?@David我已经添加了contentful服务代码。我还在server.ts、解析器、服务和组件中添加了日志。我从他们所有人那里收到日志,但不是从正在侦听的组件:
Node-Express服务器接收日志http://localhost:4000
解析程序启动。
内容式启动。
解析程序结束。
快速获取:/:295.366ms
内容式结束。
解析开始。
解析结束。
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 { }
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('/');
  }
}
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 {
  }
}

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}`);
});
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;
  }
}