外部ユニバーサルの配置をロードするユニバーサル


私のポストでLoading external configurations via http using APP_INITIALIZER , 私はクライアント側でHTTPを介して外部の設定をロードしようとしました.このポストでは、私はSSRのためのオプションを調査しています.

Find the final result here StackBlitz


外部リモート構成


拡張StackBlitz Token Test Project , 設定のURLをリモートHTTPに設定し、ローカルにビルドしてサーバーをテストする場合、同じ結果が得られました.The project resolve 予想通り働いた.唯一の問題は:リモートURLの失敗は、アプリケーションのブロックを意味した.これはリモート設定の落とし穴です.これを解決する一つの方法は次のとおりです.

設定にわずかな修正


見分けたいserved しかし、失敗した場合はUIをブロックしたくない.The project resolve たとえば、エラーをどうするかを決める必要があります.
  return this.configService.config$.pipe(
      first((n) => n.isServed),
      map((n) => {
        // if served with error, reroute or notify user, but do not block user
        console.log(n.withError); // let's introduce this property
        return true;
      })
    );
インConfigService 私は成功と失敗の区別をするのを止めますserved . 次に導入することでwithError プロパティは、失敗するとtrueに設定されます.
// after defining withError property in IConfig...
private _createConfig(config: any, withError: boolean): void {
    // cast all keys as are
    const _config = { ...Config, ...(<IConfig>config) };

    // is severd, always
    _config.isServed = true;

    // with error
    _config.withError = withError;

    // set static member
    ConfigService._config = _config;

    // next, always next the subject
    this.config.next(config);
  }

  loadAppConfig(): Observable<boolean> {
    return this.http.get(environment.configUrl).pipe(
      map((response) => {
        // create with no errors
        this._createConfig(response, false);
        return true;
      }),
      catchError((error) => {
        // if in error, return set fall back from environment
        // and create with errors
        this._createConfig(Config, true);
        return of(false);
      })
    );
  }

This works as expected, however, if the HTTP request fails on server, Angular will attempt to reconnect after rehydration, on client.


外部ローカル設定


ファイルの移動localdata フォルダ使用angular.json 資産
"assets": [
  {
    "glob": "*.json",
    "input": "configs",
    "output": "/localdata"
  }
]
設定URLは次のようになりますlocaldata/config.json . 相対です.
によるとAngular Docs :

