>  기사  >  웹 프론트엔드  >  Angular 개발 실습 서버사이드 렌더링

Angular 개발 실습 서버사이드 렌더링

亚连
亚连원래의
2018-05-28 14:07:481244검색

이 글은 주로 Angular 개발 실습에서 서버사이드 렌더링을 소개하고 있으니 참고용으로 공유해보겠습니다.

Angular Universal

Angular는 서버 측 렌더링을 위한 프런트엔드 및 백엔드 동형 솔루션을 제공하는 서버 측에서 Angular 애플리케이션을 실행하는 기술인 Angular Universal(통합 플랫폼)입니다.

표준 Angular 애플리케이션은 브라우저에서 실행되며 사용자 작업에 응답하여 DOM의 페이지를 렌더링합니다.

Angular Universal은 서버 측 렌더링(SSR)이라는 프로세스를 통해 서버에 정적 애플리케이션 페이지를 생성합니다.

이러한 페이지를 생성하고 브라우저에서 요청할 때 직접 응답할 수 있습니다. 또한 페이지를 HTML 파일로 미리 생성한 다음 이를 서버의 정적 파일로 제공할 수도 있습니다.

작동 방식

유니버설 애플리케이션을 만들려면 platform-server 패키지를 설치해야 합니다. 플랫폼 서버 패키지는 서버측 DOM 구현, XMLHttpRequest 및 기타 하위 수준 기능을 제공하지만 더 이상 브라우저에 의존하지 않습니다. platform-server 包。 platform-server 包提供了服务端的 DOM 实现、XMLHttpRequest 和其它底层特性,但不再依赖浏览器。

你要使用 platform-server 模块而不是 platform-browser 模块来编译这个客户端应用,并且在一个 Web 服务器上运行这个 Universal 应用。

服务器(下面的示例中使用的是 Node Express 服务器)会把客户端对应用页面的请求传给 renderModuleFactory 函数。

renderModuleFactory 函数接受一个模板 HTML 页面(通常是 index.html)、一个包含组件的 Angular 模块和一个用于决定该显示哪些组件的路由作为输入。

该路由从客户端的请求中传给服务器。 每次请求都会给出所请求路由的一个适当的视图。

renderModuleFactory 在模板中的 7ab1d477d2ef4cffc4b2f0ef9c222635 标记中渲染出哪个视图,并为客户端创建一个完成的 HTML 页面。

最后,服务器就会把渲染好的页面返回给客户端。

为什么要服务端渲染

三个主要原因:

  1. 帮助网络爬虫(SEO)

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

  3. 迅速显示出第一个页面

帮助网络爬虫(SEO)

Google、Bing、百度、Facebook、Twitter 和其它搜索引擎或社交媒体网站都依赖网络爬虫去索引你的应用内容,并且让它的内容可以通过网络搜索到。

这些网络爬虫可能不会像人类那样导航到你的具有高度交互性的 Angular 应用,并为其建立索引。

Angular Universal 可以为你生成应用的静态版本,它易搜索、可链接,浏览时也不必借助 JavaScript。它也让站点可以被预览,因为每个 URL 返回的都是一个完全渲染好的页面。

启用网络爬虫通常被称为搜索引擎优化 (SEO)。

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

有些设备不支持 JavaScript 或 JavaScript 执行得很差,导致用户体验不可接受。 对于这些情况,你可能会需要该应用的服务端渲染、无 JavaScript 的版本。 虽然有一些限制,不过这个版本可能是那些完全没办法使用该应用的人的唯一选择。

快速显示首页

快速显示首页对于吸引用户是至关重要的。

如果页面加载超过了三秒中,那么 53% 的移动网站会被放弃。 你的应用需要启动的更快一点,以便在用户决定做别的事情之前吸引他们的注意力。

使用 Angular Universal,你可以为应用生成“着陆页”,它们看起来就和完整的应用一样。 这些着陆页是纯 HTML,并且即使 JavaScript 被禁用了也能显示。 这些页面不会处理浏览器事件,不过它们可以用 routerLink 在这个网站中导航。

在实践中,你可能要使用一个着陆页的静态版本来保持用户的注意力。 同时,你也会在幕后加载完整的 Angular 应用。 用户会认为着陆页几乎是立即出现的,而当完整的应用加载完之后,又可以获得完全的交互体验。

