Download source
Introduction
Who hasn't heard the phrase, "the best thing since sliced bread?" Bread is one of our oldest and most basic sources of nourishment and just the mention of it makes me want to fill-up on bread before dinner; especially if there is some olive oil on the table.
The making of bread is one of the oldest food technologies, going as far back as the Neolithic era and is linked with the brewing of beer. It was also one of the first food products to succumb to automatic machinery. By the 1930s, pre-sliced bread was fully commercialized and standardization was reinforced by other inventions that required uniform slices, such as toasters. The common phrase, "the best thing since sliced bread," is often mentioned as a way of hyping a new product or invention.
For software developers, the greatest thing since sliced bread is probably the birth of the Internet back in the 1990s. The evolution of the Internet over the years has led to constant change in software development tools for web-based applications; even more so now with the emergence of open source technologies. One of these open-source technologies; AngularJS, now Angular 4 has significantly changed the way we develop web-based front-ends to our business applications. For hardcore Angular developers, the greatest thing since sliced bread might just be a new tool called Angular CLI for Angular 4.
Angular CLI
The world of the Angular platform has evolved into an ambitious platform that allows you to develop fast and scalable applications across all platforms such as web, mobile web and native mobile applications. With the release of NativeScript, you can develop truly native applications for both Android and iOS with Angular and TypeScript or JavaScript. NativeScript compiles your Angular code down to the native UI for each mobile platform all from a single codebase. Check out NativeScript at https://www.nativescript.org.
For web development tooling, Angular CLI is now available. Angular CLI is a Command Line Interface (CLI) to automate your development workflow. Some of the features of the CLI discussed in this article will be:
- Scaffolding a complete brand new Angular 4 application
- Scaffolding additional components
- Run a development web server with LiveReload support to preview your application during development
- Compile TypeScript code and modules
- Build your application for deployment to production
- Build application bundles for lazy-loading portions of your application by module
Additionally, you can perform the following testing of your application with the CLI
- Run your application’s unit tests
- Run your application’s end-to-end (E2E) tests
Visual Studio Professional
If you are a member of an Agile/Scrum team in a Microsoft development shop, you are most likely using an edition of Visual Studio Professional. The Visual Studio product line has been the bellwether development tool for Microsoft shops since the late 1990's - going all the way back to the days of Visual InterDev. This is why I like to use Visual Studio Professional with all my sample solutions for Code Project. Visual Studio Professional provides the following functionality for development teams:
- A fully-featured integrated development environment (IDE) for Android, iOS, Windows, web and the cloud
- Complete tooling for building and publishing web applications to a web server (IIS) and the Azure cloud
- Robust reporting and powerful dashboards, bug and task tracking, agile collaboration, code reviews and planning tools with its tight integration with Microsoft's Team Foundation Server (TFS)
The Angular CLI is not currently integrated with Visual Studio Professional using the Microsoft .NET Framework version 4.6 - but you might find some sample templates using Microsoft .NET Core - but the goal of this article will be to integrate the Angular CLI with Visual Studio Professional for developing and deploying an Angular 4 SIngle Page Application (SPA) and incorporating Visual Studio Professional's powerful publishing tools using Microsoft .NET Framework version 4.6.
Visual Studio Code
Not to be confused with Visual Studio Professional, Visual Studio Code is Microsoft's version of a classic lightweight text editor, similar to Eclipse and Sublime - and comes with a powerful IDE requiring very minimal configuration. Visual Studio Code comes complete with syntax highlighting and autocomplete with IntelliSense including features to debug code right from the editor and launch or attach to your running application and debug with break points, call stacks, and an interactive console. Visual Studio Code has become very popular for developing front-end web projects. For this article, Visual Studio Code will be used to edit and debug TypeScript code and set breakpoints while the Angular CLI builds and reloads the application in the browser.
Sample Application and Goals
The sample application for this article is a Customer Maintenance application that was taken from my previous article: Developing An Angular 2 Application With TypeScript - which details the original Angular 2 and TypeScript code for the sample application that is attached to this article.
The sample web application for this article will consist of the following functionality and goals:
- Use the latest version of Angular, Angular 4 and incorporate the Angular CLI toolset
- Use Angular CLI for scaffolding a complete brand new Angular 4 application
- Use Angular CLI for compiling TypeScript code and for live reloading the application in the browser
- Use Angular CLI for bundling, minification and building module chunks for production lazy-loading purposes
- Use Visual Studio Professional to create a Microsoft ASP.NET MVC project for hosting both the front-end Angular application and the back-end Microsoft .NET application
- Use Visual Studio Professional to deploy the entire application to a production web server (IIS)
- Use Visual Studio Code to edit Angular 4 code and set breakpoints while the application is running
- Integrate Visual Studio Professional and Visual Studio Code with Angular CLI
The sample application will consist of four modules, a main module for the initial base functionality (registering, logging in, etc.) of the application, a shared module that includes functionality for all modules, a customers module and a products module. The customers and products modules will be used to demonstrate the ability to lazy-load modules in a production environment using the webpack build tool that is packaged with the Angular CLI toolset.
Scaffolding a Brand New Angular 4 CLI Application
To start, you will need to install NodeJS and the Node Package Manager (NPM). Go to https://nodejs.org. Once NodeJS is installed you can install the Angular CLI from the Windows command prompt.
npm install -g @angular/cli
Normally you use NPM to install node packages like the Angular CLI - but the Angular CLI now supports a package manager called Yarn. Yarn is an alternative package manager for NPM packages with a focus on reliability and speed. It was released in October of 2016 and has already gained a lot of traction and enjoys great popularity in the JavaScript community.
Angular CLI depends on a package manager when scaffolding a new project using the ng new command. Several node packages will get installed when you create a new CLI project and using Yarn will speed up the process. To enable the Yarn package manager for Angular CLI you have to run the following command from the Windows command prompt.
ng set --global packageManager=yarn
Once this is done you can go ahead and create your new Angular CLI project from the command line as follows:
ng new CodeProject.Angular4.Portal
cd CodeProject.Angular4.Portal
Be sure to build the Angular CLI project in a temporary folder. Later this project will be copied and combined into a Visual Studio Professional ASP.NET MVC project.
Opening the project folder in Visual Studio Code, you can see the initial project structure with all the Angular code residing under a folder in src/app, complete with a node_modules folder and various configuration files for the Angular CLI.
Creating the ASP.NET MVC Project
One of the things I like to do with my Angular front-end projects is host them inside a ASP.NET MVC project. Hosting an Angular application inside an ASP.NET MVC project will allow you to store configuration settings inside a web.config file and inject the settings into the Angular application when it gets bootstrapped by ASP.NET MVC. It gives you a lot of flexibility when it comes to configuring the start up of an Angular application.
When creating the ASP.NET MVC project, be sure to select an ASP.NET Web Application that will run under .NET Framework 4.6.1 - the sample application for this article was developed for .NET Framework 4.6.1.
When choosing an ASP.NET 4.6.1 template, choose the MVC project template and choose to add the core references for Web API. The sample application will use Web API endpoints to allow the Angular front-end application access to the back-end .NET application.
Once the project has been created, you'll have a standard ASP.NET MVC project structure.
Combining the Angular CLI project with the MVC Project
Now that both the Angular CLI project and the ASP.NET MVC project have been created, we can combine the contents of the Angular CLI project with the ASP.NET MVC project by simply navigating to the Angular CLI project folder in Windows Explorer and copy and paste all the folders and files into the ASP.NET MVC project folder structure.
Now that the Angular CLI folders and files are copied into the ASP.NET MVC project folder, you will need to open Visual Studio Professional and select the Show All Files button and right-click on each folder and file and select the Include In Project option for each folder and file highlighted above - excluding the node_modules folder.
Including these folders and files in the project will allow Visual Studio Professional to include these files when checking them into a source code control repository like Team Foundation Server (TFS).
The Development Workflow and Live Reloading
To start developing the Angular 4 code for the sample application, we can begin with the Angular CLI. The first thing to do is open a Windows command prompt, and start-up the Angular 4 application by running ng serve from where the ASP.NET MVC project resides as follows:
cd codeproject.angular4.portal
ng serve
The ng serve command builds the application and starts a local webpack-dev-server. The webpack-dev-server is a little Node.js Express web server. The webpack server uses Hot Module Replacement, so whenever you change a file (TypeScript, HTML template or CSS file) and save it, the server is notified and the code is automatically recompiled and the running application is automatically updated in the browser, and rather than a full page reload, the Hot Module Replacement runtime will simply reload the updated modules and inject them into the running application.
To access the application you can navigate to http://localhost:4200/ in your browser and the application will begin to load and run. When running ng serve, the compiled output is served from memory, not from disk. This is what makes live reloading of your application fast.
Debugging with Visual Studio Code
With the lightweight Visual Studio Code IDE, you can make changes to the Angular 4 TypeScript code, debug TypeScript code and set breakpoints in the TypeScript code while the application is being served in the background by the Angular CLI. Visual Studio Code has complete support for TypeScript syntax highlighting and autocomplete with IntelliSense.
The Angular CLI will handle all the compiling and building of the TypeScript code and modules, so all we need to do is configure Visual Studio Code for debugging and setting breakpoints in the TypeScript code.
There are three things needed for configure Visual Studio Code for debugging:
- Create a shortcut for the Chrome browser that launches Chrome with an extended command line option that enables remote debugging
- Install a Chrome Debugging Extension in Visual Studio Code
- Create a launch.json file in Visual Studio Code with debugging configuration settings
Adding --remote-debugging-port=9222 to the Chrome browser command line will tell Chrome to listen on port 9222 for any debugging notifications. To debug with Chrome you will want to close all your Chrome browsers and always start up the Chrome browser using this shortcut with the port number attached to the command line.
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
Next, start Visual Studio Code and open the root project folder for the sample application. From there you can go into the Visual Studio Code Extensions (5th button on the left menu) and install the Debugger for Chrome extension. You can enter the name in the Search Extensions in Marketplace window to find it.
Finally you need to create a configuration file called launch.json that tells Visual Studio Code to attach to the Chrome browser to start debugging. You can create this configuration file from the debug window when you select the debug button (the 4th button in the left side menu) and add the configuration settings. The configuration file will be saved in a folder named .vscode one level down from the root project folder.
In the configuration file, the key setting is url, which tells the debugger to look for a Chrome window/tab referencing localhost:4200. The absolute url http://localhost:4200 is the default URL when the sample application starts up. The debugger will look for this exact URL when trying to attach to the browser.
The request property can be set to either attach or launch. Setting the property to launch will allow Visual Studio Code to start-up the Chrome browser right from the IDE. For this demo, Visual Studio Code will simply attach to an already running Chrome browser.
{
"version": "0.2.0",
"configurations":
[{
"name": "Attach to Chrome, with sourcemaps",
"type": "chrome",
"request": "attach",
"url": "http://localhost:4200",
"port": 9222,
"sourceMaps": true,
"trace": true,
"webRoot": "${workspaceRoot}"
}]
}
While ng serve is running serving the application in the background from the Windows command prompt, you can then launch the Chrome browser from the shortcut you created on your desktop with the remote debugging port append to the command line.
From Visual Studio Code, you can begin debugging the application and setting breakpoints. To start debugging you can press the debug button and hit the green arrow button with the launch configuration named Attach to Chrome, with sourcemaps set in the debug window which references the launch.json file.
In the example below from Visual Studio Code, I set a breakpoint on line 30 directly in the TypeScript code in the LoginComponent of the application and the application will break on this line when the component is executed in the browser. Once the breakpoint is hit you can step through the code hitting F10 (similar to Visual Studio Professional) with the yellow line indicating which line you are executing next. You get full debugging features in Visual Studio Code, including watch and debug console windows. Debugging TypeScript code directly in an IDE is a great tool for Angular and TypeScript developers.
Scaffolding Additional Components
When you are ready to add functionality to your Angular 4 project, you can either add additional components through Visual Studio Professional, through Visual Studio Code or through the Angular CLI.
The Angular CLI makes it easy to create an application that already works, right out of the box. Additionally, the Angular CLI can also scaffold and generate new components, routes, services and pipes with a simple command ng generate. The ng generate command can also create simple test shells for all of these.
To add a login Angular component, I executed the following command from the Windows command prompt:
ng generate component --flat login
Upon executing the ng generate command, the below information will appear:
installing component
create src\app\home\login.component.css
create src\app\home\login.component.html
create src\app\home\login.component.spec.ts
create src\app\home\login.component.ts
update src\app\app.module.ts
The ng generate command creates and scaffolds a new Angular TypeScript file login.component.ts. It also generated the HTML template for the component, a css style sheet, and a test shell file for running unit tests against the component.
The --flat option on the command line tells the ng generate command to create the component files in the current working directory. By default the files would have been generated in a new sub folder.
You'll also notice that the ng generate command will also update the main application module, adding an import statement for the LoginComponent and adding to the declarations section of the main application module.
The generated LoginComponent gets created with the basic structure for an Angular TypeScript component saving you the time from having to type this out yourself each time you create a component,
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
If you choose to create your components using the ng generate command, be sure to go into Visual Studio Professional and include these files in your project. Doing this will allow these files to be included in a change set when checking-in changes and additions into Team Foundation Server (TFS).
The Complete Sample Project
For demonstration and source code distribution purposes, I combined both the front-end Angular 4 application and the back-end Microsoft .NET back-end components and Web APIs into the same ASP.NET MVC project solution. The entire solution for this project contains 6 individual projects. In your normal development projects, you'll probably want to create complete separate solutions for the front-end project and the back-end projects. Below are all the projects included in the solution.
The attached sample application was built to have the front-end Angular 4 application make Web API requests to the back-end ASP.NET Web API components to return data back to the browser. Using the ng serve command during development, the front-end application will be served by the webpack development web server running under node.js and will be available on localhost port 4200.
During development, the ASP.NET Web API back-end application will get launched from Visual Studio Professional and will be served by the traditional IIS Express Web Server that comes with Visual Studio Professional and has been configured to be available on localhost port 55499.
Disabling TypeScript Compiling
When you try to build and launch the ASP.NET MVC project in Visual Studio Professional for the first time, the build will fail because the project sensed TypeScript code and a tsconfig.json file in the project and tried to compile the TypeScript files.
Instead of configuring Visual Studio Professional to properly compile TypeScript code, the Angular CLI will be responsible for all TypeScript compiling, bundling and module building; therefore we need to turn off TypeScript compiling in Visual Studio Professional.
To disable TypeScript compiling in Visual Studio Professional, the project file needs to be edited by right-clicking on the CodeProject.Angular4.Portal project and selecting Unload Project. This will unload the project and bring up the .csproj file so you can edit it. The following needs to be added anywhere in the project file which will disable TypeScript compiling in Visual Studio Professional:
<PropertyGroup>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
</PropertyGroup>
After editing and saving and closing the .csproj file, you can select the Reload Project option. Now Visual Studio Professional will be able to build and run the ASP.NET MVC project that will host the Web API. The good news is that Visual Studio Professional will still have full TypeScript support if you wish to edit TypeScript files in Visual Studio Professional. The ng serve command will automatically watch and pick-up and compile any TypeScript file changes regardless of which editor you want to use to edit your TypeScript files.
CORS - Cross-Origin Resource Sharing
When you first run the sample application and try to login, the Angular front-end application will receive a HTTP response error stating that the OPTIONS method is not an allowed HTTP method when trying to make a Web API request to the ASP.NET MVC Web API.
A resource makes a cross-origin HTTP request when it requests a resource from a different domain, protocol, or port that's different than it's origination. The error occurs because the Angular front-end application is being served on port 4200 and the Web API back-end application is being served on port 55499. The client AJAX request is trying to make a cross domain request.
Each time the Angular application makes an HTTP request, the Content-Type in the request header is being set to application/json; charset=utf-8, indicating that the message body content will be in JSON format. When making a cross domain request with this Content-Type setting, the request is preflighted.
Preflighted requests first send an HTTP request using the OPTIONS method to the resource on the other domain in order to determine whether the actual request is safe to send. To accept preflighted requests, the server needs to implement and support Cross-origin resource sharing (CORS). CORS is a mechanism that allows restricted resources on a web page to be requested cross domain.
ASP.NET Web API 2 supports CORS. To enable CORS support in your Web API project, you can do the following:
- Add the Microsoft.AspNet.WebApi.Cors NuGet package to your project
- Add config.EnableCors() in the Register method startup configuration
- Add the [EnableCors] attribute to your Web API controller or controller method
Often you will see the EnableCors attribute looking something like the following:
[EnableCors(origins: "http://example.com", headers: "*", methods: "*")]
The example above will enable the Web API to accept all requests from example.com, and will accept all headers and methods. This implementation seemed too much of a Black Box approach to me, and I'm not a big fan of hardcoding URLs and in the source code; sure you could set a wildcard "*" for the origin but if you need to send cookies cross-origin, then the wildcard will not allow the cookies to be included in with the HTTP request. After consideration, I decided to research what CORS was really doing and I ended up implementing my own solution that would be more dynamic and flexible without any hardcoding of URLs.
After a few days of research, I learned that CORS simply sets a few response headers. To implement my custom CORS implementation, I added an Application_BeginRequest method in the global.asa file to process all HTTP requests and set the needed headers before the request hits the Web API controllers.
In the code below, the OPTIONS request is intercepted, and the required response header values are set and returned back to the client. After the preflighted request is satisfied, the client then sends a second request back to the Web API which ends up being the normal GET or POST request that will execute the Web API controller endpoint.
Basically, the key headers that need to be set to support cross-domain requests are:
- Access-Control-Allow-Credentials
- Access-Control-Allow-Origin
- Access-Control-Allow-Headers
- Access-Control-Allow-Methods
The sample application uses a persisted JSON Web Token (JWT) stored in local storage for passing user credentials to the server, but if you happen to be using cookies for passing user credentials; you'll need to set in the Angular code, a request header option withCredentials to true in the AJAX request.
Sending cookies cross-domain means the Access-Control-Allow-Credentials response header must be set to a URL that matches exactly to the client URL origin.
protected void Application_BeginRequest()
{
var currentRequest = HttpContext.Current.Request;
var currentResponse = HttpContext.Current.Response;
string currentOriginValue = string.Empty;
string currentHostValue = string.Empty;
var currentRequestOrigin = currentRequest.Headers["Origin"];
var currentRequestHost = currentRequest.Headers["Host"];
var currentRequestHeaders = currentRequest.Headers["Access-Control-Request-Headers"];
var currentRequestMethod = currentRequest.Headers["Access-Control-Request-Method"];
if (currentRequestOrigin != null) {
currentOriginValue = currentRequestOrigin;
}
currentResponse.AppendHeader("Access-Control-Allow-Origin", currentOriginValue);
foreach (var key in Request.Headers.AllKeys)
{
if (key == "Origin" && Request.HttpMethod == "OPTIONS")
{
currentResponse.AppendHeader("Access-Control-Allow-Credentials", "true");
currentResponse.AppendHeader("Access-Control-Allow-Headers", currentRequestHeaders);
currentResponse.AppendHeader("Access-Control-Allow-Methods", currentRequestMethod);
currentResponse.StatusCode = 200;
currentResponse.End();
}
}
}
Another cool thing about implementing the Application_BeginRequest method is that you can enhance the method to restrict which client domains can access your Web API and block all other requests of unknown origins.
The Angular CLI Index.html Page
The Angular CLI comes with a little index.html file that launches the Angular application when running the application from the CLI ng serve command. Since the application needs to communicate with the back-end ASP.NET Web API endpoints, I needed a way to let the Angular application know the URL for all the Web API endpoints.
Without hardcoding the values inside any of the Angular code, I decided to inject the endpoint URL into the Angular application within the app-root tag from within the index.html page. The index.html will only be used for development purposes when running the application from the CLI. Later the setting will be stored in a web.config file and will be injected from an MVC Razor View when running the application under IIS or IIS Express.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Code Project Angular 4</title>
<base href="/">
</head>
<body>
<app-root webApiEndPoint="http://localhost:55499/api/">Loading ...</app-root>
</body>
</html>
The app-root selector loads the main application component AppComponent and inside the constructor for this component, the injected value of the webApiEndPoint parameter is accessed by the constructor and loaded into a Angular session service property called apiServer which can be referenced whenever a HTTP request is made by the Angular application.
constructor( private sessionService: SessionService, private elementRef: ElementRef) {
let native = this.elementRef.nativeElement;
this.webApiEndPoint = native.getAttribute("webApiEndPoint");
sessionService.apiServer = this.webApiEndPoint;
}
The ASP.NET MVC Index Razor View Page
After developing the application and running the Angular application from the Angular CLI developer webpack web server, you'll eventually will want to run the Angular application under IIS Express and then ultimately published out to a production Internet Information Server (IIS) web server.
In the sample application, bootstrapping the Angular application from ASP.NET MVC is done through the default index.cshtml Razor View page. It's a little more elaborate than the CLI index.html page because it will pull configuration information from the web.config and support the following three modes:
- Running the Angular front-end and the back-end ASP.NET Web API under the same IIS Express port
- Running the ASP.NET MVC project to only run the Web API without launching the Angular application
- Running the entire application in a production web server (IIS)
Below is the index.cshtml Razor View page. As you can see there is a lot going on in this page that bootstraps and launches the Angular application while also hosting the ASP.NET Web API endpoints.
@
{
string version = typeof(CodeProject.Angular4.Portal.RouteConfig).Assembly.GetName().Version;
string webApiEndPoint = System.Configuration.ConfigurationManager.AppSettings["WebApiEndPoint"];
string currentRoute = "/";
foreach (string key in HttpContext.Current.Request.QueryString.AllKeys)
{
// when the user hits F5 in the browser, Angular will get rebooted
// the current Angular route is submitted to ASP.NET MVC
// For this sample application, the ASP.NET MVC routing configuration was changed
// to return the original route back to the client as an appended query string parameter
if (key == "CurrentRoute")
{
currentRoute = HttpContext.Current.Request.QueryString[key];
break;
}
}
string runMode = CodeProject.Angular4.Portal.Properties.Settings.Default.RunMode;
IEnumerable<string> fileEntries = Enumerable.Empty<string>();
List<string> bundles = new List<string>();
if (runMode == "WEBAPI")
{
Response.Write("running web api mode");
}
else
{
fileEntries = Directory.EnumerateFiles(Server.MapPath("~/dist"));
bundles.Add("inline.");
bundles.Add("polyfills");
bundles.Add("styles.");
bundles.Add("vendor.");
bundles.Add("main.");
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8">
<title>Code Project Angular 4</title>
<link rel="icon" type="image/x-icon" href="~/dist/favicon.ico">
@if (HttpContext.Current.IsDebuggingEnabled)
{
<base href="/">
}
else
{
<base href="http://localhost/codeprojectangular4/">
}
<script>
history.pushState({}, null, "@currentRoute");
</script>
</head>
<body>
@RenderBody()
@if (runMode == "WEBAPI")
{
return;
}
<app-root imagesDirectory="dist" webApiEndPoint="@webApiEndPoint">Loading @version</app-root>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
@if (HttpContext.Current.IsDebuggingEnabled)
{
<script src="~/dist/inline.bundle.js?v=@version"></script>
<script src="~/dist/polyfills.bundle.js?v=@version"></script>
<script src="~/dist/styles.bundle.js?v=@version"></script>
<script src="~/dist/vendor.bundle.js?v=@version"></script>
<script src="~/dist/main.bundle.js?v=@version"></script>
}
else
{
foreach (string bundleName in bundles)
{
foreach (string fileName in fileEntries)
{
FileInfo fileInfo = new FileInfo(fileName);
if (fileInfo.Name.Contains(bundleName) && fileInformation.Name.Contains(".map") == false)
{
if (fileInfo.Name.Contains("styles"))
{
<link href="~/dist/@fileInfo.Name" rel="stylesheet" />
}
else
{
<script src="~/dist/@fileInfo.Name"></script>
}
}
}
}
}
</body>
</html>
The above index page consists of the following functionality:
- Load the build version from the assembly information file for display purposes
- Load the Web API endpoint URL from the web.config
- Load and set the current Angular route which defaults to "/" and supports F5 browser refreshes
- Set the base URL for application and the default URL for the browser history that Angular will reference
- Load the run mode from an application setting; the page will support a Web API only run mode
- Inject the Web API endpoint URL as a parameter into the Angular application
- Reference and load the main application component for the Angular application
- Load all the JavaScript bundles generated by the Angular CLI ng build command
- Support three modes, debug mode, Web API only mode and production release mode
Angular CLI Development Bundles
When you ran the Angular application from the Angular CLI ng serve command, the application was being bundled and served from memory by webpack. To run the Angular application from IIS Express, the Angular application needs to be compiled and bundled from within the Visual Studio Professional build pipeline which includes building both the Angular front-end and the entire back-end Microsoft .NET application.
For the sample application, Visual Studio Professional will only build the .NET portions of the application. We turned off the compiling of the Angular TypeScript code in the project. To build the Angular code automatically through Visual Studio Professional, I added the following to the .csproj project file:
<Target Name="AfterBuild">
<Exec Command="ng build --deploy-url http://localhost:55499/dist/" WorkingDirectory="$(ProjectDir)" Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' " />
</Target>
Adding this to the project file will tell Visual Studio Professional, that after the build successfully runs for all the Microsoft .NET code, go ahead and perform a build of the Angular application using the Angular CLI ng build command and compile and bundle the output into a dist folder inside the project folder structure. A conditional is added to the Exec Command to only perform this build when running in the debug configuration mode. Later we will add a production build configuration. The --deploy-url parameter will indicate the URL where the bundles will be served from.
After the ng build command successfully builds the Angular application, the following content will reside in the default dist folder which will reside within the project structure. As you can see, there are several bundle files in this folder.
Webpack
As stated before the Angular CLI uses webpack for building and bundling Angular code.
webpack is a module bundler for modern JavaScript applications. When webpack processes your application, it recursively goes through the entire Angular application codebase, starting with the top level application component and creates a dependency tree that includes every component and module your application needs, then packages all of the modules into a small number of bundles to be loaded by the browser.
In the folder above, separate bundles were created for both the Angular node_modules code and a separate bundle for the custom Angular application code including separate bundles for polyfills and style sheets.
During development, none of these bundles are minimized or have gone through any uglification. Later we will create optimized bundles for production release. To load these bundles so that the Angular application can run from ASP.NET MVC, the index.cshtml page needs to make reference to these bundles.
Extracting the Webpack Configuration
The inclusion of webpack in the Angular CLI is black box implementation. The webpack configuration is hidden within the Angular CLI. If you need to customize webpack for your application, the Angular CLI provides an ng eject command that will create a webpack.config.js file. When you run ng eject, the package.json file is modified with new npm scripts and an ejected flag is added to your .angular.cli.json file. After the webpack.config.js is extracted and created, you're free to configure it the way you want.
Loading Webpack Development Bundles
To load the bundles created by webpack, the index.cshtml page simply makes references to the bundles through script tags. Since these are development bundles and not meant to for production, these bundles will only be loaded when the project is running in debug mode. Additionally, the version number from the project assembly information is appended to the filename for browser cache busting purposes during development.
@ {
string version = typeof(CodeProject.Angular4.Portal.RouteConfig).Assembly.GetName().Version;
}
@if (HttpContext.Current.IsDebuggingEnabled)
{
<script src="~/dist/inline.bundle.js?v=@version"></script>
<script src="~/dist/polyfills.bundle.js?v=@version"></script>
<script src="~/dist/styles.bundle.js?v=@version"></script>
<script src="~/dist/vendor.bundle.js?v=@version"></script>
<script src="~/dist/main.bundle.js?v=@version"></script>
}
Running the ASP.NET MVC Project in Web API Mode
For convenience and demonstration purposes, I included both the Angular 4 front-end application and the back-end Web API in the same Visual Studio Professional solution. Normally you will create a separate solution for both projects.
The problem with including everything in the same project, is that when running the front-end from the Angular CLI and running the Visual Studio Professional project to start-up the Web API, the front-end Angular application also started up from the default index.cshtml page when starting up the project for the Web API. When running the Angular application from the CLI, I only wanted Visual Studio Professional to run the Web API and not start-up the Angular front-end application.
To accomplish this goal, I went ahead and created a Web API configuration in the project through the Visual Studio Professional Configuration Manager. Now the project has three configurations, Debug, Release and Web API.
After creating the new Web API configuration, I needed a project setting to determine which configuration was currently being run. To create this project setting, I went into the project Properties and selected Settings and added a property called RunMode.
Now I just needed to figure out a way to dynamically set the value of this property while the project was running. At the top of the Settings page, there is an option called View Code. Clicking this option took me into a file called settings.cs. In the C# code of the settings.cs file, I was able to dynamically set the RunMode property that I added.
using System.Diagnostics;
namespace CodeProject.Angular4.Portal.Properties {
public sealed partial class Settings {
public Settings() {
SetWebApiApplicationSettings();
SetDebugApplicationSettings();
SetReleaseApplicationSettings();
}
[Conditional("WEBAPI")]
private void SetWebApiApplicationSettings()
{
this["RunMode"] = "WEBAPI";
}
[Conditional("DEBUG")]
private void SetDebugApplicationSettings()
{
this["RunMode"] = "DEBUG";
}
[Conditional("RELEASE")]
private void SetReleaseApplicationSettings()
{
this["RunMode"] = "RELEASE";
}
}
}
To dynamically set the RunMode property at run-time, I added three methods to the Settings.cs file:
- SetWebApiApplicationSettings();
- SetDebugApplicationSettings();
- SetReleaseApplicationSettings();
Each method has a Conditional attribute telling Visual Studio Professional when to execute each individual method based on the current running configuration, Debug, Release or WebAPI. In the SetWebApiApplicationSetting() method, the following line sets the RunMode to WEBAPI.
this["RunMode"] = "WEBAPI";
Now in the index.cshtml page, the page can reference the RunMode property and execute without starting up and rendering the Angular application.
@{
string runMode = CodeProject.Angular4.Portal.Properties.Settings.Default.RunMode;
}
<body>
@if (runMode == "WEBAPI")
{
return;
}
<app-root webApiEndPoint="@webApiEndPoint">Loading @version</app-root>
<body>
Technically there really isn't a need to create a project settings property for integrating the Angular CLI with Visual Studio Professional, because you'll create a separate Visual Studio Professional project for Angular. But for demonstration purposes, it helped speed up the booting of the Web API. It was also worthwhile to see how to set up a project property in Visual Studio Professional and set its value dynamically.
Publishing to a Production Web Server (IIS)
When all the development, testing and debugging is complete, its time to publish the application out to a production web server running under Internet Information Server (IIS). Doing this can be done using the traditional publishing feature in Visual Studio Professional.
But before we can do this, we need to create a production bundle of the Angular 4 application and add hooks into the publishing pipeline that also publishes the production bundles of the Angular 4 application with the rest of the deployment to IIS.
To create production bundles of the Angular application, I revisited the .csproj project file and added a production build command to the AfterBuild target.
<Target Name="AfterBuild">
<Exec Command="ng build --prod --deploy-url http://localhost/codeprojectangular4/dist/" WorkingDirectory="$(ProjectDir)" Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' " />
<Exec Command="ng build --deploy-url http://localhost:55499/dist/" WorkingDirectory="$(ProjectDir)" Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' " />
</Target>
I added the following ng command to the project file which will get executed when publishing the application.
<Exec Command="ng build --prod --deploy-url http://localhost/codeprojectangular4/dist/" WorkingDirectory="$(ProjectDir)" Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "/>
The --prod option does a few things, including the following:
- performs minification of all generated bundles
- performs uglification of the code within each bundle
- performs some tree-shaking which eliminates dead code from bundle generation
- includes a random generated value that is attached to the bundle filename for cache busting purposes
- performs Ahead-Of-Time (AOT) compilation
Ahead-Of-Time (AOT) compilation
With the Angular CLI, Ahead-Of-Time (AOT) compilation is performed by default during a production build process. An Angular application consists largely of components and their HTML templates. Before the browser can render the application, the components and templates must be converted to executable JavaScript by the Angular compiler.
Without AOT, the application is compiled in the browser at runtime, as the application loads, using the just-in-time (JIT) compiler. With AOT compilation, the compiler runs once at build time and JIT runs every time for every user at runtime. With AOT, the browser downloads a pre-compiled version of the application which ends up having a smaller payload and thus reduces the load time of your application.
The output of the ng build command ends up in the dist folder within the project and will have the following contents:
The ng build command outputs everything into the project dist folder that is needed to run the Angular application, including image files and style sheets.
As you can see in the dist folder, the production build generated bundles and added a randomly generated unique value to each filename. This is for cache busting purposes so that the browser will download the new version of the application code and other resources when the user hits the web site after each new publication of the application.
After initially publishing the application to a production web server, I noticed that the dist folder didn't get included in the publish. Because the contents of the dist folder are not included in the Visual Studio Professional project, the contents of the folder never made it to the production web server.
To solve this problem I had to a add a target called CustomCollectFiles, that gets executed during the publishing process. The CustomCollectFiles reference in the .csproj project file tells Visual Studio Professional to include all the content in the dist folder while publishing the application. Once I did this and republished the application, the contents of the dist folder were published to the production IIS web server.
<PropertyGroup>
<CopyAllFilesToSingleFolderForMsdeployDependsOn>
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForMsdeployDependsOn);
</CopyAllFilesToSingleFolderForMsdeployDependsOn>
</PropertyGroup>
<Target Name="CustomCollectFiles">
<Message Text="=== CustomCollectFiles ===" Importance="high" />
<ItemGroup> <CustomFiles Include="dist\**\*" />
<FilesForPackagingFromProject Include="%(CustomFiles.Identity)">
<DestinationRelativePath>dist\%(Filename)%(Extension)</DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
Dynamically loading Production Bundles
One problem with the inclusion of a random generated value attached to each bundle filename, is that the index.cshtml page won't know the name of these files at runtime so there needs to be a mechanism to load these bundles when bootstrapping and launching the Angular application dynamically.
To load these bundles dynamically, I implemented the following in the index.cshtml page.
@{
IEnumerable<string> fileEntries = Enumerable.Empty<string>();
List<string> bundles = new List<string>();
fileEntries = Directory.EnumerateFiles(Server.MapPath("~/dist"));
bundles.Add("inline.");
bundles.Add("polyfills");
bundles.Add("styles.");
bundles.Add("vendor.");
bundles.Add("main.");
}
<body>
foreach (string bundleName in bundles)
{
foreach (string fileName in fileEntries)
{
FileInfo fileInformation = new FileInfo(fileName);
if (fileInformation.Name.Contains(bundleName))
{
if (fileInformation.Name.Contains("styles"))
{
<link href="~/dist/@fileInformation.Name" rel="stylesheet" />
}
else
{
<script src="~/dist/@fileInformation.Name"></script>
}
}
}
}
</body>
In the example above, the index.cshtml page was modified to peak into the dist folder to get a list of files in that folder. Next a generic list of known partial bundle filenames is created.
Finally, in the body, a foreach loop is executed that finds matching filenames based on the partial name using a contains method and then loads the file in either a script tag or a style tag. Now the index.cshtml can load these bundles dynamically.
Lazy-Loading of Angular Modules
For small applications, you can technically bundle your entire application into a single bundle and your application will start-up quickly. But over time your application may become larger and larger and loading the entire application all at once might end up slowing down the initial start-up of your application after each new published version of your application - especially if your application consists of hundreds of components and several modules.
One of the nice features of Angular 4, and the integration of the webpack bundler within the Angular CLI is the ability to break-up Angular code into modules and load them into separate bundles and lazy-load them upon request by the browser.
Most Angular applications don't need to load everything at once, it only needs to load what the user expects to see when the application first loads. In Angular, you can lazy load child modules to improve initial load times and keep people from downloading code for areas they don’t have access to.
Grouping related pieces of functionality of our application into separate Angular modules gives us the ability to load those pieces on demand. Angular modules that are lazily loaded will only be loaded when the user navigates to their associated routes.
The sample application for this article has the following four modules:
- The main application module for the root application component and other initial components
- A shared module for components that need to be shared across all modules
- A Customers module for Customer functionality
- A Products module for Product functionality
The Customers and Products modules don't need to be loaded until after a user has either registered or logged into the application and have navigated to those modules, so for demonstration purposes, I wanted to lazy-load these modules when the user accesses these modules.
In Angular, lazy-loading is triggered through Angular routing. The application-routes.ts file below sets-up the initial navigational routes for the main module of the application. Only a handful of Angular routes are available to the user to access after the start-up of the application.
import { Routes } from '@angular/router';
import { AboutComponent } from './home/about.component';
import { RegisterComponent } from './home/register.component';
import { LoginComponent } from './home/login.component';
import { ContactComponent } from './home/contact.component';
import { HomeComponent } from './home/home.component';
import { UserProfileComponent } from './user/user-profile.component';
import { AuthorizationGuard } from "./authorization-guard";
export const AppRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'home/about', component: AboutComponent },
{ path: 'home/contact', component: ContactComponent },
{ path: 'home/home', component: HomeComponent },
{ path: 'home/register', component: RegisterComponent },
{ path: 'home/login', component: LoginComponent },
{ path: 'user/user-profile', component: UserProfileComponent, canActivate: [AuthorizationGuard] },
{ path: 'customers', loadChildren: './customers/customers.module#CustomersModule' },
{ path: 'products', loadChildren: './products/products.module#ProductsModule' }
];
References to the customer and products modules are included in this TypeScript file, with a loadChildren property. This property gets set to the path of their associated modules. With this set-up, the customers and products modules will not be loaded until after the user hits one of the routes in each of these two modules.
Next we need to set-up the a module for both the customers and the products modules. The example below is the TypeScript code for the customers module customers.modules.ts with declarations for the components included in the customers module; the CustomerInquiry and the CustomerMaintenance components.
The customer module also needs to import the SharedModule which contains all the shared services and components for the entire application, the CommonModule and the FormsModule. The CommonModule and the FormsModule references Angular modules every application module needs, And finally the CustomersRoutingModule needs to be imported into the Customers module.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CustomerInquiryComponent } from './customer-inquiry.component';
import { CustomerMaintenanceComponent } from './customer-maintenance.component';
import { CustomersRoutingModule } from './customers-routing.module';
import { FormsModule } from '@angular/forms';
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [
CustomerInquiryComponent,
CustomerMaintenanceComponent,
],
imports: [
CommonModule,
CustomersRoutingModule,
FormsModule,
SharedModule
]
})
export class CustomersModule { }
The customers.routing.ts TypeScript file is where all the routes for the customers module is defined. Setting up routes for lazy loading purposes is the same as setting up routes for any other module with one exception, in the example below, you have to import the Angular RouterModule and tell the RouterModule that the customer routes are child routes by specifying forChild(customersRoutes) when importing the RouterModule.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { CustomerMaintenanceComponent } from './customer-maintenance.component';
import { CustomerInquiryComponent } from './customer-inquiry.component';
import { AuthorizationGuard } from "../authorization-guard";
const customerRoutes: Routes = [
{ path: '', component: CustomerInquiryComponent },
{ path: 'customer-inquiry', component: CustomerInquiryComponent},
{ path: 'customer-maintenance', component: CustomerMaintenanceComponent}
]
@NgModule({
imports: [
RouterModule.forChild(customerRoutes)
],
exports: [RouterModule]
})
export class CustomersRoutingModule {}
Generating Separate Lazy-Loaded Bundles
Now the big question is, how do we generate separate JavaScript bundles for production that can be lazy-loaded on demand? Well I have good news, if you are using the Angular CLI, the webpack bundler took care of all this for us. If you noticed in the dist folder, you'll see two additional files with an extension of chunk.
The webpack bundler was smart enough to know while traversing the Angular application code for dependencies, it noticed how the Angular routing structure was defined and went ahead and created two separate bundles (chunks); one for the customers module and one for the products module. We don't have to do anything else.
When a user hits one of the routes in the customers module, the chunk for the customer module will automatically get loaded. The same occurs when the user hits a route in the products module. The deploy-url parameter in the ng build command helped the application know where to find these chunks. Webpack is a powerful tool and makes different production deployment scenarios easy to accomplish.
Of course one of the great things about developing Angular and Single-Page-Applications (SPA) is that eventually all the application content will get downloaded and cached on the client, meaning at some point the application is only hitting the server for database data when making HTTP Web API requests reducing the load on the web server. This is also why the cache busting file naming convention in the dist folder is important. It will allow a new version of your application to get downloaded to the client instead of the older version getting cached and executed after the publication of the new version of the application.
Conclusion
For web developers, life has become all about tooling and developing high quality tested web applications rapidly. To ease the burden of developing and testing web-based applications and SPAs, there are now hundreds of tools and frameworks for web developers, including tools and frameworks for testing, front-end development, various IDE editors, text editors, libraries, modules, extensions, code generators, UI components and grid tools, and more.
Of course there is a large entry price for admission. You'll have go through the mind-boggling experience of trying to figure which tools to use, how to use them and then ultimately become well educated on a particular core framework or two; such as Angular, ReactJS and NodeJS. But like the invention of the toaster, you'll eventually find these tools to be the greatest thing since sliced bread.
Downloading and Running the Sample Application
If you wish to run the sample application attached to this article, the instructions are as follows:
- Download and extract the compressed zip file attached to this article (link at the top)
- Download and install Visual Studio Professional (Community, 2015 or 2017 edition)
- Download and install Visual Studio Code (optional)
- Download and install NodeJS
- Install Angular CLI
- From the Windows command prompt, run ng new temp-project in a temporary folder
- From Windows Explorer, copy the node_modules folder created by the Angular CLI ng new command
- From Windows Explorer, navigate to the CodeProject.Angular4.Portal project folder
- From Windows Explorer, paste the node_modules folder into the CodeProject.Angular4.Portal folder
- From the Windows command prompt, change directories to the CodeProject.Angular4.Portal folder
- From the Windows command prompt, run npm install
- Open the project CodeProject.Angular4.Portal with Visual Studio Professional
- Build and run the project CodeProject.Angular4.Portal selecting the Web Api configuration
- From the Windows command prompt, execute ng serve
- Open up a browser and navigate to localhost:4200