简体   繁体   中英

Passing Data from ASP.NET Core to Typescript in Angular Cli-based App

I am trying to setup a SPA where I can pass data from my appsettings.json to my clientside on server render. I followed the steps to configure SSR using the new SpaServices Templates

https://docs.microsoft.com/en-us/aspnet/core/spa/angular

However I am not understanding how to accomplish the task labeled here

You can pass this to other parts of your app in any way supported by Angular

I see the example that is used is base_url, but it seems that base is injected into the page as a DOM element

 <base href='/'>

But it is not clear how to read other items in this manner. I am testing passing whether the app is Https or not, I have this section in my Startup.cs

options.SupplyData = (context, data) =>
{
    data["isHttpsRequest"] = context.Request.IsHttps;
};

and in main.server.ts

 { provide: 'isHttps', useValue: params.data.isHttpsRequest }

but it is in main.ts that I get lost, I have something like this

export function getBaseUrl() {
  return document.getElementsByTagName('base')[0].href;
}

export function getIsHttps() {
  // NO idea what needs to go here
  return "";
}

const providers = [
  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] },
  { provide: 'isHttps', useFactory: getIsHttps, deps: [] }
];

I am not sure how SpaServices injects the value on Prerender into the app (I looked through the code but it isn't clear to me). Is the value put on window? How do I read the value so I can inject into constructors on components?

So I dove into this and tried to make it work, but unfortunately it's not meant to work like you or I thought it should work. "You can pass this to other parts of your app in any way supported by Angular" means it needs to be accessible to client side code either by using the global namespace or some other means (which I can't think of). When the app is rendered through the main.server.ts, you can embed variables from the server, but that doesn't mean they are available to the client app automatically...kind of a half baked solution in my opinion, and you and I are not the only ones confused by this (see https://github.com/aspnet/JavaScriptServices/issues/1444 ).

I recommend you look for other methods of including server side data, like a config service on your application bootstrap that gets the config on startup or embedding a JSON object in the Angular hosting page that is written by the server side code.

Found an answer. You can use Angular's offical TransferState . You'll need to modify the following files

app.server.module.ts (Add ServerTransferStateModule)

 import { NgModule } from '@angular/core'; import { ServerModule, ServerTransferStateModule } from '@angular/platform-server'; import { AppModule } from './app.module'; import { AppComponent } from './app.component'; @NgModule({ imports: [ AppModule, ServerModule, ServerTransferStateModule ], bootstrap: [AppComponent], }) export class AppServerModule { } 

app.module.ts (Add BrowserTransferStateModule)

 import { BrowserModule, BrowserTransferStateModule } from '@angular/platform-browser'; import { HttpClientModule } from '@angular/common/http'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule.withServerTransition({ appId: 'universal-demo-v5' }), HttpClientModule, BrowserTransferStateModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } 

This adds the modules needed to use TransferState.

You'll then need to provide the value sent by the dotnet Server on main.server.ts and add a dummy provider on main.ts so as to not raise a conflict.

main.server.ts

 import "zone.js/dist/zone-node"; import "reflect-metadata"; import { renderModule, renderModuleFactory } from "@angular/platform-server"; import { APP_BASE_HREF } from "@angular/common"; import { enableProdMode } from "@angular/core"; import { provideModuleMap } from "@nguniversal/module-map-ngfactory-loader"; import { createServerRenderer } from "aspnet-prerendering"; export { AppServerModule } from "./app/app.server.module"; enableProdMode(); export default createServerRenderer(params => { const { AppServerModule, AppServerModuleNgFactory, LAZY_MODULE_MAP } = (module as any).exports; const options = { document: params.data.originalHtml, url: params.url, extraProviders: [ provideModuleMap(LAZY_MODULE_MAP), { provide: APP_BASE_HREF, useValue: params.baseUrl }, { provide: "BASE_URL", useValue: params.origin + params.baseUrl}, //Added provider, ServerParams is the data sent on Startup.cs { provide: "SERVER_PARAMS", useValue: params.data.ServerParams } ] }; const renderPromise = AppServerModuleNgFactory ? /* AoT */ renderModuleFactory(AppServerModuleNgFactory, options) : /* dev */ renderModule(AppServerModule, options); return renderPromise.then(html => ({ html })); }); 

main.ts (Add dummy provider and bootstrap app only when DOM finished loading. This will give us the time to fully save object on TransferState)

 import { enableProdMode } from "@angular/core"; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; import { AppModule } from "./app/app.module"; import { environment } from "./environments/environment"; export function getBaseUrl() { return document.getElementsByTagName("base")[0].href; } const providers = [ { provide: "BASE_URL", useFactory: getBaseUrl, deps: [] }, { provide: "SERVER_PARAMS", useValue: "" } ]; if (environment.production) { enableProdMode(); } document.addEventListener("DOMContentLoaded", () => { platformBrowserDynamic(providers) .bootstrapModule(AppModule) .catch(err => console.log(err)); }); 

Finally, we save the server provided object on app.component.ts on the TransferState

app.component.ts

 import { Component, Inject } from "@angular/core"; import { TransferState, makeStateKey } from "@angular/platform-browser"; const SERVER_PARAMS = makeStateKey("serverParams"); @Component({ selector: "app-root", templateUrl: "./app.component.html", styleUrls: ["./app.component.css"] }) export class AppComponent { serverParams: any; // Provider set on main.ts and main.server.ts constructor(@Inject("SERVER_PARAMS") private stateInj: string, private state: TransferState) { this.serverParams = this.state.get(SERVER_PARAMS, null as any); if (!this.serverParams) { this.state.set(SERVER_PARAMS, stateInj as any); } } } 

THATS IT

Now you can use Transfer state on any component to get the data as so

home.component.ts

 import { Component, Inject } from "@angular/core"; import { TransferState, makeStateKey } from "@angular/platform-browser"; const SERVER_PARAMS = makeStateKey("serverParams"); @Component({ selector: "app-home", templateUrl: "./home.component.html" }) export class HomeComponent { serverParameter = "PLACEHOLDER"; constructor(public state: TransferState) {} ngOnInit() { this.serverParameter = this.state.get(SERVER_PARAMS, ""); } } 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM