亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

深入淺析Angular SSR

 更新時間:2022年11月06日 11:13:05   作者:世開Coding  
Angular Universal 主要關(guān)注將 Angular App 如何進行服務(wù)端渲染和生成靜態(tài) HTML,對于用戶交互復雜的 SPA 并不推薦使用 SSR,本文是在 Angular 14 環(huán)境中完成,有些內(nèi)容對于新的 Angular 版本可能并不適用,感興趣的朋友一起通過本文學習

你知道 Angular Universal 嗎?可以幫助網(wǎng)站提供更好的 SEO 支持哦!

一般來說,普通的 Angular 應用是在 瀏覽器 中運行,在 DOM 中對頁面進行渲染,并與用戶進行交互。而 Angular Universal 是在 服務(wù)端 進行渲染(Server-Side Rendering,SSR),生成靜態(tài)的應用程序網(wǎng)頁,然后在客戶端展示,好處是可以更快地進行渲染,在提供完整的交互之前就可以為用戶提供內(nèi)容展示。

本文是在 Angular 14 環(huán)境中完成,有些內(nèi)容對于新的 Angular 版本可能并不適用,請參考 Angular 官方文檔。

使用 SSR 的好處

對 SEO 更加友好

雖然現(xiàn)在包括 Google 在內(nèi)的某些搜索引擎和社交媒體聲稱已經(jīng)能支持對由 JavaScript(JS)驅(qū)動的 SPA(Single-Page Application)應用進行爬取,但是結(jié)果似乎差強人意。靜態(tài) HTML 網(wǎng)站的 SEO 表現(xiàn)還是要好于動態(tài)網(wǎng)站,這也是 Angular 官網(wǎng)所持有的觀點(Angular 可是 Google 的?。?。

Universal 可以生成無 JS 的靜態(tài)版本的應用程序,對搜索、外鏈、導航的支持更好。

提高移動端的性能

某些移動端設(shè)備可能不支持 JS 或者對 JS 的支持非常有限,導致網(wǎng)站的訪問體驗非常差。這種情況下,我們需要提供無 JS 版本的應用,以便為用戶提供更好的體驗。

更快地展示首頁

對于用戶的使用體驗來說,首頁展示速度的快慢至關(guān)重要。根據(jù) eBay 的數(shù)據(jù),搜索結(jié)果的展示速度每提高 100 毫秒,“添加至購物車”的使用率就提高 0.5%。

使用了 Universal 之后,應用程序的首頁會以完整的形態(tài)展示給用戶,這是純的 HTML 網(wǎng)頁,即使不支持 JS,也可以展示。此時,網(wǎng)頁雖然不能處理瀏覽器的事件,但是支持通過 routerLink 進行跳轉(zhuǎn)。

這么做的好處是,我們可以先用靜態(tài)網(wǎng)頁抓住用戶的注意力,在用戶瀏覽網(wǎng)頁的時候,同時加載整個 Angular 應用。這給了用戶一個非常好的極速加載的體驗。

為項目增加 SSR

Angular CLI 可以幫助我們非常便捷的將一個普通的 Angular 項目轉(zhuǎn)變?yōu)橐粋€帶有 SSR 的項目。創(chuàng)建服務(wù)端應用只需要一個命令:

ng add @nguniversal/express-engine

建議在運行該命令之前先提交所有的改動。

這個命令會對項目做如下修改:

  • 添加服務(wù)端文件:

    • main.server.ts - 服務(wù)端主程序文件
    • app/app.server.module.ts - 服務(wù)端應用程序主模塊
    • tsconfig.server.json - TypeScript 服務(wù)端配置文件
    • server.ts - Express web server 的運行文件
  • 修改的文件:

    • package.json - 添加 SSR 所需要的依賴和運行腳本
    • angular.json - 添加開發(fā)、構(gòu)建 SSR 應用所需要的配置

替換瀏覽器 API

由于 Universal 應用不是在瀏覽器中執(zhí)行,因此一些瀏覽器的 API 或功能將不可用。例如,服務(wù)端應用是無法使用瀏覽器中的全局對象 window、document,navigatorlocation。

Angular 提供了兩個可注入對象,用于在服務(wù)端替換對等的對象:LocationDOCUMENT。

例如,在瀏覽器中,我們通過 window.location.href 獲取當前瀏覽器的地址,而改成 SSR 之后,代碼如下:

import { Location } from '@angular/common';

export class AbmNavbarComponent implements OnInit{
  // ctor 中注入 Location
  constructor(private _location:Location){
    //...
  }

  ngOnInit() {
    // 打印當前地址
    console.log(this._location.path(true));
  }
}

同樣,對于在瀏覽器使用 document.getElementById() 獲取 DOM 元素,在改成 SSR 之后,代碼如下:

import { DOCUMENT } from '@angular/common';

export class AbmFoxComponent implements OnInit{
  // ctor 中注入 DOCUMENT
  constructor(@Inject(DOCUMENT) private _document: Document) { }

  ngOnInit() {
    // 獲取 id 為 fox-container 的 DOM
    const container = this._document.getElementById('fox-container');
  }
}

使用 URL 絕對地址

在 Angular SSR 應用中,HTTP 請求的 URL 地址必須為 絕對地址(即,以 http/https 開頭的地址,不能是相對地址,如 /api/heros)。Angular 官方推薦將請求的 URL 全路徑設(shè)置到 renderModule()renderModuleFactory()options 參數(shù)中。但是在 v14 自動生成的代碼中,并沒有顯式調(diào)用這兩個方法的代碼。而通過讀 Http 請求的攔截,也可以達到同樣的效果。

下面我們先準備一個攔截器,假設(shè)文件位于項目的 shared/universal-relative.interceptor.ts 路徑:

import { HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Inject, Injectable, Optional } from '@angular/core';
import { REQUEST } from '@nguniversal/express-engine/tokens';
import { Request } from 'express';

// 忽略大小寫檢查
const startsWithAny = (arr: string[] = []) => (value = '') => {
    return arr.some(test => value.toLowerCase().startsWith(test.toLowerCase()));
};

// http, https, 相對協(xié)議地址
const isAbsoluteURL = startsWithAny(['http', '//']);

@Injectable()
export class UniversalRelativeInterceptor implements HttpInterceptor {
    constructor(@Optional() @Inject(REQUEST) protected request: Request) { }

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        // 不是絕對地址的 URL
        if (!isAbsoluteURL(req.url)) {
            let protocolHost: string;
            if (this.request) {
                // 如果注入的 REQUEST 不為空,則從注入的 SSR REQUEST 中獲取協(xié)議和地址
                protocolHost = `${this.request.protocol}://${this.request.get(
                    'host'
                )}`;
            } else {
                // 如果注入的 REQUEST 為空,比如在進行 prerender build:
                // 這里需要添加自定義的地址前綴,比如我們的請求都是從 abmcode.com 來。
                protocolHost = 'https://www.abmcode.com';
            }
            const pathSeparator = !req.url.startsWith('/') ? '/' : '';
            const url = protocolHost + pathSeparator + req.url;
            const serverRequest = req.clone({ url });
            return next.handle(serverRequest);

        } else {
            return next.handle(req);
        }
    }
}
import { HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Inject, Injectable, Optional } from '@angular/core';
import { REQUEST } from '@nguniversal/express-engine/tokens';
import { Request } from 'express';

// 忽略大小寫檢查
const startsWithAny = (arr: string[] = []) => (value = '') => {
    return arr.some(test => value.toLowerCase().startsWith(test.toLowerCase()));
};

// http, https, 相對協(xié)議地址
const isAbsoluteURL = startsWithAny(['http', '//']);

@Injectable()
export class UniversalRelativeInterceptor implements HttpInterceptor {
    constructor(@Optional() @Inject(REQUEST) protected request: Request) { }

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        // 不是絕對地址的 URL
        if (!isAbsoluteURL(req.url)) {
            let protocolHost: string;
            if (this.request) {
                // 如果注入的 REQUEST 不為空,則從注入的 SSR REQUEST 中獲取協(xié)議和地址
                protocolHost = `${this.request.protocol}://${this.request.get(
                    'host'
                )}`;
            } else {
                // 如果注入的 REQUEST 為空,比如在進行 prerender build:
                // 這里需要添加自定義的地址前綴,比如我們的請求都是從 abmcode.com 來。
                protocolHost = 'https://www.abmcode.com';
            }
            const pathSeparator = !req.url.startsWith('/') ? '/' : '';
            const url = protocolHost + pathSeparator + req.url;
            const serverRequest = req.clone({ url });
            return next.handle(serverRequest);

        } else {
            return next.handle(req);
        }
    }
}

然后在 app.server.module.ts 文件中 provide 出來:

import { UniversalRelativeInterceptor } from './shared/universal-relative.interceptor';
// ... 其他 imports

@NgModule({
  imports: [
    AppModule,
    ServerModule,
    // 如果你用了 @angular/flext-layout,這里也需要引入服務(wù)端模塊
    FlexLayoutServerModule, 
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: UniversalRelativeInterceptor,
      multi: true
    }
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule { }

這樣任何對于相對地址的請求都會自動轉(zhuǎn)換為絕對地址請求,在 SSR 的場景下不會再出問題。

Prerender 預渲染靜態(tài) HTML

經(jīng)過上面的步驟后,如果我們通過 npm run build:ssr 構(gòu)建項目,你會發(fā)現(xiàn)在 dist/<your project>/browser 下面只有 index.html 文件,打開文件查看,發(fā)現(xiàn)其中還有 <app-root></app-root> 這樣的元素,也就是說你的網(wǎng)頁內(nèi)容并沒有在 html 中生成。這是因為 Angular 使用了動態(tài)路由,比如 /product/:id 這種路由,而頁面的渲染結(jié)果要經(jīng)過 JS 的執(zhí)行才能知道,因此,Angular 使用了 Express 作為 Web 服務(wù)器,能在服務(wù)端運行時根據(jù)用戶請求(爬蟲請求)使用模板引擎生成靜態(tài) HTML 界面。

prerendernpm run prerender)會在構(gòu)建時生成靜態(tài) HTML 文件。比如我們做企業(yè)官網(wǎng),只有幾個頁面,那么我們可以使用預渲染技術(shù)生成這幾個頁面的靜態(tài) HTML 文件,避免在運行時動態(tài)生成,從而進一步提升網(wǎng)頁的訪問速度和用戶體驗。

預渲染路徑配置

需要進行預渲染(預編譯 HTML)的網(wǎng)頁路徑,可以有幾種方式進行提供:

1.通過命令行的附加參數(shù):

ng run <app-name>:prerender --routes /product/1 /product/2

2.如果路徑比較多,比如針對 product/:id 這種動態(tài)路徑,則可以使用一個路徑文件:

routes.txt

/products/1
/products/23
/products/145
/products/555

然后在命令行參數(shù)指定該文件:

ng run <app-name>:prerender --routes-file routes.txt

3.在項目的 angular.json 文件配置需要的路徑:

 "prerender": {
   "builder": "@nguniversal/builders:prerender",
   "options": {
     "routes": [ // 這里配置
       "/",
       "/main/home",
       "/main/service",
       "/main/team",
       "/main/contact"
     ]
   },

配置完成后,重新執(zhí)行預渲染命令(npm run prerender 或者使用命令行參數(shù)則按照上面<1><2>中的命令執(zhí)行),編譯完成后,再打開 dist/<your project>/browser 下的 index.html 會發(fā)現(xiàn)里面沒有 <app-root></app-root> 了,取而代之的是主頁的實際內(nèi)容。同時也生成了相應的路徑目錄以及各個目錄下的 index.html 子頁面文件。

SEO 優(yōu)化

SEO 的關(guān)鍵在于對網(wǎng)頁 title,keywordsdescription 的收錄,因此對于我們想要讓搜索引擎收錄的網(wǎng)頁,可以修改代碼提供這些內(nèi)容。

在 Angular 14 中,如果路由界面通過 Routes 配置,可以將網(wǎng)頁的靜態(tài) title 直接寫在路由的配置中:

{ path: 'home', component: AbmHomeComponent, title: '<你想顯示在瀏覽器 tab 上的標題>' },

另外,Angular 也提供了可注入的 TitleMeta 用于修改網(wǎng)頁的標題和 meta 信息:

import { Meta, Title } from '@angular/platform-browser';

export class AbmHomeComponent implements OnInit {

  constructor(
    private _title: Title,
    private _meta: Meta,
  ) { }

  ngOnInit() {
    this._title.setTitle('<此頁的標題>');
    this._meta.addTags([
      { name: 'keywords', content: '<此頁的 keywords,以英文逗號隔開>' },
      { name: 'description', content: '<此頁的描述>' }
    ]);
  }
}

總結(jié)

Angular 作為 SPA 企業(yè)級開發(fā)框架,在模塊化、團隊合作開發(fā)方面有自己獨到的優(yōu)勢。在進化到 v14 這個版本中提供了不依賴 NgModule 的獨立 Component 功能,進一步簡化了模塊化的架構(gòu)。

Angular Universal 主要關(guān)注將 Angular App 如何進行服務(wù)端渲染和生成靜態(tài) HTML,對于用戶交互復雜的 SPA 并不推薦使用 SSR。針對頁面數(shù)量較少、又有 SEO 需求的網(wǎng)站或系統(tǒng),則可以考慮使用 Universal 和 SSR 技術(shù)。

相關(guān)文章

  • AngularJS中run方法的巧妙運用

    AngularJS中run方法的巧妙運用

    前端技術(shù)的發(fā)展是如此之快,各種優(yōu)秀技術(shù)、優(yōu)秀框架的出現(xiàn)簡直讓人目不暇接,緊跟時代潮流,學習掌握新知識自然是不敢怠慢。下面這篇文章主要給大家介紹了AngularJS中run方法的巧妙運用,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-01-01
  • 深入理解AngularJS中的ng-bind-html指令和$sce服務(wù)

    深入理解AngularJS中的ng-bind-html指令和$sce服務(wù)

    這篇文章給大家詳細介紹了AngularJS中的ng-bind-html指令和$sce服務(wù),對大家學習AngularJS具有一定參考借鑒價值,有需要都可以參考學習。
    2016-09-09
  • Angular中點擊li標簽實現(xiàn)更改顏色的核心代碼

    Angular中點擊li標簽實現(xiàn)更改顏色的核心代碼

    這篇文章主要介紹了Angular中點擊li標簽實現(xiàn)更改顏色的核心代碼,需要的朋友可以參考下
    2017-12-12
  • 詳解基于Angular4+ server render(服務(wù)端渲染)開發(fā)教程

    詳解基于Angular4+ server render(服務(wù)端渲染)開發(fā)教程

    本篇文章主要介紹了詳解基于Angular4+ server render(服務(wù)端渲染)開發(fā)教程 ,具有一定的參考價值,有興趣的可以了解一下
    2017-08-08
  • Angular.JS中的this指向詳解

    Angular.JS中的this指向詳解

    這篇文章主要給大家介紹了關(guān)于Angular.JS中this指向的相關(guān)資料,文中介紹的非常詳細,對大家具有一定的參考學習價值,需要的朋友們下面來一起看看吧。
    2017-05-05
  • Angular+ionic實現(xiàn)折疊展開效果的示例代碼

    Angular+ionic實現(xiàn)折疊展開效果的示例代碼

    這篇文章主要介紹了Angular+ionic實現(xiàn)折疊展開效果,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-07-07
  • Angular 2父子組件之間共享服務(wù)通信的實現(xiàn)

    Angular 2父子組件之間共享服務(wù)通信的實現(xiàn)

    這篇文章主要給大家介紹了關(guān)于Angular 2父子組件之間共享服務(wù)通信的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家具有一定的參考學習價值,需要的朋友們下面來一起看看吧。
    2017-07-07
  • AngularJS 讓人愛不釋手的八種功能

    AngularJS 讓人愛不釋手的八種功能

    AngularJS 讓人愛不釋手的八種功能,想知道AngularJS哪八種功能讓人喜歡就快點看下本文吧
    2016-03-03
  • AngularJS實踐之使用ng-repeat中$index的注意點

    AngularJS實踐之使用ng-repeat中$index的注意點

    最近通過客戶的投訴主要到在ng-repeat中使用了$index引發(fā)的一個bug,下面一起來看看這個錯誤是如何引發(fā)的, 以及如何避免這種bug產(chǎn)生,然后說說我們從中得到的經(jīng)驗和教訓。有需要的朋友們可以參考借鑒,下面來一起看看吧。
    2016-12-12
  • AngularJS的ng-repeat指令與scope繼承關(guān)系實例詳解

    AngularJS的ng-repeat指令與scope繼承關(guān)系實例詳解

    這篇文章主要介紹了AngularJS的ng-repeat指令與scope繼承關(guān)系,結(jié)合實例形式通過ng-repeat指令詳細分析了scope繼承關(guān)系,需要的朋友可以參考下
    2017-01-01

最新評論