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