angular 服务器渲染_带有Angular Universal的Angular 2中的服务器端渲染

angular 服务器渲染

Server-side rendering is a headache and if you ever worked with Angular 1, you should be worried about how Angular 2 plans to handle it. Server-side rendering in Angular 2 is often-times also called Universal.

服务器端渲染令人头疼,如果您曾经使用过Angular 1,则应该担心Angular 2计划如何处理它。 Angular 2中的服务器端渲染通常也称为Universal

You might often hear people say stuff like: "I am building an app that will be universal". What does that even mean?

您可能经常听到人们说诸如此类的话:“我正在构建一个通用的应用程序”。 那有什么意思?

什么是通用? ( What is Universal? )

When we build apps, we consider the following elements:

在构建应用程序时,我们考虑以下要素:

  1. Content

    内容
  2. Style

    样式
  3. State

By default, front end frameworks have virtual DOM so when a hard request is made to an app built with such frameworks (like Angular), the expected behavior is weird. The request is served with a response of just the HTML content found at the entry point of the app while the main app content is lost.

默认情况下,前端框架具有虚拟DOM,因此当对使用此类框架(例如Angular)构建的应用发出硬性请求时,预期的行为很奇怪。 该请求仅包含在应用程序入口点找到HTML 内容的响应,而主要应用程序内容丢失。

This is not a problem to your users because your app virtually has content, styles, and state. Where the problem kicks in is with web crawlers that index your site for SEO sake. They get served with the incomplete HTML content which is very useless to them. This behavior even gets more painful when you expect people to share your app's link and you see an image like the one below when they do:

这对您的用户来说不是问题,因为您的应用程序实际上具有内容样式状态 。 问题出在哪里是Web搜寻器,这些搜索器为SEO索引了您的网站。 他们得到了不完整HTML 内容 ,这对他们来说是毫无用处的。 当您期望人们共享应用程序的链接并且当他们看到以下图像时,这种行为会变得更加痛苦:

angular 服务器渲染_带有Angular Universal的Angular 2中的服务器端渲染_第1张图片

In Angular 1, if you tried to bind the app's title and description values using $rootScope you end up disappointed at how crawlers and social media robots see your site.

在Angular 1中,如果您尝试使用$rootScope绑定应用程序的标题和描述值,您最终会对爬虫和社交媒体机器人看到您的网站感到失望。

To replicate this challenge in Angular 2, I built an Angular 2 app with three routes (home, about and contact):

为了在Angular 2中重现这一挑战,我构建了一个Angular 2应用,其中包含以下三种路线(家庭,关于和联系):

angular 服务器渲染_带有Angular Universal的Angular 2中的服务器端渲染_第2张图片

Preview

预习

angular 服务器渲染_带有Angular Universal的Angular 2中的服务器端渲染_第3张图片

Source

资源


<app-root>Loading...app-root>

The conventional server rendering solution has saved us for years with Angular 1 by provisioning web crawlers with our actual content. That seemed to keep us happy but we were missing one thing -- state.

常规的服务器渲染解决方案通过为Web爬网程序提供实际内容为Angular 1节省了我们很多年。 那似乎使我们快乐,但是我们却错过了一件事- 状态

The Universal idea is to build an app that does not just render to server but also runs on the server. Running in the sense that our state, content and styles are intact on the client and the server as well. In Angular 2, this is achieved with the help of Angular Universal which loads our app on the server first, and then drops it to the browser once ready.

通用想法是构建一个不仅可以呈现到服务器而且可以在服务器上运行的应用程序。 从某种意义上说,我们的状态,内容和样式在客户端和服务器上都是完整的。 在Angular 2中,这是通过Angular Universal的帮助实现的,它将首先将我们的应用程序加载到服务器上,然后在准备好后将其放到浏览器中。

什么是Angular Universal? ( What is Angular Universal? )

angular 服务器渲染_带有Angular Universal的Angular 2中的服务器端渲染_第4张图片

Angular Universal is the library the awesome Angular team is working on to make building universal apps a smooth experience. The library fixes a lot of nightmare we had working with Angular 1. As a matter of fact, our worst nightmare in Angular 1 is what Angular Universal takes away in Angular 2:

Angular Universal是令人敬畏的Angular团队正在努力的图书馆,旨在使构建通用应用程序具有流畅的体验。 该库解决了我们使用Angular 1时遇到的许多噩梦。事实上,我们在Angular 1中最糟糕的噩梦是Angular Universal在Angular 2中所消除的。

SEO友好 (SEO Friendly)

Angular Universal helps you to serve your app content to the server just as you did on the browser. When a web crawlers visit, they will be able to index you website's full content that can be used on search engines. This thereby solves one of the most popular front end challenges when working with JavaScript frameworks that create virtual DOM.

Angular Universal可以像在浏览器中一样帮助您将应用程序内容提供给服务器。 当网络爬虫访问时,他们将能够索引您网站上可在搜索引擎上使用的全部内容。 因此,这可以解决使用创建虚拟DOMJavaScript框架时最受欢迎的前端挑战之一。

社交媒体友好 (Social Media Friendly)

With Angular Universal serving your sever with browser content, social media platforms that display brief information about your website when a link is added can get access to the needed details.

Angular Universal通过浏览器内容为您的服务器提供服务,添加链接后显示有关您的网站的简要信息的社交媒体平台可以访问所需的详细信息。

预渲染 (Pre-rendering)

This is the most bind-blowing feature offered by Angular Universal. Angular Universal renders both your content and state as well as registering events on the server. The implication is that when your app boots, the server would serve the server-rendered content and a user can make use of the app at that stage.

这是Angular Universal提供的最具约束力的功能。 Angular Universal可以渲染您的内容和状态,以及在服务器上注册事件。 含义是,当您的应用启动时,服务器将提供服务器渲染的内容,并且用户可以在该阶段使用该应用。

The user's activities are recorded (with Preboot.js) at this stage and after couple of seconds when Angular is ready with the real thing, the user is automatically switched and those events replayed. The user won't even catch a glimpse of what is happening under the hood.

在此阶段(使用Preboot.js )记录用户的活动,并在Angular准备好真实事物后几秒钟后,将自动切换用户并重播这些事件。 用户甚至不会瞥见引擎盖下正在发生的事情。

入门 ( Getting Started )

Now that we have seen what Universal and Angular Universal are, how do we join the party? Angular Universal is coming to Angular CLI real soon but before that we have projects to build now.

现在我们已经了解了什么是Universal和Angular Universal,我们如何加入该党? Angular Universal即将正式投入Angular CLI的使用,但是在此之前,我们现在要构建项目。

通用入门项目 (Universal Starter Project)

Patrick built an awesome starter for Angular Universal apps which we are going to clone so as to test out these awesome tool.

Patrick为Angular Universal应用程序构建了一个很棒的启动器 ,我们将对其进行克隆,以测试这些出色的工具。

# Clone repo
git clone https://github.com/angular/universal-starter scotchiversal

#Install modules
cd scotchiversal & npm i

If you have been building Angular 2 app lately, you will see that the directory looks much alike to what we are used to. The key difference is the bootstrapping process. Bootstrapping Universal apps are done with a different library and in two different files (client.ts & server.ts).

如果您最近一直在构建Angular 2应用程序,则将看到该目录与我们以前使用的目录非常相似。 关键区别在于引导过程。 引导通用应用程序是通过不同的库和两个不同的文件( client.tsserver.ts )完成的。

Furthermore, we usually make use of platformBrowserDynamic to bootstrap but for Universal, we use platformUniversalLibrary from the Angular Universal library:

此外,我们通常使用platformBrowserDynamic进行引导,但对于Universal,我们使用Angular Universal库中的platformUniversalLibrary

// ./src/client.ts
// the polyfills must be the first thing imported
import 'angular2-universal-polyfills';

// Angular 2
import { enableProdMode } from '@angular/core';
import { platformUniversalDynamic } from 'angular2-universal/browser';
import { bootloader } from '@angularclass/bootloader';

import { MainModule } from './browser.module';

export const platformRef = platformUniversalDynamic();

export function main() {
  return platformRef.bootstrapModule(MainModule);
}
// Bootstrap
bootloader(main);

The bootloader function just checks that the DOM is ready before bootstrapping Angular 2.

bootloader功能仅在引导Angular 2之前检查DOM是否准备就绪。

The above logic is what we are used to but this time we are making use if platformUniversalDynamic to bootstrap. The module we are bootstrapping is imported from ./src/browser.module.ts:

上面的逻辑是我们习惯的,但是这次我们要使用如果platformUniversalDynamic进行引导。 我们正在引导的模块是从./src/browser.module.ts导入的:

// ./src/browser.module.ts
import { NgModule } from '@angular/core';
import { UniversalModule } from 'angular2-universal/browser';

@NgModule({
    ...
  imports: [
    // Import this first
    UniversalModule,
    ...
  ]
})
export class MainModule {}

Same old module that we are comfortable with. We also import the UniversalModule which must be imported before every other imports.

我们满意的旧模块。 我们还导入了UniversalModule ,必须在其他所有导入之前先将其导入。

The two files we just looked at handles browser rendering, so let's look at server.ts and it's accompanying module for server rendering:

我们刚刚看过的两个文件处理浏览器渲染,因此让我们看一下server.ts及其随附的服务器渲染模块:

// ./src/server.ts
import 'angular2-universal-polyfills';

import * as path from 'path';
import * as express from 'express';
import * as bodyParser from 'body-parser';
import * as cookieParser from 'cookie-parser';
import * as morgan from 'morgan';
import * as compression from 'compression';

// Angular 2
import { enableProdMode } from '@angular/core';
// Angular 2 Universal
import { createEngine } from 'angular2-express-engine';

// App
import { MainModule } from './node.module';

// enable prod for faster renders
enableProdMode();

const app = express();
const ROOT = path.join(path.resolve(__dirname, '..'));

// Express View
app.engine('.html', createEngine({
  ngModule: MainModule,
  providers: [
      // You can include static providers like the Title service here
  ]
}));
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname);
app.set('view engine', 'html');
app.set('json spaces', 2);

app.use(cookieParser('Angular 2 Universal'));
app.use(bodyParser.json());
app.use(compression());

app.use(morgan('dev'));

function cacheControl(req, res, next) {
  // instruct browser to revalidate in 60 seconds
  res.header('Cache-Control', 'max-age=60');
  next();
}
// Serve static files
app.use('/assets', cacheControl, express.static(path.join(__dirname, 'assets'), {maxAge: 30}));
app.use(cacheControl, express.static(path.join(ROOT, 'dist/client'), {index: false}));

function ngApp(req, res) {
  res.render('index', {
    req,
    res,
    preboot: false, // turn on if using preboot
    baseUrl: '/',
    requestUrl: req.originalUrl,
    originUrl: `http://localhost:${ app.get('port') }`
  });
}
// Our page routes
export const routes: string[] = [
  'about',
  'home',
  'contact'
];
app.get('/', ngApp);
routes.forEach(route => {
  app.get(`/${route}`, ngApp);
  // Route pattern
  app.get(`/${route}/*`, ngApp);
});

app.get('*', function(req, res) {
  res.setHeader('Content-Type', 'application/json');
  var pojo = { status: 404, message: 'No Content' };
  var json = JSON.stringify(pojo, null, 2);
  res.status(404).send(json);
});

// Server
let server = app.listen(app.get('port'), () => {
  console.log(`Listening on: http://localhost:${server.address().port}`);
});

Universal is supported in Node and ASP.Net but coming to other backend platforms soon. We are using Node and to be precise to express server. The server uses the createEngine method to create a view engine that will deliver our Angular app content. The routes for each of the pages we need to render are handled as well. The rest of the code preps the express server.

Node和ASP.Net支持Universal,但很快将用于其他后端平台。 我们正在使用Node并精确地express服务器。 服务器使用createEngine方法创建一个视图引擎,该引擎将交付我们的Angular应用程序内容。 我们也需要处理每个页面的路由。 其余代码为express服务器做准备。

MainModule is imported here too and used to render our views:

MainModule这里导入,并用于呈现我们的视图:

// ./src/node.module.ts
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { UniversalModule } from 'angular2-universal/node';

@NgModule({
  imports: [
    UniversalModule,
    FormsModule,
    RouterModule,
    ...
   ],
   ...
})
export class MainModule {}

定制组件 (Custom Components)

Every other thing in the app anatomy can now be all about your custom logic. Let's create our page components:

现在,应用程序剖析中的所有其他内容都可以与您的自定义逻辑有关。 让我们创建页面组件:

Home Component

家庭组件

// ./src/app/home/home.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent {}

Home Template

主页模板


<div class="jumbotron">
  <h2 class="text-center">Homeh2>
div>
<div class="container">
  <p>. . . p>
  <p>. . . p>
  <div class="panel panel-default">
    <div class="panel-heading">Panel heading without titlediv>
    <div class="panel-body">
      Panel content
    div>
  div>
div>

Same idea applies to about and contact component so you can have fun by creating those ones yourself.

aboutcontact组件也有相同的想法,因此您可以自己创建一个组件,从而获得乐趣。

路由 (Routing)

We can now create the 3 routes we specified in the server file:

现在,我们可以创建在服务器文件中指定的3条路由:

// ./src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';

import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { ContactComponent } from './contact/contact.component';

@NgModule({
  imports: [
    RouterModule.forChild([
      { path: '', redirectTo: '/home', pathMatch: 'full' },
      { path: 'home', component: HomeComponent },
      { path: 'about', component: AboutComponent },
      { path: 'contact', component: ContactComponent }
    ])
  ],
})
export class AppRoutingModule { }

The routes should be imported into our app module before it can work:

路线应先导入到我们的应用模块中,然后才能起作用:

// ./src/app/app.module.ts
import { NgModule } from '@angular/core';

import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { ContactComponent } from './contact/contact.component';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';


@NgModule({
  declarations: [ 
    AppComponent, 
    HomeComponent,
    AboutComponent,
    ContactComponent 
  ],
  imports: [
    SharedModule,
    AppRoutingModule
  ]
})
export class AppModule {}

Remember to specify the outlet for the route as well (it is easy to forget):

记住还要指定路线的出口(很容易忘记):


<nav class="navbar navbar-default">
  <div class="container-fluid">
    <div class="navbar-header">
      <a class="navbar-brand" href="#">Scotchiversala>
    div>

    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        
        <li><a [routerLink]="['home']">Homea>li>
        <li><a [routerLink]="['about']">Abouta>li>
        <li><a [routerLink]="['contact']">Contacta>li>
      ul>
    div>
  div>
nav>
<div>
  
  <router-outlet>router-outlet>
div>

You can run the app with:

您可以通过以下方式运行该应用程序:

npm start

angular 服务器渲染_带有Angular Universal的Angular 2中的服务器端渲染_第5张图片

Preview

预习

angular 服务器渲染_带有Angular Universal的Angular 2中的服务器端渲染_第6张图片

Source

资源

You can see the difference in the server-served content. It clearly represents the Contact page with the form and button.

您可以看到服务器提供的内容的差异。 它使用表单和按钮清楚地表示“联系人”页面。

远离DOM ( Stay Away From The DOM )

The power of Universal comes from the fact that Angular abstracts DOM rendering. This is what makes Angular support for multi-platform possible. If you want Universal to keep working as expected, the you have to stay away from the DOM.

Universal的功能来自Angular抽象DOM渲染的事实。 这就是使Angular支持多平台成为可能的原因。 如果要让Universal保持预期的工作状态,则必须远离DOM。

This does not mean that you cannot perform DOM operations but do not do that with the native solutions (document.domMethod() or $('dom-element')).

这并不意味着您不能执行DOM操作,但不能使用本机解决方案( document.domMethod()$('dom-element') )来执行。

Angular provides us a better way to perform DOM operations safely. See ElementRef, Renderer and ViewContainer APIs for more details.

Angular为我们提供了更好的安全执行DOM操作的方法。 有关更多详细信息,请参见ElementRef , Renderer和ViewContainer API。

Thank you!

谢谢!

翻译自: https://scotch.io/tutorials/server-side-rendering-in-angular-2-with-angular-universal

angular 服务器渲染

你可能感兴趣的:(angular 服务器渲染_带有Angular Universal的Angular 2中的服务器端渲染)