Saturday, 19 December 2020

Running Angular on a fixed port on Windows platform

This article is for Angular developers running on a Windows platform. We will use Windows domain tools such as netstat and Powershell in this article. A cross platform version of Powershell exist. I have not tested this approach on other OS-es, such as Linux. Linux developers using Angular (if any) might follow my approach here with success, it is is possible to do this in a similar manner on *nix systems. Also note that this article is meant for those using Angular SPA with .Net Core. This is the standard setup for Windows platform and Angular development. When developing an Angular app locally, sometimes you want to run on a fixed port. For example, your app might federate its access towards ADFS (Active Directory Federated Services) and you desire to have a fixed port. This makes it possible to set up a callback url that can be fixed and not have the standard setup with a random port that Angular and webpack sets up for you. Here is how I managed to achieve to override this and set up a fixed port. First off, we need to create a short Powershell script with a function to stop (kill) the processes running at a given port with the following contents:

param ( [Parameter(Mandatory=$true)][string] $portToFind ) # This PS script requires the paramter $portToFind to be passed into it pwd Write-Host Probing for Angular App running at $portToFind $runningEudAppProcessLocal = 'netstat -ano | findstr "$portToFind"' $arr = Invoke-Expression $runningEudAppProcessLocal # $runningEudAppProcessLocal $arr = $arr -split '\s+' Write-Host Probing complete: $arr if ($arr.Length -ge 5) { $runningAngularAppPort = $arr[5] $runningAngularAppPort Write-Host Killing the process.. $killScript = "taskkill /PID $runningAngularAppPort /F" Invoke-Expression $killScript Write-Host probing once more $arr = Invoke-Expression $runningEudAppProcessLocal if ($arr.Length -eq 0){ Write-Host There is no running process any more at $portToFind } }
The powershell script above runs 'netstat ano -findstr "someportnumber"'. It finds the PID at that port and splits the result using the '\s+' expression, i.e. whitespace. If we find a PID (process id), we stop that process using the 'taskkill' command with the '-force' flag. We then need to have a C# class that .Net Core can call. The code here has been tested with .Net Core 3.0 and 3.1 successfully.

using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using System; using System.Diagnostics; namespace EndUserDevice { public static class AngularKillPortHelper { /// <summary> /// Kills Angular app running with for example node at configured port Host:SpaPort /// </summary> public static void KillPort() { try { string environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); string configFile = environmentName == Environments.Development ? "appsettings.json" : environmentName == Environments.Staging ? "appsettings.Staging.json" : environmentName == Environments.Production ? "appsettings.Production.json" : "appsettings.json"; // Set up configuration sources. var config = new ConfigurationBuilder() .AddJsonFile(configFile, optional: false) .Build(); string angularAppPort = config.GetValue<string>("Configuration:Host:SpaPort"); if (environmentName == Environments.Development) { string killAngularAppRunningAtPortMessage = $"Trying to shutdown existing running Angular SPA if it is running at reserved fixed port: {angularAppPort}"; Debug.WriteLine(killAngularAppRunningAtPortMessage); Console.WriteLine(killAngularAppRunningAtPortMessage); //requires Nuget Packages: //Microsoft.Powershell.SDK //System.Management.Automation string ps1File = config.GetValue<string>("Configuration:Host:CloseSpaPortScriptPath"); var startInfo = new ProcessStartInfo(); startInfo.FileName = "powershell.exe"; startInfo.Arguments = "-noprofile \"& \"\"" + ps1File + "\"\"\""; startInfo.Arguments += " -portToFind " + angularAppPort; startInfo.UseShellExecute = false; //WARNING!!! If the powershell script outputs lots of data, this code could hang //You will need to output using a stream reader and purge the contents from time to time startInfo.RedirectStandardOutput = !startInfo.UseShellExecute; startInfo.RedirectStandardError = !startInfo.UseShellExecute; //startInfo.CreateNoWindow = true; var process = new System.Diagnostics.Process(); process.StartInfo = startInfo; process.Start(); process.WaitForExit(3*1000); //if you want to limit how long you wait for the program to complete //input is in milliseconds //var seconds_to_wait_for_exit = 120; //process.WaitForExit(seconds_to_wait_for_exit * 1000); string output = ""; if (startInfo.RedirectStandardOutput) { output += "Standard Output"; output += Environment.NewLine; output += process.StandardOutput.ReadToEnd(); } if (startInfo.RedirectStandardError) { var error = process.StandardError.ReadToEnd(); if (!string.IsNullOrWhiteSpace(error)) { if (!string.IsNullOrWhiteSpace(output)) { output += Environment.NewLine; output += Environment.NewLine; } output += "Error Output"; output += Environment.NewLine; output += error; } } Console.WriteLine(output); Debug.WriteLine(output); } } catch (Exception err) { Console.WriteLine(err); } } } }
Finally we can configure our app to use the ports we want to use like this:

"Configuration": { "Host": { "SpaPort": "44394", "ApiPort": "44364", "CloseSpaPortScriptPath": "c:\\dev\\someapp\\somesubdir\\KillAngularApp.ps1" }, }
The ApiPort here is not used by our code in our article. The SpaPort however is used. We then call this helper class like this in Program.cs of our core application hosting the Angular Spa:

public class Program { public static void Main(string[] args) { AngularKillPortHelper.KillPort(); IWebHost host = CreateWebHostBuilder(args).Build(); host.Run(); } //..
This makes sure that the port is ready and not buy by another app (such as your previous debugging session of the app!) and by killing the app running with node at that port, we make
sure that Angular can run at a fixed port. Are we done yet? No! We must also update the package.json file to use that port we want to use! This must correspond to the port we configure in the appsettings.json file to kill in the first place. (i.e. make sure the port is freely available and not busy)

{ "name": "enduserdevice", "version": "0.0.0", "scripts": { "ng": "ng", "start": "ng serve -o --ssl true --ssl-key /node_modules/browser-sync/certs/server.key --ssl-cert /node_modules/browser-sync/certs/server.crt --port=44394 &REM", "build": "ng build --base-href /eud/ --source-map --prod", "build-staging": "ng build --base-href /eud/ --source-map --staging", "build-mobile": "ng build --base-href /eud2/ --source-map -c mobile", "prod-build-dev": "ng build --prod --source-map --base-href /eud/ --prod", "build:ssr": "ng run EndUserDevice:server:dev", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e" },
Never mind much of the setup above, the important bit to make note of here is the part: --port=44394 &REM The adjustments of the ng start script makes sure we use a fixed port of our Angular app. Sadly, as you can see - this is not easily configurable as we hard code it into our package.json. Maybe we could set this up as an environment variable in a Angular environment file.. ? Hope you found this article interesting. Having a fixed port is always handy when developing locally Angular app and you do no not like this random port thingy that is default set up otherwise. Works on my PC!
Share this article on LinkedIn.

No comments:

Post a comment