Maison > interface Web > js tutoriel > Pratique de développement angulaire (6) : rendu côté serveur

Pratique de développement angulaire (6) : rendu côté serveur

不言
Libérer: 2018-04-02 15:07:01
original
2249 Les gens l'ont consulté

Angular Universal

Angular fournit une solution isomorphe front-end et back-end pour le rendu côté serveur. Il s'agit d'Angular Universal (plateforme unifiée), une technologie qui exécute des applications Angular sur le serveur.

Une application Angular standard sera exécutée dans le navigateur et restituera la page dans le DOM en réponse aux opérations de l'utilisateur.

Angular Universal génère des pages d'application statiques sur le serveur via un processus appelé rendu côté serveur (SSR).

Il peut générer ces pages et y répondre directement à la demande du navigateur. Il peut également pré-générer des pages dans des fichiers HTML, puis les utiliser comme fichiers statiques pour une utilisation sur le serveur.

Comment ça marche

Pour créer une application universelle, vous devez installer le package platform-server. Le package platform-server fournit une implémentation DOM côté serveur, XMLHttpRequest et d'autres fonctionnalités de bas niveau, mais ne repose plus sur le navigateur.

Vous devez utiliser le module platform-server au lieu du module platform-browser pour compiler l'application client et exécuter l'application universelle sur un serveur Web. Le serveur

