关于 Angular Universal 应用渲染两次的问题

比特破浪者
• 阅读 229

Angular Repository url:https://github.com/angular/angular-cli/issues/7477

现象:

I built a sample repo using angular-cli and followed the steps in the Universal Rendering story to enable server side rendering. The application loads well on running, but I see the client rendering also happening after the page is served from the server. So the page gets rendered from the server and then the content disappears for a second as the client rendering happens.

观察到 page 从服务器端 serve 之后,client side rendering 还会再次发生。有一瞬间的功夫,服务器端渲染的内容消失了,client rendering 接着发生。

有网友回复:

The server renders the page without any state and outputs static HTML to view in the browser while the angular application is being bootstrapped. A re-render when the application has been bootstrapped is required to use state saved in the browser such as logged in user information.

SSR 渲染出的是纯粹的 Static HTML,没有任何状态,也无法响应用户的交互。

当应用在客户端完成 Boostrap 之后,re-render 是必要的,以便使用服务器端静态 HTML 里嵌入的通过 State Transfer 传递的业务数据(这些业务数据在 SSR 时通过服务器端执行的 AJAX,从 API server 读取)。

The initial page contains static HTML which needs to be replaced with your angular application once bootstrapped.

这些静态页面的内容,需要被 Angular 应用在客户端 Bootstrap 之后重新渲染的内容所替换。

To make the transfer state work, the Angular application must be bootstrapped after the DOM is loaded. This is because, the data transferred from the server is stored in an HTML element and this element should be accessible to Angular to get the data. Open the file main.ts and move the application bootstrap logic inside the DOMContentLoaded event as shown below:

因为 Transfer State 的工作机制是解析嵌入在 HTML 页面里的 DOM 元素,所以客户端渲染的 Angular 应用,需要等待 DOM 加载之后再进行 Bootstrap.

为此我们需要在 main.ts 文件里编写下面的代码:

document.addEventListener('DOMContentLoaded', () => {  
  platformBrowserDynamic().bootstrapModule(AppModule)    
  .catch(err => console.log(err));
});

这段代码是Angular应用的入口点,它有以下作用:

  1. 等待文档加载完成document.addEventListener('DOMContentLoaded', ...) 表示在文档(DOM)加载完成后执行特定的操作。这是为了确保JavaScript代码在HTML文档完全加载后运行,以避免在DOM元素还未准备好的情况下执行操作,从而避免潜在的错误。
  2. 动态启动Angular应用platformBrowserDynamic().bootstrapModule(AppModule) 是Angular的核心方法之一,它用于动态启动(bootstrap)一个Angular应用。在这里,它将 AppModule 作为应用的根模块来启动。
  3. 处理启动失败.catch(err => console.log(err)) 表示在启动过程中如果出现错误,将错误信息打印到控制台。这是为了帮助开发人员调试应用并捕获潜在的问题。

让我们详细解释每个部分,并举例说明其作用:

1. 等待文档加载完成 (document.addEventListener('DOMContentLoaded', ...))

这是为了确保在HTML文档完全加载后再执行Angular应用的启动过程。当浏览器加载HTML页面时,它按照从上到下的顺序逐步构建DOM树。如果你的JavaScript代码在DOM还没有完全加载之前执行,那么它可能无法找到所需的DOM元素,导致错误或不稳定的行为。

例如,假设你的Angular应用需要在特定的HTML元素上渲染内容,如果你在DOM未加载完成时尝试访问这些元素,可能会出现问题。通过等待DOMContentLoaded 事件,你可以确保DOM已准备就绪,然后再执行Angular应用的启动。

document.addEventListener('DOMContentLoaded', () => {
  // 在这里启动Angular应用
});

2. 动态启动Angular应用 (platformBrowserDynamic().bootstrapModule(AppModule))

这部分代码是启动Angular应用的核心。它使用了Angular的 platformBrowserDynamic 类的实例来动态启动一个Angular应用,然后将 AppModule 作为应用的根模块。这意味着你的Angular应用将从 AppModule 开始加载,并构建整个应用。

让我们来看一个示例,假设有一个名为 AppModule 的Angular模块,它定义了应用的根组件和其他相关配置。这是一个典型的Angular模块定义:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

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

@NgModule({
  imports: [BrowserModule],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule { }

在这个示例中,AppModule 使用 @NgModule 装饰器来定义模块,它引入了 BrowserModule 作为依赖,声明了根组件 AppComponent,并将 AppComponent 标记为启动组件(bootstrap 属性)。这意味着 AppComponent 将成为应用的根组件。

在主要的 main.ts 文件中,我们使用 platformBrowserDynamic().bootstrapModule(AppModule) 来启动应用,这将启动整个应用并将 AppComponent 渲染到页面上。

document.addEventListener('DOMContentLoaded', () => {
  platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.log(err));
});

这段代码会在文档加载完成后,启动Angular应用,并且任何错误(如果有)都会被捕获并打印到控制台。

3. 处理启动失败 (.catch(err => console.log(err)))

最后一部分代码是使用 .catch() 方法处理启动过程中可能出现的错误。在实际应用中,启动Angular应用可能会面临各种问题,如依赖注入失败、组件未找到、模块配置错误等。通过使用 .catch() 方法,你可以捕获这些错误并将它们记录到控制台,以便进行调试。

示例代码如下:

document.addEventListener('DOMContentLoaded', () => {
  platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.log(err));
});

在这个示例中,如果在启动应用过程中出现了任何错误,错误信息将被传递给 err,然后使用 console.log() 打印到控制台,以便开发人员可以查看错误详情。

总结来说,这段代码的主要作用是等待文档加载完成后,动态启动Angular应用,并在启动过程中处理可能出现的错误。这是Angular应用的入口点,确保应用在稳定的环境中启动,并为开发人员提供了一种捕获和调试潜在问题的机制。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(