Angular Universal不会在服务器端渲染所有内容

我正在用Angular 4 + Angular Universal + Knockout构建解决方案。

这个想法是,角度应用程序将在服务器端呈现HTML用于搜索引擎优化的目的,但作为该渲染过程的一部分,它必须能够使用knockoutJs绑定一些HTML文本与视图模型,以便首先渲染HTML绑定到挖空然后完成服务器端渲染并将结果html发送到浏览器。

将angular与knockout混合的原因是因为我们有一些html(例如: '<span data-bind="text: test"></span>' )作为来自现有第三方的字符串,其中包含knockout标记。

我能够在我的Angular模块中使用knockout并应用绑定。 html文本能够显示视图模型变量的内容...... 但是当在服务器端执行Angular应用时,这部分不在服务器端渲染 ,它可以工作,但是它在客户端渲染,并且它不可接受为我们解决方案。

就好像服务器端JavaScript呈现引擎没有等待异步调用应用挖空绑定,然后发送给客户端呈现完成的html。

我已经按照这些步骤在后端运行带有Universal的Angular 4。

这是我的简单淘汰赛模型mysample.ts

import * as ko from 'knockout';

export interface IMySample {
    test: string;
}

export class MySample implements IMySample {
    public test: any = ko.observable("Hi, I'm a property from knockout");

    constructor(){
    }
}

这是我的主要组件home.component.ts我有一些HTML作为包含淘汰赛绑定的文本,我想在服务器端呈现。

import { Component, OnInit } from '@angular/core';
import { MySample } from '../ko/mysample';
import { KoRendererService } from '../ko-renderer.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css'],
  providers: [KoRendererService]
})
export class HomeComponent implements OnInit {

  htmlWithKnockout: string = '<span data-bind="text: test"></span>';
  htmlAfterBinding: string = null;
  constructor(private koRendererService: KoRendererService) 
  { 
  }

  ngOnInit() {
    var mySample = new MySample();
    this.koRendererService.getHtmlRendered(this.htmlWithKnockout, mySample).then((htmlRendered: string) => {
      this.htmlAfterBinding = htmlRendered;
    });
  }
}

它的视图home.component.html应该显示与knockout绑定相同的html,但已经在服务器中呈现(它只在客户端工作):

<p>This content should be part of the index page DOM</p>
<div id="ko-result" [innerHTML]="htmlAfterBinding"></div>

这是我创建的用于应用挖空绑定的服务ko-rendered.service.ts 。 我已经使它异步了,因为我在这里读到Angular Unviersal应该等待异步调用在服务器端呈现html之前结束)

import * as ko from 'knockout';

interface IKoRendererService {
    getHtmlRendered(htmlAsText: string, viewModel: any): Promise<string>;
}

export class KoRendererService implements IKoRendererService {
    constructor(){

    }

    getHtmlRendered(htmlAsText: string, viewModel: any): Promise<string> {
        return new Promise<string>((resolve, reject) => {
            var htmlDivElement: HTMLDivElement = document.createElement('div');
            htmlDivElement.innerHTML = htmlAsText;
            ko.applyBindings(viewModel, htmlDivElement.firstChild);
            var result = htmlDivElement.innerHTML;
            resolve(result);
        });
    }
}

这是对浏览器中index.html页面的响应。 我们可以在这里看到,角度内容已经在服务器端正确渲染,但是具有敲除绑定的部分不在DOM中,它在客户端检索。

<!DOCTYPE html><html lang="en"><head>
  <meta charset="utf-8">
  <title>Sample</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="styles.d41d8cd98f00b204e980.bundle.css" rel="stylesheet"><style ng-transition="carama"></style></head>
<body>
  <app-root _nghost-c0="" ng-version="4.1.3"><ul _ngcontent-c0="">
  <li _ngcontent-c0=""><a _ngcontent-c0="" routerLink="/" href="/">Home</a></li>
  <li _ngcontent-c0=""><a _ngcontent-c0="" routerLink="about" href="/about">About Us</a></li>
</ul>
<hr _ngcontent-c0="">

<h1 _ngcontent-c0="">Welcome to the server side rendering test with Angular Universal</h1>
<router-outlet _ngcontent-c0=""></router-outlet><app-home _nghost-c1=""><p _ngcontent-c1="">This content should be part of the index page DOM</p>
<div _ngcontent-c1="" id="ko-result"></div></app-home>
</app-root>
<script type="text/javascript" src="inline.77dfeeb563e4dcc7a506.bundle.js"></script><script type="text/javascript" src="polyfills.d90888e283bda7f009a0.bundle.js"></script><script type="text/javascript" src="vendor.451987311459166e7919.bundle.js"></script><script type="text/javascript" src="main.af6e993f16ecd4063c3b.bundle.js"></script>