If you are using one of the @nguniversal/*-engine packages (such as @nguniversal/express-engine), this is taken care for you automatically. You don't need to do anything to make relative URLs work on the server.


まあ、GET localdata/config.prod.json NetworkError私は、彼らが何を意味するかと思います.つまり、
server.get('*', (req, res) => {
  res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});
そして、私はあなたに理由を話します、そして、方法.それから、私は相対的なURLの解決を経験します.

サーバーの分離


我々がドキュメンテーションに従うならばServer-side rendering (SSR) with Angular Universal これは、srcフォルダ内のseverを構築し、ビルドプロセスでは、サーバーを切断するまでを歩く.私は、私のサーバーが私の開発ソースコードにある間、古い学校からあまりに卑屈なことがよく眠ることができないとわかります.サーバー上の何かが間違っている場合、私は構築し、テストする必要がありますか?毎回?涼しくない.
私はすぐにポストするかもしれない一つの良いシナリオは、同じビルドを使用して多言語アングルアプリを提供しています.
まずサイズを小さくしましょうserver.ts 角度の医者によって提案されたngExpressEngine , それをエクスポートし、別のエクスプレスアプリケーションを作成します.
// server.ts
// export the ngExpressEngine
export const AppEngine = ngExpressEngine({
  bootstrap: AppServerModule
});
SSRをビルドするには以下を使用しますangular.json 設定
// ... angular.json
"architect": {
     // ... 
    "server": {
        "builder": "@angular-devkit/build-angular:server",
        "options": {
            // choose the output path where the main.js will end up
            "outputPath": "./host/server", 
            "main": "server.ts",
            "tsConfig": "tsconfig.server.json"
        },
        "configurations": {
            "production": {
                // don't delete because there will be other files
                "deleteOutputPath": false
                // ...
            }
        }
    }
}
The main.js 生成されますoutputPath , そこにサーバーを作りましょうAppEngine .
// host/server.js
const express = require('express');

// express app
var app = express();

// setup express
require('./server/express')(app);

// setup routes
require('./server/routes')(app);

// other stuff is up to you

// listen
var port = process.env.PORT || 1212;
app.listen(port, function (err) {
  console.log('started to listen to port: ' + port);
  if (err) {
      console.log(err);
      return;
  }
});
Expressのモジュールは基本です、あなたはそれを見てみることができますStackBlitz . The routes.js 料理が起こるところ
  • PS : StackBlitzでテストできません__dirname 正確な道をたどる
  • const express = require('express');
    
    // ngExpressEngine from compiled main.js
    const ssr = require('./main');
    
    // setup the routes
    module.exports = function (app) {
      // set engine, we called it AppEngine in server.ts
      app.engine('html', ssr.AppEngine);
    
      // set view engine
      app.set('view engine', 'html');
    
      // set views directory
      app.set('views', '../client');
    
      // expose the configs path as localdata (or whatever you choose to name it)
      app.use('/localdata', express.static('../localdata', { fallthrough: false }));
    
      // expose client folder
      app.use(express.static('../client'));
    
      // now THIS
      app.get('/*', (req, res) => {
        // point to your index.html
        res.render(`../client/index.html`, {
          req, // pass request
          res, // pass response
          // here, we can provide things for ssr
        });
      });
    };
    
    インres.render , 戻ってきたresponse and request ちょうど私は角でそれらを使用したい場合.(珍しいことですが).それで、それは理由です、そして、方法.

    ローカルリクエストの絶対URLを指定する


    ローカルリクエストはlocaldata/config.prod.json . それを修正するには、サーバURLによってprependedしなければなりません.の最終結果ConfigService 次のようになります.
      loadAppConfig(): Observable<boolean> {
        // fix url first if its on server
        let url = environment.configUrl;
        if (serverUrlExsits) {
          url = serverUrl + url;
        }
        return this.http.get(url).pipe(
         // ... etc
        );
      }
    
    サーバー上のURLはREQUEST インジェクショントークンdocumented on NPM packages .
    // change ConfigService
    // for this line to work, install @types/express
    import { Request } from 'express'; 
    import { REQUEST } from '@nguniversal/express-engine/tokens';
    
    @Injectable()
    export class RequestService {
      // make it Optional to work on browser platform as well
      constructor(@Optional() @Inject(REQUEST) private request: Request) {}
    }
     loadAppConfig(): Observable<boolean> {
        // fix url first if its on server
        let url = environment.configUrl;
        if (this.request) {
          // on ssr get a full url of current server
          url = `${this.request.protocol}://${this.request.get('host')}/${url}`;
        }
     // ... etc
      } 
    }
    
    すでに提供されてreqres.render これは十分です.しかし、それは醜く見えます.HTTPインターセプターを作成できますlocaldata 他のローカルデータを使用するには.でもまず

    逆プロキシの奇妙な場合


    このポストの範囲を超えて、無関心なことなく、プロキシサーバと負荷分散を通常のサーバーでリバースhttps into http , and real.host.com into localhost . 使用して固定した後者req.get('host')header . プロトコルを修正するには、別のヘッダ値にアクセスします.x-forwarded-proto .
    ここに、私が設定したAzureウェブサイトの例があります.これは、クラウドホスティングの設定によって、ヘッダーの値がどのようにプレーンなものと異なるかに注目しています.
    https://aumet.azurewebsites.net/webinfo
    {
        "request": {
            "headers": {
                 "host": "aumet.azurewebsites.net",
                "disguised-host": "aumet.azurewebsites.net",
                "x-original-url": "/webinfo",
                "x-forwarded-for": "client-ip-address-here",
                "x-forwarded-proto": "https"
            },
           // on other servers this could be localhost
            "hostname": "aumet.azurewebsites.net",
            "path": "/webinfo",
            // don't read this value
            "protocol": "http",
     }
    }
    
    しかし、私は私の角度のアプリに戻る前に、懸念の分離について強迫観念されて、これは角度の問題ではないので、それはアプリに属してはならない.私はむしろ正しいURLを設定し、それを提供したい.このように:
    // in host/server/routes.js
    // change the final get
      app.get('/*', (req, res) => {
    
        // fix and provide actual url
        let proto = req.protocol;
        if (req.headers && req.headers['x-forwarded-proto']) {
            // use this instead
            proto = req.headers['x-forwarded-proto'].toString();
        }
        // also, always use req.get('host')
        const url = `${proto}://${req.get('host')}`;
    
        res.render(`../client/index.html`, {
          req,
          res,
          // here, provide it
          providers: [
            {
              provide: 'serverUrl',
              useValue: url,
            },
          ],
        });
      });
    
    私たちの角度のアプリに戻るには、適切なHTTPインターセプターを作成するlocaldata 呼び出し
    // Angular inteceptor
    @Injectable()
    export class LocalInterceptor implements HttpInterceptor {
      constructor(
        // inject our serverURL
        @Optional() @Inject('serverUrl') private serverUrl: string
      ) {}
      intercept(req: HttpRequest<any>,next: HttpHandler): Observable<HttpEvent<any>> {
        // if request does not have 'localdata' ignore
        if (req.url.indexOf('localdata') < 0) {
          return next.handle(req);
        }
    
        let url = req.url;
        if (this.serverUrl) {
          // use the serverUrl if it exists
          url = `${this.serverUrl}/${req.url}`;
        }
    
        const adjustedReq = req.clone({ url: url });
        return next.handle(adjustedReq);
      }
    }
    
    HttpIntereptorを提供するAppModule
    // app.module.ts
    providers: [
        {
          provide: APP_INITIALIZER,
          useFactory: configFactory,
          multi: true,
          deps: [ConfigService],
        },
        // provide http interceptor here
        {
          provide: HTTP_INTERCEPTORS,
          useClass: LocalInterceptor,
          multi: true,
        },
      ],
    
    クリーンアップConfigService 任意の参照から我々のサーバーに.建築試験所
    そして、これについてとても良いことは、サーバーを変更することができますかconfig.prod.json サーバーを再起動せずに、他の環境、およびサーバーを汚染する心配しないでください.現在、私はよりよく眠ることができます.

    サーバへの設定の提供


    私たちは別のサーバを持っており、Cofigurationファイルはリモートではありません.ConfigService ?
    // host/server/routes.js
    // require the json file sitting in localdata
    const localConfig = require('../localdata/config.prod.json');
    
    // setup the routes
    module.exports = function (app) {
       // ... 
       res.render(`../client/index.html`, {
          req,
          res,
          // also provide the localConfig
          providers: [
            {
              provide: 'localConfig',
              useValue: localConfig
            }
            // though don't lose the serverUrl, it's quite handy
          ] 
        });
      });
    };
    
    インConfigService
      constructor(
        private http: HttpClient,
        // optional injector for localConfig
        @Optional() @Inject('localConfig') private localConfig: IConfig
      ) {}
    
        loadAppConfig(): Observable<boolean> {
        // if on server, grab config without HTTP call
        if (this.localConfig) {
          this._createConfig(this.localConfig, true);
          return of(true);
        }
    
        return this.http.get(environment.configUrl).pipe(
         // ...
        );
      }
    
    これは、サーバーの設定を取得するための最速のエラー最小の方法です.しかし、それは一部のためのoverkillであるかもしれません.力はあなたと共にありますように.
    私の非常に長いポストのこれまで読んでくれてありがとう.私は間違いをしたに違いない.

    資源

  • Angular Express Engine
  • Angular Docs
  • X-Forwarded-Proto
  • StackBlitz