(le serveur Node Express est utilisé dans l'exemple ci-dessous) transmettra la demande du client pour la page d'application à la fonction renderModuleFactory. La fonction

renderModuleFactory accepte en entrée un modèle de page HTML (généralement index.html), un module Angular contenant les composants et une route qui détermine quels composants doivent être affichés.

Cette route est transmise au serveur à partir de la demande du client. Chaque demande donnera une vue appropriée de l'itinéraire demandé.

renderModuleFactory restitue la vue dans la balise <app> du modèle et crée une page HTML terminée pour le client.

Enfin, le serveur renverra la page rendue au client.

Pourquoi le rendu côté serveur

Trois raisons principales :

  1. Aider les robots d'exploration Web (SEO)

  2. Améliorer les performances sur les téléphones mobiles et les appareils à faible consommation

  3. Afficher rapidement la première page

Aide les robots d'exploration du Web (SEO)

Google, Bing, Baidu, Facebook, Twitter et d'autres moteurs de recherche ou sites de médias sociaux s'appuient sur des robots d'exploration Web pour indexer le contenu de votre application et rendre son contenu consultable sur le Web.

Ces robots d'exploration Web peuvent ne pas naviguer vers et indexer votre application Angular hautement interactive comme le ferait un humain.

Angular Universal peut générer une version statique de votre application consultable, pouvant être liée et parcourue sans avoir besoin de JavaScript. Il permet également de prévisualiser le site, puisque chaque URL renvoie une page entièrement rendue.

L'activation des robots d'exploration Web est souvent appelée optimisation des moteurs de recherche (SEO).

Amélioration des performances sur les téléphones mobiles et les appareils à faible consommation

Certains appareils ne prennent pas en charge JavaScript ou JavaScript est mal implémenté, ce qui entraîne une expérience utilisateur inacceptable. Dans ces cas, vous souhaiterez peut-être une version de l'application rendue par le serveur et sans JavaScript. Bien qu'il existe certaines limitations, cette version peut être la seule option pour ceux qui n'ont aucun moyen d'utiliser l'application.

Afficher rapidement la page d'accueil

Afficher rapidement la page d'accueil est crucial pour attirer les utilisateurs.

53% des sites Web mobiles sont abandonnés si une page met plus de trois secondes à se charger. Votre application doit se lancer plus rapidement pour attirer l'attention de l'utilisateur avant qu'il ne décide de faire autre chose.

En utilisant Angular Universal, vous pouvez générer des « pages de destination » pour votre application qui ressemblent à l'application complète. Ces pages de destination sont en HTML pur et s'affichent même si JavaScript est désactivé. Ces pages ne gèrent pas les événements du navigateur, mais elles peuvent être parcourues au sein du site à l'aide de routerLink.

En pratique, vous souhaiterez peut-être utiliser une version statique de la page de destination pour retenir l’attention de l’utilisateur. Dans le même temps, vous chargerez également l’application Angular complète en coulisses. Les utilisateurs s'attendront à ce que la page de destination apparaisse presque instantanément et, une fois l'application complète chargée, ils vivront une expérience entièrement interactive.

Analyse d'échantillons

Ce qui suit sera expliqué en fonction de mon exemple de projet angulaire-universal-starter sur GitHub.

Ce projet, comme l'exemple de projet dans le premier article, est développé et construit sur la base d'Angular CLI, donc la seule différence entre eux réside dans la configuration requise pour le rendu côté serveur.

Outils d'installation

Avant de commencer, les packages suivants doivent être installés (les exemples de projets ont été configurés, juste npm install suffit) :

  • @angular/platform-server - Composant serveur pour Universal.

  • @nguniversal/module-map-ngfactory-loader - Utilisé pour gérer le chargement paresseux dans un environnement de rendu côté serveur.

  • @nguniversal/express-engine - Moteur express pour applications universelles.

  • ts-loader - utilisé pour traduire les applications côté serveur.

  • express - Serveur Node Express

Installez-les à l'aide de la commande suivante :

npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader @nguniversal/express-engine express
Copier après la connexion

Configuration du projet

Le travail de configuration comprend :

  1. 创建服务端应用模块:src/app/app.server.module.ts

  2. 修改客户端应用模块:src/app/app.module.ts

  3. 创建服务端应用的引导程序文件:src/main.server.ts

  4. 修改客户端应用的引导程序文件:src/main.ts

  5. 创建 TypeScript 的服务端配置:src/tsconfig.server.json

  6. 修改 @angular/cli 的配置文件:.angular-cli.json

  7. 创建 Node Express 的服务程序:server.ts

  8. 创建服务端预渲染的程序:prerender.ts

  9. 创建 Webpack 的服务端配置:webpack.server.config.js

1、创建服务端应用模块:src/app/app.server.module.ts

import { NgModule } from '@angular/core';
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';

import { AppBrowserModule } from './app.module';
import { AppComponent } from './app.component';

// 可以注册那些在 Universal 环境下运行应用时特有的服务提供商
@NgModule({
    imports: [
        AppBrowserModule, // 客户端应用的 AppModule
        ServerModule, // 服务端的 Angular 模块
        ModuleMapLoaderModule, // 用于实现服务端的路由的惰性加载
        ServerTransferStateModule, // 在服务端导入,用于实现将状态从服务器传输到客户端
    ],
    bootstrap: [AppComponent],
})
export class AppServerModule {
}
Copier après la connexion

服务端应用模块(习惯上叫作 AppServerModule)是一个 Angular 模块,它包装了应用的根模块 AppModule,以便 Universal 可以在你的应用和服务器之间进行协调。 AppServerModule 还会告诉 Angular 再把你的应用以 Universal 方式运行时,该如何引导它。

2、修改客户端应用模块:src/app/app.module.ts

import { BrowserModule, BrowserTransferStateModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { APP_ID, Inject, NgModule, PLATFORM_ID } from '@angular/core';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { TransferHttpCacheModule } from '@nguniversal/common';
import { isPlatformBrowser } from '@angular/common';
import { AppRoutingModule } from './app.routes';

@NgModule({
    imports: [
        AppRoutingModule,
        BrowserModule.withServerTransition({appId: 'my-app'}),
        TransferHttpCacheModule, // 用于实现服务器到客户端的请求传输缓存,防止客户端重复请求服务端已完成的请求
        BrowserTransferStateModule, // 在客户端导入,用于实现将状态从服务器传输到客户端
        HttpClientModule
    ],
    declarations: [
        AppComponent,
        HomeComponent
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppBrowserModule {
    constructor(@Inject(PLATFORM_ID) private platformId: Object,
                @Inject(APP_ID) private appId: string) {
        
        // 判断运行环境为客户端还是服务端
        const platform = isPlatformBrowser(platformId) ? 'in the browser' : 'on the server';
        console.log(`Running ${platform} with appId=${appId}`);
    }
}
Copier après la connexion

NgModule 的元数据中 BrowserModule 的导入改成 BrowserModule.withServerTransition({appId: 'my-app'}),Angular 会把 appId 值(它可以是任何字符串)添加到服务端渲染页面的样式名中,以便它们在客户端应用启动时可以被找到并移除。

此时,我们可以通过依赖注入(@Inject(PLATFORM_ID)@Inject(APP_ID))取得关于当前平台和 appId 的运行时信息:

constructor(@Inject(PLATFORM_ID) private platformId: Object,
            @Inject(APP_ID) private appId: string) {
    
    // 判断运行环境为客户端还是服务端
    const platform = isPlatformBrowser(platformId) ? 'in the browser' : 'on the server';
    console.log(`Running ${platform} with appId=${appId}`);
}
Copier après la connexion

3、创建服务端应用的引导程序文件:src/main.server.ts

该文件导出服务端模块:

export { AppServerModule } from './app/app.server.module';
Copier après la connexion

4、修改客户端应用的引导程序文件:src/main.ts

监听 DOMContentLoaded 事件,在发生 DOMContentLoaded 事件时运行我们的代码,以使 TransferState 正常工作

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppBrowserModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
    enableProdMode();
}

// 在 DOMContentLoaded 时运行我们的代码,以使 TransferState 正常工作
document.addEventListener('DOMContentLoaded', () => {
    platformBrowserDynamic().bootstrapModule(AppBrowserModule);
});
Copier après la connexion

5、创建 TypeScript 的服务端配置:src/tsconfig.server.json

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "baseUrl": "./",
    "module": "commonjs",
    "types": [
      "node"
    ]
  },
  "exclude": [
    "test.ts",
    "**/*.spec.ts"
  ],
  "angularCompilerOptions": {
    "entryModule": "app/app.server.module#AppServerModule"
  }
}
Copier après la connexion

tsconfig.app.json 的差异在于:

  • module 属性必须是 commonjs,这样它才能被 require() 方法导入你的服务端应用。

  • angularCompilerOptions 部分有一些面向 AOT 编译器的选项:

    • entryModule - 服务端应用的根模块,其格式为 path/to/file#ClassName。

6、修改 @angular/cli 的配置文件:.angular-cli.json

apps 下添加:

{
    "platform": "server",
    "root": "src",
    "outDir": "dist/server",
    "assets": [
      "assets",
      "favicon.ico"
    ],
    "index": "index.html",
    "main": "main.server.ts",
    "test": "test.ts",
    "tsconfig": "tsconfig.server.json",
    "testTsconfig": "tsconfig.spec.json",
    "prefix": "",
    "styles": [
      "styles.scss"
    ],
    "scripts": [],
    "environmentSource": "environments/environment.ts",
    "environments": {
      "dev": "environments/environment.ts",
      "prod": "environments/environment.prod.ts"
    }
}
Copier après la connexion

7、创建 Node Express 的服务程序:server.ts

import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { enableProdMode } from '@angular/core';

import * as express from 'express';
import { join } from 'path';
import { readFileSync } from 'fs';

// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();

// Express server
const app = express();

const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist');

// Our index.html we'll use as our template
const template = readFileSync(join(DIST_FOLDER, 'browser', 'index.html')).toString();

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main.bundle');

// Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';

// 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', join(DIST_FOLDER, 'browser'));

/* - Example Express Rest API endpoints -
  app.get('/api/**', (req, res) => { });
*/

// 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}`);
});
Copier après la connexion
Universal 模板引擎

