我们正在将我们的应用程序从AngularJS转换为Angular5。我试图弄清楚如何使用Angular5复制某些行为 - 即使用服务器端渲染来创建可注入值。
在我们当前的Angular1.6应用中,我们有 index.hbs 文件:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Collaborative Tool</title>
<link href="favicon.ico" rel="shortcut icon" type="image/x-icon">
</head>
<body class="aui content" ng-app="app">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.5/angular.js"></script>
<script>
/* globals angular */
angular.module('app')
.value('USER', JSON.parse('{{{user}}}'))
.value('WORKSTREAM_ENUM', JSON.parse('{{{workStreamEnum}}}'))
.value('CATEGORY_ENUM', JSON.parse('{{{categoryEnum}}}'))
.value('ROLES_ENUM', JSON.parse('{{{roles}}}'))
.value('FUNCTIONAL_TEAM_ENUM', JSON.parse('{{{functionalTeams}}}'))
.value('CDT_ENV', '{{CDT_ENV}}')
.value('CDT_HOST', '{{CDT_HOST}}')
.value('CDT_LOGOUT_URL', '{{CDT_LOGOUT_URL}}');
</script>
</body>
</html>
所以我们所做的是在第一个脚本标记中加载角度,然后使用第二个脚本标记创建一些值/枚举/常量。基本上使用服务器端渲染(把手)将数据发送到前端。
我的问题:有没有办法与Angular5做一些非常相似的事情? 我们如何使用服务器端渲染在Angular5中创建可注入的模块/值?
答案 0 :(得分:4)
我的团队在从AngularJS过渡到Angular(v2的早期候选版本)时遇到了同样的问题。我们提出了一个我们仍然使用的解决方案,并且我不知道任何更新以使其更容易(至少在不使用Angular Universal时 - 如果您正在使用它,那么内置的东西用于引导初始数据) 。我们通过序列化JSON对象并将其设置为HTML中应用程序根Angular组件的属性,将数据传递给我们的Angular应用程序:
<app-root [configuration]="JSON_SERIALIZED_OBJECT"></app-root>
其中JSON_SERIALIZED_OBJECT
是实际的序列化对象。我们使用.NET(非核心,因此Angular Universal并不是一个真正的选项)来渲染我们的页面(做[configuration]="@JsonConvert.SerializeObject(Model.Context)"
)所以不知道你需要做什么,但它看起来像你应该能够做你以前做过的同样的事情来序列化它。
设置完成后,我们必须在主应用程序组件中手动JSON.parse(...)
该对象,但我们将其视为Angular输入。这就是我们的组件想要抓住它:
import { Component, ElementRef } from '@angular/core';
import { ConfigurationService } from 'app/core';
@Component(...)
export class AppComponent {
constructor(private element: ElementRef, private configurationService: ConfigurationService) {
this.setupConfiguration();
}
private setupConfiguration() {
const value = this.getAttributeValue('[configuration]');
const configuration = value ? JSON.parse(value) : {};
this.configurationService.setConfiguration(configuration);
}
private getAttributeValue(attribute: string) {
const element = this.element.nativeElement;
return element.hasAttribute(attribute) ? element.getAttribute(attribute) : null;
}
}
如图所示,我们使用服务来共享系统周围的数据。它可以像这样简单:
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Configuration } from './configuration.model';
@Injectable()
export class ConfigurationService {
private readonly configurationSubject$ = new BehaviorSubject<Configuration>(null);
readonly configuration$ = this.configurationSubject$.asObservable();
setConfiguration(configuration: Configuration) {
this.configurationSubject$.next(configuration);
}
}
然后,在需要配置数据的组件中,我们注入此服务并注意更改。
import { Component, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/takeUntil';
import { ConfigurationService } from 'app/core';
@Component(...)
export class ExampleThemedComponent implements OnDestroy {
private readonly destroy$ = new Subject<boolean>();
readonly theme$: Observable<string> = this.configurationService.configuration$
.takeUntil(this.destroy$.asObservable())
.map(c => c.theme);
constructor(private configurationService: ConfigurationService) {
}
ngOnDestroy() {
this.destroy$.next(true);
}
}
注意:我们有时会在运行时更改配置,这就是我们使用主题和可观察对象的原因。如果您的配置不会发生变化,那么您可以跳过这些示例中的所有部分。
答案 1 :(得分:4)
在服务器端渲染时,仍可以在组件内部使用依赖注入。
如果你计划在Angular 5中使用服务器端渲染,你应该考虑查看Angular Universal它提供了在服务器端呈现Angular单页面应用程序的构建块(对于SEO友好的可索引)含量)。
那里有许多优秀的角度通用启动器项目。一个很好的例子是[universal-starter][2]
。它使用ngExpressEngine在请求的URL上动态呈现您的应用程序。它使用webpack项目配置,其中包含一个prerender任务,用于编译应用程序并预呈现应用程序文件。此任务如下所示:
// Load zone.js for the server.
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import {readFileSync, writeFileSync, existsSync, mkdirSync} from 'fs';
import {join} from 'path';
import {enableProdMode} from '@angular/core';
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
// Import module map for lazy loading
import {provideModuleMap} from '@nguniversal/module-map-ngfactory-loader';
import {renderModuleFactory} from '@angular/platform-server';
import {ROUTES} from './static.paths';
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main.bundle');
const BROWSER_FOLDER = join(process.cwd(), 'browser');
// Load the index.html file containing referances to your application bundle.
const index = readFileSync(join('browser', 'index.html'), 'utf8');
let previousRender = Promise.resolve();
// Iterate each route path
ROUTES.forEach(route => {
var fullPath = join(BROWSER_FOLDER, route);
// Make sure the directory structure is there
if(!existsSync(fullPath)){
mkdirSync(fullPath);
}
// Writes rendered HTML to index.html, replacing the file if it already exists.
previousRender = previousRender.then(_ => renderModuleFactory(AppServerModuleNgFactory, {
document: index,
url: route,
extraProviders: [
provideModuleMap(LAZY_MODULE_MAP)
]
})).then(html => writeFileSync(join(fullPath, 'index.html'), html));
});
稍后,您可以运行一个快速服务器,使您的应用程序生成HTML:
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser'), {
maxAge: '1y'
}));
// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render('index', { req });
});
// Start up the Node server
app.listen(PORT, () => {
console.log(`Node Express server listening on http://localhost:${PORT}`);
});
您可以运行服务器端特定代码,例如:
import { PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
constructor(@Inject(PLATFORM_ID) private platformId: Object) { ... }
ngOnInit() {
if (isPlatformBrowser(this.platformId)) {
// Client only code.
...
}
if (isPlatformServer(this.platformId)) {
// Server only code.
...
}
}
但要注意窗口,文档,导航器和其他浏览器类型 - 在服务器上不存在。因此,任何可能使用它们的库都可能无效。
答案 2 :(得分:4)
创建文件:data.ts.在此文件中声明变量及其类型(我将只显示一个)并为每个变量创建InjectionToken:
import { InjectionToken } from '@angular/core';
// describes the value of the variable
export interface EmbeddedUserData {
userId: string;
// etc
}
// tells the app that there will be a global variable named EMBEDDED_USER_DATA (from index.html)
export declare const EMBEDDED_USER_DATA: EmbeddedUserData;
// creates injection token for DI that you can use it as a provided value (like value or constant in angular 1)
export UserData = new InjectionToken<EmbeddedUserData>('EmbeddedUserData');
然后来到app.module.ts并提供此令牌:
// ...
providers: [
{ provide: UserData, useValue: EMBEDDED_USER_DATA }
],
// ...
最后将其用作任何正常服务/注入值:
// ...
constructor(@Inject(UserData) userData: EmbeddedUserData) {}
// ...
或将其用作简单的导入变量(在这种情况下甚至不需要提供/注入任何东西):
import { EMBEDDED_USER_DATA } from './data.ts';
结果你在angularjs中几乎一样。剩下的唯一事情就是在角度脚本之前将变量添加到index.html(甚至可以将它放在head
中):
<script>var EMBEDDED_USER_DATA = JSON.parse({ ... })</script>