示例解析

下面将基于我在GitHub上的示例项目 angular-universal-starter 来进行讲解。

这个项目与第一篇的示例项目一样,都是基于 Angular CLI进行开发构建的,因此它们的区别只在于服务端渲染所需的那些配置上。

安装工具

在开始之前,下列包是必须安装的(示例项目均已配置好,只需 npm install 即可):

  1. @angular/platform-server - Universal 的服务端元件。

  2. @nguniversal/module-map-ngfactory-loader

    클라이언트 애플리케이션을 컴파일하고 웹 서버에서 유니버설 애플리케이션을 실행하려면 platform-browser 모듈 대신 platform-server 모듈을 사용해야 합니다. 🎜🎜서버(아래 예에서는 Node Express 서버가 사용됨)는 애플리케이션 페이지에 대한 클라이언트의 요청을 renderModuleFactory 함수에 전달합니다. 🎜🎜renderModuleFactory 함수는 템플릿 HTML 페이지(일반적으로 index.html), 구성 요소가 포함된 Angular 모듈, 표시할 구성 요소를 결정하는 경로를 입력으로 받아들입니다. 🎜🎜이 경로는 클라이언트의 요청에 따라 서버로 전달됩니다. 각 요청은 요청된 경로에 대한 적절한 보기를 제공합니다. 🎜🎜renderModuleFactory는 템플릿의 7ab1d477d2ef4cffc4b2f0ef9c222635 태그에 있는 뷰를 렌더링하고 클라이언트를 위한 완성된 HTML 페이지를 생성합니다. 🎜🎜마지막으로 서버는 렌더링된 페이지를 클라이언트에 반환합니다. 🎜🎜🎜서버 측 렌더링이 필요한 이유🎜🎜🎜세 가지 주요 이유: 🎜
    1. 🎜웹 크롤러(SEO) 지원 🎜
    2. 🎜성능 향상 휴대폰 및 저전력 장치에서🎜
    3. 🎜첫 페이지를 빠르게 표시🎜
    🎜🎜웹 크롤러(SEO)에 도움이 됩니다🎜🎜🎜Google, Bing, Baidu, Facebook, Twitter 및 기타 검색 엔진이나 소셜 미디어 사이트는 모두 웹 크롤러를 사용하여 앱 콘텐츠를 색인화하고 웹에서 콘텐츠를 검색할 수 있도록 합니다. 🎜🎜이러한 웹 크롤러는 인간처럼 대화형 Angular 앱을 탐색하고 색인을 생성하지 못할 수 있습니다. 🎜🎜Angular Universal은 JavaScript 없이도 검색, 연결 및 탐색이 가능한 정적 버전의 애플리케이션을 생성할 수 있습니다. 또한 각 URL이 완전히 렌더링된 페이지를 반환하므로 사이트를 미리 볼 수 있습니다. 🎜🎜웹 크롤러를 활성화하는 것을 흔히 검색 엔진 최적화(SEO)라고 합니다. 🎜🎜🎜휴대폰 및 저전력 장치의 성능 향상🎜🎜🎜일부 장치에서는 JavaScript를 지원하지 않거나 JavaScript가 제대로 구현되지 않아 허용할 수 없는 사용자 경험이 발생합니다. 이러한 경우에는 서버에서 렌더링되고 JavaScript가 없는 앱 버전이 필요할 수 있습니다. 몇 가지 제한 사항이 있지만 앱을 전혀 사용할 수 없는 사람들에게는 이 버전이 유일한 옵션일 수 있습니다. 🎜🎜🎜홈페이지를 빠르게 표시🎜🎜🎜홈페이지를 빠르게 표시하는 것은 사용자의 관심을 끄는 데 중요합니다. 🎜🎜페이지를 로드하는 데 3초 이상 걸리면 모바일 웹사이트의 53%가 이탈됩니다. 사용자가 다른 작업을 하기로 결정하기 전에 사용자의 관심을 끌 수 있도록 앱을 더 빠르게 실행해야 합니다. 🎜🎜Angular Universal을 사용하면 전체 애플리케이션처럼 보이는 애플리케이션의 "랜딩 페이지"를 생성할 수 있습니다. 이러한 랜딩 페이지는 순수 HTML이며 JavaScript가 비활성화된 경우에도 표시됩니다. 이러한 페이지는 브라우저 이벤트를 처리하지 않지만 routerLink를 사용하여 사이트 내에서 탐색할 수 있습니다. 🎜🎜실제로는 사용자의 관심을 끌기 위해 정적 버전의 랜딩 페이지를 사용할 수 있습니다. 동시에 무대 뒤에서 완전한 Angular 애플리케이션도 로드하게 됩니다. 사용자는 랜딩 페이지가 거의 즉시 나타날 것으로 기대하며, 전체 앱이 로드되면 완전한 대화형 경험을 갖게 됩니다. 🎜🎜🎜예제 분석🎜🎜🎜다음은 GitHub의 예제 프로젝트인 angle-universal-starter를 기반으로 설명합니다. 🎜🎜이 프로젝트는 첫 번째 기사의 예제 프로젝트와 마찬가지로 Angular CLI를 기반으로 개발 및 구축되었으므로 두 프로젝트 간의 유일한 차이점은 서버 측 렌더링에 필요한 구성입니다. 🎜🎜🎜설치 도구🎜🎜🎜시작하기 전에 다음 패키지를 설치해야 합니다(샘플 프로젝트는 npm install으로 구성됨). 🎜
    1. 🎜@angular/platform-server - Universal의 서버 구성요소입니다. 🎜
    2. 🎜@nguniversal/module-map-ngfactory-loader - 서버 측 렌더링 환경에서 지연 로딩을 처리하는 데 사용됩니다. 🎜
    3. @nguniversal/express-engine - Universal 应用的 Express 引擎。

    4. ts-loader - 用于对服务端应用进行转译。

    5. express - Node Express 服务器

    使用下列命令安装它们:

    npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader @nguniversal/express-engine express

    项目配置

    配置工作有:

    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 {
    }

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

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

    @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}`);
      }
    }

    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}`);
    }

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

    该文件导出服务端模块:

    export { AppServerModule } from './app/app.server.module';

    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);
    });

    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"
     }
    }

    tsconfig.app.json 的差异在于:

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

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

    1. 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"
      }
    }

    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}`);
    });

    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));
    });

    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'),
          {}
        )
      ]
    };

    测试配置

    通过上面的配置,我们就制作完成一个可在服务端渲染的 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"
      }
    }

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

    dist目录:

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

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

    dist目录:

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

    export const ROUTES = [
      '/',
      '/lazy'
    ];

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

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

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

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

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

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

    3. 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`));
      }
    }

    代码运行之后,

    服务端请求并打印:

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

    方法1:使用 TransferHttpCacheModule

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

    方法2:使用 BrowserTransferStateModule

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

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

    import { Component, OnDestroy, OnInit } from &#39;@angular/core&#39;;
    import { makeStateKey, TransferState } from &#39;@angular/platform-browser&#39;;
    import { HttpClient } from &#39;@angular/common/http&#39;;
    import { Observable } from &#39;rxjs/Observable&#39;;
    
    const KFCLIST_KEY = makeStateKey(&#39;kfcList&#39;);
    
    @Component({
      selector: &#39;app-home&#39;,
      templateUrl: &#39;./home.component.html&#39;,
      styleUrls: [&#39;./home.component.scss&#39;]
    })
    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, &#39;北京市&#39;).subscribe((data: any) => {
            console.log(data);
            this.state.set(KFCLIST_KEY, data as any); // 存储数据
          });
        }
      }
      
      ngOnDestroy() {
        if (typeof window === &#39;object&#39;) {
          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`));
      }
    }
    1. 使用 const KFCLIST_KEY = makeStateKey('kfcList') 创建储存传输数据的 StateKey

    2. HomeComponent 的构造函数中注入 TransferState

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

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

    上面是我整理给大家的,希望今后会对大家有帮助。

    相关文章:

    Js面试算法详解

    JS简单获取并修改input文本框内容的方法示例

    详解vue表单

위 내용은 Angular 개발 실습 서버사이드 렌더링의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.