As part of getting my set of Identity Server 4 sample applications to run in Azure, I needed a way in the Client Application to pass some configuration values from appsettings.json to the Angular front end that could be used both during server-side rendering and client-side rendering. This application is using JavaScriptServices. This solution may need tweaking if your application isn’t using JavaScriptServices. The code for the client application can be found here.
Settings
In this example, we need to pass the address of our Identity Server and API from appsettings.json to Angular. The following is the settings file for this example.
{ "Logging": { "IncludeScopes": false, "Debug": { "LogLevel": { "Default": "Warning" } }, "Console": { "LogLevel": { "Default": "Warning" } } }, "IdentityServerAddress": "http://localhost:5000", "ApiAddress": "http://localhost:5001/" }
Providing Configuration Data to Angular
In this application, Angular is loaded from the index action of the home controller. This view can be found in the Views/Home folder in the Index.cshtml file. The following is the file before any changes.
@{ ViewData["Title"] = "Home Page"; } <app asp-prerender-module="ClientApp/dist/main-server">Loading...</app> <script src="~/dist/vendor.js" asp-append-version="true"></script> @section scripts { <script src="~/dist/main-client.js" asp-append-version="true"></script> }
The first change needed is to inject the configuration data using ASP.NET Core’s DI system. Add the following two lines at the top of the file.
@using Microsoft.Extensions.Configuration @inject IConfiguration Configuration
Now the configuration data from the application is available to this view. Next, we need to pull a couple of values out of the configuration data and pass it to the Angular application. To do this we are going to use the asp-prerender-data tag helper. You can read more about it in the official docs. The idea is you construct an object which is then serialized and stored in params.data. In our example, we are passing the URLs for the Identity and API Applications.
<app asp-prerender-module="ClientApp/dist/main-server" asp-prerender-data='new { apiUrl = Configuration["ApiAddress"], identityUrl = Configuration["IdentityServerAddress"] }'>Loading...</app>
The above is creating a new object with an apiUrl property and an identityUrl property. The following is the full completed view for reference.
@using Microsoft.Extensions.Configuration @inject IConfiguration Configuration @{ ViewData["Title"] = "Home Page"; } <app asp-prerender-module="ClientApp/dist/main-server" asp-prerender-data='new { apiUrl = Configuration["ApiAddress"], identityUrl = Configuration["IdentityServerAddress"] }'>Loading...</app> <script src="~/dist/vendor.js" asp-append-version="true"></script> @section scripts { <script src="~/dist/main-client.js" asp-append-version="true"></script> }
Angular Server-Side Boot
When Angular gets prerendered on the server-side it runs the code in the boot.server.ts file. This is where we will set up the providers needed on for the server side prerender. This is the bit that I missed for the longest time when trying to get this example going. I kept trying to find a way to add the providers in the app.module.server.ts file. Add any providers you need to the providers constant. For example, the following is passing URLs for an API and Identity Server in addition to the defaults provided by JavaScriptServices.
const providers = [ { provide: INITIAL_CONFIG, useValue: { document: '<app></app>', url: params.url } }, { provide: APP_BASE_HREF, useValue: params.baseUrl }, { provide: 'BASE_URL', useValue: params.origin + params.baseUrl } { provide: 'API_URL', useValue: params.data.apiUrl }, { provide: 'IDENTITY_URL', useValue: params.data.identityUrl } ];
Lower in the same file we can pass through the configuration values to the client side render as globals on the window object. To do this add a globals property to the object being passed to the resolve call.
return new Promise<RenderResult>((resolve, reject) => { zone.onError.subscribe((errorInfo: any) => reject(errorInfo)); appRef.isStable.first(isStable => isStable).subscribe(() => { // Because 'onStable' fires before 'onError', we have to delay slightly before // completing the request in case there's an error to report setImmediate(() => { resolve({ html: state.renderToString(), globals: {url_Config: params.data} }); moduleRef.destroy(); }); }); });
The above will have the URLs as part of a single object, but you could have each URL as its own property if you prefer.
Angular Client-Side
Now that the server-side has providers for API URL and Identity URL we need to provide the client-side with the same capabilities. These changes will be in the app.module.browser.ts file. The first step is to add providers for each.
providers: [ { provide: 'ORIGIN_URL', useFactory: getBaseUrl }, { provide: 'API_URL', useFactory: apiUrlFactory }, { provide: 'IDENTITY_URL', useFactory: identityUrlFactory }, AppModuleShared ]
Next, we need functions to return the URLs from the url_Config property of the window object which the following two functions do.
export function apiUrlFactory() { return (window as any).url_Config.apiUrl; } export function identityUrlFactory() { return (window as any).url_Config.identityUrl; }
Wrapping Up
With the above, you can now use your configuration values from ASP.NET Core and pass them through to your Angular application. In hindsight, the process is pretty simple, but getting to that point took me much longer to figure out than I would like to admit. I hope this post saves you some time!
Also published on Medium.
This is great information. I would love to see your working example. Do you have a git repo for the solution?
Glad it was helpful! I do have a working example that can be found here which is part of my Identity Server 4 example repo. The link leads to the client application, but the repo also contains an Identity Server project and an API project.
hi Eric, you’re a life saver ❤
Glad it was helpful Arji!
Oh my god, thank you!
I have been trying to solve this problem for at least a whole day without luck… this should be included in the official docs i think, there is not much info on how to pass settings from serverside to angular apps..
You saved my upcomming months man :)
Glad to hear it, Tody!
Hello!
Before all, thank you very much! i have a problem with your solution. I did all the tutoral but I have the following error:
TypeError: Cannot read property ‘clientID’ of undefined
And this is my code:
export function clientIDFactory() {
return (window as any).url_Config.clientID;
}
I dont know why (windows).url_Config is not define. I did it:
setImmediate(() => {
resolve({
html: state.renderToString(),
globals: { url_Config: params.data }
});
moduleRef.destroy();
});
I waiting for you response!
Thank you ?
It sounds like you are using trying to use the window object as part of a server-side render. Window is only valid on the client side.
Yes, you are right! But I put this code
export function clientIDFactory() {
return (window as any).url_Config.clientID;
}
in app.browser.module.ts! That file is for client side not?
I saw your project example and my project have the same code! I dont know what is happening :(
Yes, that file should be on the client side. Have you cloned my repo to see if you can get it to work? If it does work then it will be a matter of comparing the two until you find the differences between the two.
Excelent! I will do that! Thanks you very much :)
Thank you for your time and effort, Eric.
This tutorial was very helpful for our ASP.NET Core 2.0 Azure project.
Best-
Glad to hear it, Sergei.
Pingback: Pass parameters to Angular7 app without SystemJS - TCup