我正在构建一个有角度4+角度通用+敲除的解决方案。
其思想是,为了SEO目的,角应用程序将在服务器端呈现html,但作为呈现过程的一部分,它必须能够用视图模型绑定一些html文本,以便首先呈现绑定到敲除的html,然后最后完成服务器端呈现并将结果html发送到浏览器。
之所以将棱角和敲除混为一谈,是因为我们有一些html (例如:'<span data-bind="text: test"></span>')作为来自现有第三方的字符串,其中包含敲除标记。
我可以使用敲出在我的角度模块和应用绑定。html文本能够显示视图模型变量的内容.,但在服务器端执行角应用程序时,这个部分不是在服务器端中呈现的,而是在客户端呈现的,对于我们来说是不可接受的解决方案。
就好像服务器端的javascript呈现引擎没有等待异步调用应用敲除绑定之后,才将呈现的最终html发送给客户端。
我跟踪了这些步骤运行角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应该显示相同的html,带有敲除绑定,但已经在服务器上呈现(它只在客户端工作):
<p>This content should be part of the index page DOM</p>
<div id="ko-result" [innerHTML]="htmlAfterBinding"></div>这是我为应用敲除绑定而创建的服务ko-rendered.service.ts。我使它是异步的,因为在服务器端呈现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>注意带有id="ko-result"的div是如何为空的。稍后,在客户端,这个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"
}
}UPDATE 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>UPDATE 3:我阅读了这里,为了使其具有通用的角度应用程序兼容,我们不应该直接操作DOM。我想知道的是,我使用document在HtmlElement中创建了剔除applyBindings所需的ko-rendered.service.ts,这是否使服务器端忽略这种呈现的角度通用化,但我尝试用Renderer2 (例如:import { Component, OnInit, Renderer2 } from '@angular/core';)创建DOM元素,但这也解决不了这个问题:
var htmlDivElement: any = renderer2.createElement('div')
// var htmlDivElement: HTMLDivElement = document.createElement('div');UPDATE 4:在ko.applyBindings调用期间,我在服务器端节点环境中看到了以下错误,因此我怀疑整个方法是有问题的,因为knockoutJs实际上并不适合在没有浏览器的环境中在服务器端执行。它过于依赖DOM,就像角通用良好做法所说的:
不要使用全局命名空间中提供的任何浏览器类型,例如导航器或文档。在将应用程序序列化为html时,无法检测到角以外的任何内容。
这些错误肯定是导致角环球停止在服务器端呈现并简单地将其传递给浏览器:
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:\Dev\node_modules\knockout\build\output\knockout-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发布于 2017-06-23 14:23:21
敲除显然不适用于角服务器端渲染。淘汰赛的理解是有一个DOM,但没有这样的东西。
尊重角度规则:不要直接使用特定于浏览器的API(比如DOM)。如果这样做,您的模块将不兼容通用服务器呈现和其他角高级选项
https://stackoverflow.com/questions/44262257
复制相似问题