</body></html>

注意div与id="ko-result"是空的。 后来在客户端这个div在DOM中正确修改,如下所示:

<div _ngcontent-c1="" id="ko-result"><span>Hi, I'm a property from knockout</span></div>

但我需要在服务器端渲染 ...

任何帮助将非常感激。 谢谢!

更新1 :这是我的依赖包package.json:

{
  "name": "server-side-rendering",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "prestart": "ng build --prod && ngc",
    "start": "ts-node src/server.ts"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^4.1.3",
    "@angular/common": "^4.0.0",
    "@angular/compiler": "^4.0.0",
    "@angular/core": "^4.0.0",
    "@angular/forms": "^4.0.0",
    "@angular/http": "^4.0.0",
    "@angular/platform-browser": "^4.0.0",
    "@angular/platform-browser-dynamic": "^4.0.0",
    "@angular/platform-server": "^4.1.3",
    "@angular/router": "^4.0.0",
    "core-js": "^2.4.1",
    "rxjs": "^5.1.0",
    "zone.js": "^0.8.4",
    "knockout": "^3.4.2"
  },
  "devDependencies": {
    "@angular/cli": "1.0.6",
    "@angular/compiler-cli": "^4.0.0",
    "@types/jasmine": "2.5.38",
    "@types/node": "~6.0.60",
    "codelyzer": "~2.0.0",
    "jasmine-core": "~2.5.2",
    "jasmine-spec-reporter": "~3.2.0",
    "karma": "~1.4.1",
    "karma-chrome-launcher": "~2.1.1",
    "karma-cli": "~1.0.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "karma-coverage-istanbul-reporter": "^0.2.0",
    "protractor": "~5.1.0",
    "ts-node": "~2.0.0",
    "tslint": "~4.5.0",
    "typescript": "~2.2.0"
  }
}

更新2 :在客户端渲染它也可以工作,我可以在浏览器上看到最终的渲染,但正如预期的那样,index.html请求中缺少所有内容,并且它是稍后获取内容的JavaScript。 这是使用ng-serve (客户端呈现)运行相同应用程序时的响应:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Sample</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root>Loading sample, you should not see this in client side...</app-root>
<script type="text/javascript" src="inline.bundle.js"></script><script type="text/javascript" src="polyfills.bundle.js"></script><script type="text/javascript" src="styles.bundle.js"></script><script type="text/javascript" src="vendor.bundle.js"></script><script type="text/javascript" src="main.bundle.js"></script></body>
</html>

更新3 :我在这里读到要让Angular应用程序通用兼容,我们不应该直接操纵DOM。 我想知道是否使用document来创建knockout applyBindings在我的ko-rendered.service.ts需要的HtmlElement的事实是以某种方式使角度通用来忽略服务器端的这种渲染,但我试图创建DOM元素与Renderer2 (例如: import { Component, OnInit, Renderer2 } from '@angular/core'; ),而且这也不能解决问题:

var htmlDivElement: any = renderer2.createElement('div')
            // var htmlDivElement: HTMLDivElement = document.createElement('div');

更新4 :在ko.applyBindings调用期间,我在服务器端节点环境中看到了以下错误,所以我怀疑整个方法是有问题的,因为knockoutJs并非真正设计为在没有浏览器的环境中在服务器端执行。 它过分依赖于DOM ,而Angular Universal的良好实践则说:

请勿使用全局名称空间(如导航器或文档)中提供的任何浏览器类型。 在将您的应用程序序列化为html时,Angular以外的任何内容都不会被检测到

这些是Angular Universal停止在服务器端渲染并将其传递到浏览器的错误。

listening on http://localhost:4000!
ERROR { Error: Uncaught (in promise): TypeError: Cannot read property 'body' of undefined
TypeError: Cannot read property 'body' of undefined
    at Object.ko.applyBindings 

(C:Devnode_modulesknockoutbuildoutputknockout-latest.debug.js:3442:47)
..
ERROR { Error: Uncaught (in promise): TypeError: this.html.charCodeAt is not a function
TypeError: this.html.charCodeAt is not a function

Knockout显然不适用于角度服务器端渲染。 淘汰赛是有一个DOM的理解,但没有这样的事情。

尊重Angular规则:不要直接使用特定于浏览器的API(如DOM)。 如果这样做,您的模块将不兼容通用服务器渲染和其他Angular高级选项

链接地址: http://www.djcxy.com/p/5299.html

上一篇: Angular Universal does not render all in server side

下一篇: node.js