这个文件中最重要的部分是 ngExpressEngine 函数:

app.engine('html', ngExpressEngine({
    bootstrap: AppServerModuleNgFactory,
    providers: [
        provideModuleMap(LAZY_MODULE_MAP)
    ]
}));
Copier après la connexion

ngExpressEngine 是对 Universal 的 renderModuleFactory 函数的封装。它会把客户端请求转换成服务端渲染的 HTML 页面。如果你使用不同于Node的服务端技术,你需要在该服务端的模板引擎中调用这个函数。

  • 第一个参数是你以前写过的 AppServerModule。 它是 Universal 服务端渲染器和你的应用之间的桥梁。

  • 第二个参数是 extraProviders。它是在这个服务器上运行时才需要的一些可选的 Angular 依赖注入提供商。当你的应用需要那些只有当运行在服务器实例中才需要的信息时,就要提供 extraProviders 参数。

ngExpressEngine 函数返回了一个会解析成渲染好的页面的承诺(Promise)。

接下来你的引擎要决定拿这个页面做点什么。 现在这个引擎的回调函数中,把渲染好的页面返回给了 Web 服务器,然后服务器通过 HTTP 响应把它转发给了客户端。

8、创建服务端预渲染的程序:prerender.ts

// 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 => {
    const 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));
});
Copier après la connexion

9、创建 Webpack 的服务端配置:webpack.server.config.js

Universal 应用不需要任何额外的 Webpack 配置,Angular CLI 会帮我们处理它们。但是由于本例子的 Node Express 的服务程序是 TypeScript 应用(server.ts及prerender.ts),所以要使用 Webpack 来转译它。这里不讨论 Webpack 的配置,需要了解的移步 Webpack官网

// Work around for https://github.com/angular/angular-cli/issues/7200

const path = require('path');
const webpack = require('webpack');

module.exports = {
    entry: {
        server: './server.ts', // This is our Express server for Dynamic universal
        prerender: './prerender.ts' // This is an example of Static prerendering (generative)
    },
    target: 'node',
    resolve: {extensions: ['.ts', '.js']},
    externals: [/(node_modules|main\..*\.js)/,], // Make sure we include all node_modules etc
    output: {
        path: path.join(__dirname, 'dist'), // Puts the output at the root of the dist folder
        filename: '[name].js'
    },
    module: {
        rules: [
            {test: /\.ts$/, loader: 'ts-loader'}
        ]
    },
    plugins: [
        new webpack.ContextReplacementPlugin(
            /(.+)?angular(\\|\/)core(.+)?/, // fixes WARNING Critical dependency: the request of a dependency is an expression
            path.join(__dirname, 'src'), // location of your src
            {} // a map of your routes
        ),
        new webpack.ContextReplacementPlugin(
            /(.+)?express(\\|\/)(.+)?/, // fixes WARNING Critical dependency: the request of a dependency is an expression
            path.join(__dirname, 'src'),
            {}
        )
    ]
};
Copier après la connexion

测试配置

通过上面的配置,我们就制作完成一个可在服务端渲染的 Angular Universal 应用。

在 package.json 的 scripts 区配置 build 和 serve 有关的命令:

{
    "scripts": {
        "ng": "ng",
        "start": "ng serve -o",
        "ssr": "npm run build:ssr && npm run serve:ssr",
        "prerender": "npm run build:prerender && npm run serve:prerender",
        "build": "ng build",
        "build:client-and-server-bundles": "ng build --prod && ng build --prod --app 1 --output-hashing=false",
        "build:prerender": "npm run build:client-and-server-bundles && npm run webpack:server && npm run generate:prerender",
        "build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
        "generate:prerender": "cd dist && node prerender",
        "webpack:server": "webpack --config webpack.server.config.js --progress --colors",
        "serve:prerender": "cd dist/browser && http-server",
        "serve:ssr": "node dist/server"
    }
}
Copier après la connexion

开发只需运行 npm run start

执行 npm run ssr 编译应用程序,并启动一个Node Express来为应用程序提供服务 http://localhost:4000

dist目录:

Pratique de développement angulaire (6) : rendu côté serveur

执行npm run prerender - 编译应用程序并预渲染应用程序文件,启动一个演示http服务器,以便您可以查看它 http://localhost:8080

注意: 要将静态网站部署到静态托管平台,您必须部署dist/browser文件夹, 而不是dist文件夹

dist目录:

Pratique de développement angulaire (6) : rendu côté serveur

根据项目实际的路由信息并在根目录的 static.paths.ts 中配置,提供给 prerender.ts 解析使用。

export const ROUTES = [
    '/',
    '/lazy'
];
Copier après la connexion

因此,从dist目录可以看到,服务端预渲染会根据配置好的路由在 browser 生成对应的静态index.html。如 / 对应 /index.html/lazy 对应 /lazy/index.html

服务端的模块懒加载

在前面的介绍中,我们在 app.server.module.ts 中导入了 ModuleMapLoaderModule,在 app.module.ts

ModuleMapLoaderModule 模块可以使得懒加载的模块也可以在服务端进行渲染,而你要做也只是在 app.server.module.ts 中导入。

服务端到客户端的状态传输

在前面的介绍中,我们在 app.server.module.ts 中导入了 ServerTransferStateModule,在 app.module.ts 中导入了 BrowserTransferStateModule 和 TransferHttpCacheModule。

这三个模块都与服务端到客户端的状态传输有关:

  • ServerTransferStateModule:在服务端导入,用于实现将状态从服务端传输到客户端

  • BrowserTransferStateModule:在客户端导入,用于实现将状态从服务端传输到客户端

  • TransferHttpCacheModule:用于实现服务端到客户端的请求传输缓存,防止客户端重复请求服务端已完成的请求

使用这几个模块,可以解决 http请求在服务端和客户端分别请求一次 的问题。

比如在 home.component.ts 中有如下代码:

import { Component, OnDestroy, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';

@Component({
    selector: 'app-home',
    templateUrl: './home.component.html',
    styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit, OnDestroy {
    constructor(public http: HttpClient) {
    }
    
    ngOnInit() {
        this.poiSearch(this.keyword, '北京市').subscribe((data: any) => {
            console.log(data);
        });
    }
    
    ngOnDestroy() {
    }
    
    poiSearch(text: string, city?: string): Observable<any> {
        return this.http.get(encodeURI(`http://restapi.amap.com/v3/place/text?keywords=${text}&city=${city}&offset=20&key=55f909211b9950837fba2c71d0488db9&extensions=all`));
    }
}
Copier après la connexion

代码运行之后,

服务端请求并打印:

Pratique de développement angulaire (6) : rendu côté serveur

客户端再一次请求并打印:

Pratique de développement angulaire (6) : rendu côté serveur

方法1:使用 TransferHttpCacheModule

使用 TransferHttpCacheModule 很简单,代码不需要改动。在 app.module.ts 中导入之后,Angular自动会将服务端请求缓存到客户端,换句话说就是服务端请求到数据会自动传输到客户端,客户端接收到数据之后就不会再发送请求了。

方法2:使用 BrowserTransferStateModule

该方法稍微复杂一些,需要改动一些代码。

调整 home.component.ts 代码如下:

import { Component, OnDestroy, OnInit } from '@angular/core';
import { makeStateKey, TransferState } from '@angular/platform-browser';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';

const KFCLIST_KEY = makeStateKey('kfcList');

@Component({
    selector: 'app-home',
    templateUrl: './home.component.html',
    styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit, OnDestroy {
    constructor(public http: HttpClient,
                private state: TransferState) {
    }
    
    ngOnInit() {
    
        // 采用一个标记来区分服务端是否已经拿到了数据,如果没拿到数据就在客户端请求,如果已经拿到数据就不发请求
        const kfcList:any[] = this.state.get(KFCLIST_KEY, null as any);

        if (!this.kfcList) {
            this.poiSearch(this.keyword, '北京市').subscribe((data: any) => {
                console.log(data);
                this.state.set(KFCLIST_KEY, data as any); // 存储数据
            });
        }
    }
    
    ngOnDestroy() {
        if (typeof window === 'object') {
            this.state.set(KFCLIST_KEY, null as any); // 删除数据
        }
    }
    
    poiSearch(text: string, city?: string): Observable<any> {
        return this.http.get(encodeURI(`http://restapi.amap.com/v3/place/text?keywords=${text}&city=${city}&offset=20&key=55f909211b9950837fba2c71d0488db9&extensions=all`));
    }
}
Copier après la connexion
  • 使用 const KFCLIST_KEY = makeStateKey('kfcList') 创建储存传输数据的 StateKey

  • HomeComponent 的构造函数中注入 TransferState

  • ngOnInit 中根据 this.state.get(KFCLIST_KEY, null as any) 判断数据是否存在(不管是服务端还是客户端),存在就不再请求,不存在则请求数据并通过 this.state.set(KFCLIST_KEY, data as any) 存储传输数据

  • ngOnDestroy 中根据当前是否客户端来决定是否将存储的数据进行删除

客户端与服务端渲染对比

最后,我们分别通过这三个原因来进行对比:

  1. 帮助网络爬虫(SEO)

  2. 提升在手机和低功耗设备上的性能

  3. 迅速显示出首页

帮助网络爬虫(SEO)

客户端渲染:

Pratique de développement angulaire (6) : rendu côté serveur

服务端渲染:

Pratique de développement angulaire (6) : rendu côté serveur

从上面可以看到,服务端提前将信息渲染到返回的页面上,这样网络爬虫就能直接获取到信息了(网络爬虫基本不会解析javascript的)。

提升在手机和低功耗设备上的性能

这个原因通过上面就可以看出,对于一些低端的设备,直接显示页面总比要解析javascript性能高的多。

迅速显示出首页

同样在 Fast 3G 网络条件下进行测试

客户端渲染:

Pratique de développement angulaire (6) : rendu côté serveur

服务端渲染:

Pratique de développement angulaire (6) : rendu côté serveur

牢记几件事情

  • 对于服务器软件包,您可能需要将第三方模块包含到nodeExternals白名单中

  • window, document, navigator 以及其它的浏览器类型 - 不存在于服务端 - 如果你直接使用,在服务端将无法正常工作。 以下几种方法可以让你的代码正常工作:

    • 可以通过PLATFORM_ID标记注入的Object来检查当前平台是浏览器还是服务器,然后使用浏览器端特有的类型

       import { PLATFORM_ID } from '@angular/core';
       import { isPlatformBrowser, isPlatformServer } from '@angular/common';
       
       constructor(@Inject(PLATFORM_ID) private platformId: Object) { ... }
       
       ngOnInit() {
         if (isPlatformBrowser(this.platformId)) {
            // 仅运行在浏览器端的代码
            ...
         }
         if (isPlatformServer(this.platformId)) {
           // 仅运行在服务端的代码
           ...
         }
       }
      Copier après la connexion
 - 尽量**限制**或**避免**使用`setTimeout`。它会减慢服务器端的渲染过程。确保在组件的`ngOnDestroy`中删除它们
 
 - 对于RxJs超时,请确保在成功时 _取消_ 它们的流,因为它们也会降低渲染速度。
Copier après la connexion
  • 不要直接操作nativeElement,使用Renderer2,从而可以跨平台改变应用视图。

constructor(element: ElementRef, renderer: Renderer2) {
  this.renderer.setStyle(element.nativeElement, 'font-size', 'x-large');
}
Copier après la connexion
  • 解决应用程序在服务器上运行XHR请求,并在客户端再次运行的问题

    • 使用从服务器传输到客户端的缓存(TransferState)

  • 清楚了解与DOM相关的属性和属性之间的差异

  • 尽量让指令无状态。对于有状态指令,您可能需要提供一个属性,以反映相应属性的初始字符串值,例如img标签中的url。对于我们的native元素,src属性被反映为元素类型HTMLImageElement的src属性

相关推荐:

Angular开发实践(五):深入解析变化监测

Angular开发实践(四):组件之间的交互

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal