外部のインラインインデクスをロードし、角度でSSRでロードする


前の記事では、AppCount Initlizerトークンを使用しましたload external configurations via HTTP . 今日、私は設定をより近くに持ってきます.しかし、どのように、我々は角のアプリケーションでJSONをHTMLに注入しますか?
実装は2つのターゲットを満たす必要があります.
  • 設定をコンパイルされたソースに含めることはできません.したがって、直接入力することはできません.
    これはローカルインポートを除外します:import * as WebConfig from '/localdata/config.json';またはモジュールスクリプト<script type="module" src="/localdata/config.js">または動的モジュール読み込み
  • import('./localdata/config.js')  
      .then((config) => {
        // do something with config
      });
    
  • 私たちはタイピングを維持したいconfig 使用する前に使用できません.
  • JSONがセキュリティ上の注意のためHTMLに注入されないので、設定スクリプトを作成します.
    // configs/config.js file, named it "WebConfig" to avoid confusion
    const WebConfig = {
      isServed: true,
      API: {
        apiRoot: 'url/server/app',
      },
      MyKey: 'MyValue',
    };
    

    スクリプトを注入する


    JavaScriptの設定をインポートせずにインポートする唯一の場所は、直接HTMLヘッダにあります.それは設計時にチェックされない唯一の場所です、そして、ランタイムで沈黙404をスローします.
    このようにします.
    <script src="localdata/config.js"></script>
    
    このパスを動作させるには、angular.json assets が必要です.

    I make it a habit to name things differently just to remember that the rule exists.


    { //... angular.json
    "assets": [
      {
        "glob": "*",
        "input": "configs",
        "output": "/localdata"
      }
    ]
    

    AppRank初期化子の実装


    をつくりましょうAPP_INITIALIZER 最低限の応答void . ここではConfigService
    
    // declare WebConfig
    declare const WebConfig: any;
    
    export const configFactory = (config: ConfigService): (() => void) => {
        return () => config.loadAppConfig();
    };
    
    @Injectable({
      providedIn: 'root',
    })
    export class ConfigService {
      constructor() {}
    
     // set a static member for easier handling
     private static _config: IConfig;
    
     static get Config(): IConfig {
        return this._config || Config;
      }
    
      private _createConfig(config: any): IConfig {
        // cast all keys as are, extend local Config
        const _config = { ...Config, ...(<IConfig>config) };
        // set static member
        ConfigService._config = _config;
        return _config;
      }
    
      loadAppConfig(): void {
        // here is the JavaScript variable... is it ready?
        if (WebConfig?.isServed) {
          this._createConfig(WebConfig);
        } else {
          // not loaded? fall back
          console.log('error');
          this._createConfig(Config);
        }
      }
    }
    

    課題


    FIXへの最初の問題はWebConfig , 同じサービスファイルにconstを宣言するdeclare const WebConfig: any;もう一つの問題は、遅い構成の極端な場合です.スクリプトがdefer プロパティをブロックしてはならず、localdata 同じサーバーから提供* *それは十分に速くする必要があります*.しかし、Stackblitzでは、それはあまりに遅いです.私はそのトラックを下っていません、なぜなら、「リモートコンフィグをローカルにロードするのを待つ」ことに注意しなければならないなら、私たちはHTTPメソッドでより良いです.
    しかし、極端な場合は以下のように局所的に生成されます.
  • リモートサーバから設定をロードする
  • 追加async 属性
  • そして、おそらくスクリプトの前に
  • <script src="https://saphire.sekrab.com/localdata/config.js" async></script>…The WebConfig 最初に値がないので、「未定義」エラーをスローします.それを修正するには、index.html または任意のJavaScriptでコードに追加しました.
    <script>
      window.WebConfig = {
        isServed: false,
      };
    </script>
    

    AppRangeブートストラップリスナーの実装


    このリスナーの主な問題は、ルータが解決した後に発火するということです.

    プラットフォーム初期化の実装


    トークンの返却が重要でないので、プラットフォーム初期化装置では、以前にロードすることができます.あなたは注意しなければならないがdefer そして、ローカル滞在.( ps . Stackblitzではこのメソッドを使用できません)
    export const platformFactory = (): (() => void)  => {
        ConfigService.loadAppConfig(); // static element
        return () => null;
    };
    
    インmain.ts
     platformBrowserDynamic([
        {
              provide: PLATFORM_INITIALIZER,
              useFactory: platformFactory,
              multi: true,
         }
     ]).bootstrapModule(AppBrowserModule)
    
    このトークンは依存性を使わないので、ConfigService 静的要素のグループであるので、どこにでもそれを提供する必要はありません.書き直してテストしましょう.
    // notice it no longer needs to be injected
    export class ConfigService {
        private static _config: IConfig;
    
        static get Config(): IConfig {
            return this._config || Config;
        }
    
       private static _createConfig(config: any): IConfig {
        // cast all keys as are
        const _config = { ...Config, ...(<IConfig>config) };
        // set static member
        ConfigService._config = _config;
        return _config;
       }
      static loadAppConfig(): void {
         if (WebConfig?.isServed) {
            this._createConfig(WebConfig);
          } else {
           // error
            this._createConfig(Config);
          }
       }
    }
    
    ちょうどローカルにしましょう.<script src="localdata/config.js" defer></script>静的要素をどこでも参照するのと同じくらい簡単です.ConfigService.Config.isServed以来、ルータの解決もテストに耐えるdefer 属性はパース後にJavaScriptを読み込みますDOMContentLoaded . クライアント側では、すべてが動作します.今SSRに.

    SSR


    我々が使うならばAPP_INITIALIZER (静的メソッドを使用すると、トークンはまだAppModule , どちらのプラットフォームでも共有できます.我々が使うならばPLATFORM_INITIALIZER , に注入されましたplatformBrowserDynamic ブラウザプラットフォームのみを実行します.SSRのために、サーバープラットホームで注入される必要があります.
    インserver.ts , ブートストラップAppServerModule オプションとして発生するngExpressEngine , これには別のオプションがあります.providers array , これはトークンが提供されている場所です.
    // in server.ts, or where you create the ngExpressEngine
    export const AppEngine = ngExpressEngine({
        bootstrap: AppServerModule,
        // pass provider here
        providers:[
            {
                provide: PLATFORM_INITIALIZER,
                useFactory: platformFactory,
                multi: true,
            }
        ]
    });
    
    それは十分ではない.現在WebConfig サーバ側では未定義です.
    ビルド後のサーバー出力フォルダwhere the express app is defined , the WebConfig 変数はグローバルコンテキストで設定する必要があります.インNodeJs (全部使っているわけじゃないかな?と同じくらい簡単ですglobal.WebConfig global.WebConfig = require('./localdata/config.js');The localdata この場合、サーバーフォルダがあります.jsファイル.
    しかし、設定を待ちます.jsファイルにexports 動作する行のステートメント.また、それはexports ステートメントを水和後ブラウザで実行する!
    解決策つのプラットホームでNULLであるプロパティをチェックします.最も簡単なプロパティはwindow . ( 1つを作成することができますが、コードの5倍のコードを受け取ります.
    まず、あなたのエクスプレスサーバファイルでglobal.window = undefined .
    次に、ホスト設定ファイル( server/localdata/config . js )にあります.
    // in config.js add the following lines 
    if (!window) {
        module.exports = WebConfig;
    }
    
    がある.今、configファイルはブラウザとサーバープラットフォームで動作します.

    挑戦


  • HTMLでなければなりませんconfig.js からconfig.prod.js あなたは最終的になりますindex.dev.html and index.html 生産のために.

  • JSONファイルではなく、constを持つjsです.

  • それはローカルでなければなりません、リモートはあまりに遅いです、そして、継ぎ目の側で働きません.
  • *SSRには余分な荷物が必要です
  • StackBlitz project does not run Angular universal, nevertheless, the source code includes SSR


    戻る、HTTPへ


    私はHTTPメソッドが好き!SSRで利用することができますが、1つのトリックは、SSRのためのHTMLエンジンのレンダリングオプションのJSONを提供することができます.それは何が必要ですか?何を修正する必要がありますか?来週お話しましょう.より良い制御のための角から完全にサーバーコードを分離する方法とともに.

    資源

  • Angular Express Engine
  • PLATFORM_INITIALIZER
  • StackBlitz App
  • Defer attribute on MDN