Quantcast
Channel: Candor Developer
Viewing all articles
Browse latest Browse all 37

How to use Angular CLI with Visual Studio 2017

$
0
0

This article will show how to create an Angular web application using Angular CLI to manage the build process and dependency management, using WebPack. The latest version of Angular at this time of this article is 4.0.1. Using this technique you can integrate Angular into a new or existing Asp.Net MVC web application. Your application can either be a full Angular SPA-only application or it may also include some MVC routed pages. This sample will also use Asp.Net MVC Web API to create the REST layer data endpoints called by the Angular typescript services. All of this will be hosted in a single web application that you can deploy to any IIS-based server or to an Azure cloud service role.

These directions have also been verified with Visual Studio 2015 Community Edition.

Why have both Angular and Asp.Net MVC in one application?

You can certainly have an all Angular application, and there is nothing wrong with that. But there are plenty of reasons to have both in a single application. Many websites have a number of static pages and those pages may have common headers and footers easily managed with master layouts in MVC. Those layouts may have data rendered on the server, or cached in a CDN. Some projects manage this by putting the Angular app in a virtual directory sub-path of the primary domain.

Maybe you want a way to manage multiple separate SPA applications under one deployed application. You can do this by combining multiple Angular CLI SPA apps into one MVC app at build time and then deploying the combined app as one.

The most common scenario may be that you already have an Asp.Net MVC web application now, and you want to phase in an Angular CLI SPA application over time, moving a page at a time from an MVC route into the SPA application.

You can also easily accomplish this using SystemJS by following my previous Angular article series.

The Problems with SystemJS

In my last Angular series the sample project used SystemJS in an Asp.Net MVC web application project. The series discovered some annoyances with the structure of SystemJS. First you had to remember to add new dependencies to the bootrapping code in the main cshtml razor view hosting your Angular app; and second your templateUrl and CssUrl file references in components had to take into account where the view template would be at run-time (dist folder) instead of where its relative location during development.

Differences in Angular CLI

Angular CLI (command line interface) manages your Angular code build for development and the AOT (ahead of time) production build, manages registering all dependencies on the page automatically, helps you follow organizational guidelines with the generator CLI commands and a lot more.

What is the downside? Its a great tool for WebStorm or VS Code, but its not meant to be part of an Asp.Net MVC web application. We need to do a couple things to integrate it into the Visual Studio ecosystem. Hopefully this will be smoother in future versions, and for now this article will show you how to do the initial setup to smooth out this hopefully temporary rough edge.

The root of the problem is that Angular CLI wants to handle the entire build process and serving the app inside Express web server when in development mode. The Visual Studio ‘run’ button also does this for web applications and class libraries needed by your web application. CLI cannot manage the build of your server side C#.Net code. Visual Studio runs in IIS Express or full IIS, while Angular development mode wants to run in Express.

Integrating Visual Studio MVC web apps and Angular CLI apps

As noted earlier, Angular and Visual Studio both want to manage their own build process. So our solution is to let them, and combine the results mid-way. We’re going to build the Angular CLI project first, then copy the few build output files into a folder of the Asp.Net MVC web application and then Visual Studio can perform it’s build as usual and then run the project. Simple.

Creating the project structures

Ok, so the concept is easy enough, but now lets get into the detail of what kind of projects you should create. If you already have an Asp.Net MVC application, you can skip ahead to the creation of the Angular CLI project.

Create your solution and class library

My first step is to create a new solution with a class library project. Be sure to keep “Create directory for solution” checked. Add it to Git source control, and build the business layer. I’m not going to walk through this step in any more detail, but you can check out the sample project.

Create your Asp.Net MVC web application

This step will be nothing new for anyone currently developing with Asp.Net MVC. You should add a new project to the solution, I recommend template “Asp.Net Web Application (.Net Framework)”.

On the second step of the project wizard, select your type of authentication you want included with the app, and also check the box to add “Web API”, since in this example that will serve as the REST service used by the Angular CLI project.

Creating your Angular CLI project

This step uses the CLI — command line interface. The best way I’ve found to do this is via a normal windows command prompt, NOT the Visual Studio 2017 command prompt.” The Visual Studio variety does not respect your system PATH setting and thus ‘npm’ won’t be available to you.

Start by creating a Visual Studio project to hold the CLI generated project. Use ‘Add Project’ and pick ‘Asp.Net Web Application’ just like the MVC project first step, but on step 2 just select the ‘Empty’ template (not MVC or Web API). We will fill this project folder with the CLI generated content. The sample project is named ‘candor-sample-ng-cli’.

Prerequisite alert: For the next step you need to download and install NodeJS / NPM if you don’t have it already. Version 6.9 or later should work, but I have the latest Node 6.10.2 / NPM 3.10.10 at the time of this writing. I used the Windows MSI installer. Go get it here:
https://nodejs.org/en/download/

Once Node is installed, make sure you have the Angular CLI installed globally. This is done once per developer machine, not a per project step. I ran it from my solution folder, but it could be from any folder. After running this command you’ll see NPM output with dependency packages being installed

C:\Users\you\Documents\GitHub\sample-ng2-mvc> npm install -g @angular/cli@latest 

Verify the CLI is installed by checking the CLI help command. After running this command you’ll see all the types of code you can generate and the options for each one.

C:\Users\you\Documents\GitHub\sample-ng2-mvc> ng --help
...
ng new<options...>
  Creates a new directory and a new Angular app.
  --dry-run (Boolean) (Default: false) Run through without making any changes.
    aliases: -d, --dryRun
  --verbose (Boolean) (Default: false) Adds more details to output logging.
    aliases: -v, --verbose
  --link-cli (Boolean) (Default: false) Automatically link the `@angular/cli` package.
    aliases: -lc, --linkCli
  --skip-install (Boolean) (Default: false) Skip installing packages.
    aliases: -si, --skipInstall
  --skip-git (Boolean) (Default: false) Skip initializing a git repository.
    aliases: -sg, --skipGit
  --skip-tests (Boolean) (Default: false) Skip creating spec files.
    aliases: -st, --skipTests
  --skip-commit (Boolean) (Default: false) Skip committing the first commit to git.
    aliases: -sc, --skipCommit
  --directory (String) The directory name to create the app in.
    aliases: -dir <value>, --directory <value>
  --source-dir (String) (Default: src) The name of the source directory.
    aliases: -sd <value>, --sourceDir <value>
  --style (String) (Default: css) The style file default extension.
    aliases: --style <value>
  --prefix (String) (Default: app) The prefix to use for all component selectors.
    aliases: -p <value>, --prefix <value>
  --routing (Boolean) (Default: false) Generate a routing module.
    aliases: --routing
  --inline-style (Boolean) (Default: false) Should have an inline style.
    aliases: -is, --inlineStyle
  --inline-template (Boolean) (Default: false) Should have an inline template.
    aliases: -it, --inlineTemplate
... rest clipped for brevity

Now to create a new CLI project in your solution, run the ‘new’ command. This will fill in the Visual Studio generated project folder with the CLI structure.

C:\Users\you\Documents\GitHub\sample-ng2-mvc>ng new candor-sample-ng-cli --routing --skip-git --directory candor-sample-ng-cli

Since our project solution is already under Git source control, we can use the skip-git flag to tell the CLI not to generate a new Git repo for the project. The directory option will generate the CLI project in our existing Visual Studio project folder since the name of that folder is supplied.

The output shows the files created relative to the output directory specified.

installing ng
  create .editorconfig
  create README.md
  create src\app\app-routing.module.ts
  create src\app\app.component.css
  create src\app\app.component.html
  create src\app\app.component.spec.ts
  create src\app\app.component.ts
  create src\app\app.module.ts
  create src\assets\.gitkeep
  create src\environments\environment.prod.ts
  create src\environments\environment.ts
  create src\favicon.ico
  create src\index.html
  create src\main.ts
  create src\polyfills.ts
  create src\styles.css
  create src\test.ts
  create src\tsconfig.app.json
  create src\tsconfig.spec.json
  create src\typings.d.ts
  create .angular-cli.json
  create e2e\app.e2e-spec.ts
  create e2e\app.po.ts
  create e2e\tsconfig.e2e.json
  create karma.conf.js
  create package.json
  create protractor.conf.js
  create tsconfig.json
  create tslint.json
Installing packages for tooling via npm.
Installed packages for tooling via npm.
Project 'candor-sample-ng-cli' successfully created.

The last 2 lines of the output take some time since they are downloading lots of packages over the internet, so just be patient and wait for it to complete. Your experience may vary if you have ‘Yarn’ package manager installed. If you wish the CLI to install packages via Yarn and configure Yarn at project setup, then you need to tell the CLI to do so by default on creation of new packages. If you wish to do that the command is as follows (to be run before ng new), and Yarn must be installed first.

C:\Users\you\Documents\GitHub\sample-ng2-mvc>ng set --global packageManager=yarn

Now you can switch over to Visual studio and include all of these generated files into the project. But do not include the node_modules folder, since those are restored by other developers using the ‘npm install’ command or by using Visual Studio’s package restore menu item.

Setup the build process

Build and Test the ng project

You should now ‘serve’ the CLI project and make sure it runs.

C:\Users\you\Documents\GitHub\sample-ng2-mvc>cd candor-sample-ng-cli

C:\Users\you\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli>ng serve
** NG Live Development Server is running on http://localhost:4200 **
Hash: bec42c6bef317950e4ef
Time: 8356ms
chunk    {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 158 kB {4} [initial] [rendered]
chunk    {1} main.bundle.js, main.bundle.js.map (main) 4.92 kB {3} [initial] [rendered]
chunk    {2} styles.bundle.js, styles.bundle.js.map (styles) 9.77 kB {4} [initial] [rendered]
chunk    {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.64 MB [initial] [rendered]
chunk    {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered]
webpack: Compiled successfully.

As the instructions note, the app is now running and you can open it at http://localhost:4200/. Open it and you will see “Loading…” and then “app works.” To quit the server to enter further commands, press CTRL-C. You’ll be asked if you want to terminate batch job. Type ‘Y’ and press enter to confirm.

The serve command does not generate a build, rather you need to run the build command to do this.

C:\Users\you\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli>ng build
Hash: 7125a86fd8201d4b0de4
Time: 7496ms
chunk    {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 158 kB {4} [initial] [rendered]
chunk    {1} main.bundle.js, main.bundle.js.map (main) 4.9 kB {3} [initial] [rendered]
chunk    {2} styles.bundle.js, styles.bundle.js.map (styles) 9.77 kB {4} [initial] [rendered]
chunk    {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.34 MB [initial] [rendered]
chunk    {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered]

You will now have a dist folder in your project containing an index.html and a few script files with source maps. This is all you need to run the application. Don’t check in the ‘dist’ folder into source control and don’t include it in the Visual Studio project.

Build CLI output in the MVC web application – Option 1

Now that we have the CLI project, the next step is to get the build output and package it into the main MVC application.

Open your project properties to the ‘Build Events’ tab. For the “Pre-build event command line” enter an xcopy command.

xcopy /I /E /Y "$(SolutionDir)candor-sample-ng-cli\dist" "$(ProjectDir)Scripts\NgApp" 

This is a good start, but it only copies files, and doesn’t rebuild the Angular CLI project each time. Let’s add the ‘ng build’ command before the copy, and some echo statements so we know what happened afterwards.

echo "cd $(SolutionDir)candor-sample-ng-cli" &&^
cd "$(SolutionDir)candor-sample-ng-cli" &&^
echo "building candor-sample-ng-cli" &&^
ng build &&^
echo 'copy files' &&^
xcopy /I /E /Y "$(SolutionDir)candor-sample-ng-cli\dist" "$(ProjectDir)Scripts\NgApp"

This will produce output in the output window of Visual Studio each time you build the project. To be clear, this build script goes in the build process of the MVC web project and not the CLI container MVC app. This makes more sense since that is the web application to run in Visual Studio.

Note, if you have an error here about not finding the path specified, you may have your solution (.sln) file in the wrong location. In the sample project I had to move the solution file to the root, out of the Candor.Sample\ directory in order for this script to work. Normally solution files are at the root below all the project folders and this will work fine. Be sure when creating a brand new project that you keep “Create directory for solution” checked.

1>------ Build started: Project: Candor.Sample.MvcWeb, Configuration: Debug Any CPU ------
1>  "cd C:\Users\mlang\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli" 
1>  "building candor-sample-ng-cli" 
1>  Hash: [1m7125a86fd8201d4b0de4[39m[22m
1>  Time: [1m7325[39m[22mms
1>  chunk    {[1m[33m0[39m[22m} [1m[32mpolyfills.bundle.js, polyfills.bundle.js.map[39m[22m (polyfills) 158 kB {[1m[33m4[39m[22m}[1m[33m [initial][39m[22m[1m[32m [rendered][39m[22m
1>  chunk    {[1m[33m1[39m[22m} [1m[32mmain.bundle.js, main.bundle.js.map[39m[22m (main) 4.9 kB {[1m[33m3[39m[22m}[1m[33m [initial][39m[22m[1m[32m [rendered][39m[22m
1>  chunk    {[1m[33m2[39m[22m} [1m[32mstyles.bundle.js, styles.bundle.js.map[39m[22m (styles) 9.77 kB {[1m[33m4[39m[22m}[1m[33m [initial][39m[22m[1m[32m [rendered][39m[22m
1>  chunk    {[1m[33m3[39m[22m} [1m[32mvendor.bundle.js, vendor.bundle.js.map[39m[22m (vendor) 2.34 MB[1m[33m [initial][39m[22m[1m[32m [rendered][39m[22m
1>  chunk    {[1m[33m4[39m[22m} [1m[32minline.bundle.js, inline.bundle.js.map[39m[22m (inline) 0 bytes[1m[33m [entry][39m[22m[1m[32m [rendered][39m[22m
1>  'copy files' 
1>  C:\Users\mlang\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli\dist\favicon.ico
1>  C:\Users\mlang\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli\dist\index.html
1>  C:\Users\mlang\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli\dist\inline.bundle.js
1>  C:\Users\mlang\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli\dist\inline.bundle.js.map
1>  C:\Users\mlang\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli\dist\main.bundle.js
1>  C:\Users\mlang\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli\dist\main.bundle.js.map
1>  C:\Users\mlang\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli\dist\polyfills.bundle.js
1>  C:\Users\mlang\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli\dist\polyfills.bundle.js.map
1>  C:\Users\mlang\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli\dist\styles.bundle.js
1>  C:\Users\mlang\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli\dist\styles.bundle.js.map
1>  C:\Users\mlang\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli\dist\vendor.bundle.js
1>  C:\Users\mlang\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli\dist\vendor.bundle.js.map
1>  12 File(s) copied
1>    0% compiling 10% building modules 0/1 modules 1 active ...-mvc\candor-sample-ng-cli\src\main.ts ...            
1>  Candor.Sample.MvcWeb -> C:\Users\mlang\Documents\GitHub\sample-ng2-mvc\Candor.Sample.MvcWeb\bin\Candor.Sample.MvcWeb.dll
========== Build: 1 succeeded, 0 failed, 1 up-to-date, 0 skipped ==========
Build CLI output and copy into the web application – Option 2

–Update May 3– A problem with xcopy in the MVC web application is that any changes to the CLI project while the MVC project is running won’t be reflected in the MVC project. Depending on your team workflow this may or may not be a problem.

You can move the xcopy command to the CLI project in the packages.json file. Create a new task for copy, and another to distribute a build to the MVC app.

  "name": "candor-sample-ng-cli",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "copy": "xcopy \"dist\" \"..\\Candor.Sample.MvcWeb\\Scripts\\NgApp\" /i /s /r /y /c",
    "dist": "npm run build && npm run copy"
  },

You can run this on the command line as:

C:\Users\you\Documents\GitHub\sample-ng2-mvc>npm run dist

> candor-sample-ng-cli@0.0.0 dist C:\Users\mlang\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli
> npm run build && npm run copy


> candor-sample-ng-cli@0.0.0 build C:\Users\mlang\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli
> ng build

Hash: ba16068dcc4018b1b749
Time: 13856ms
chunk    {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 158 kB {4} [initial] [rendered]
chunk    {1} main.bundle.js, main.bundle.js.map (main) 27 kB {3} [initial] [rendered]
chunk    {2} styles.bundle.js, styles.bundle.js.map (styles) 60.1 kB {4} [initial] [rendered]
chunk    {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 3.55 MB [initial] [rendered]
chunk    {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered]

> candor-sample-ng-cli@0.0.0 copy C:\Users\mlang\Documents\GitHub\sample-ng2-mvc\candor-sample-ng-cli
> xcopy "dist" "..\Candor.Sample.MvcWeb\Scripts\NgApp" /i /s /r /y /c

dist\favicon.ico
dist\index.html
dist\inline.bundle.js
dist\inline.bundle.js.map
dist\main.bundle.js
dist\main.bundle.js.map
dist\polyfills.bundle.js
dist\polyfills.bundle.js.map
dist\styles.bundle.js
dist\styles.bundle.js.map
dist\vendor.bundle.js
dist\vendor.bundle.js.map
dist\assets\logo.png
13 File(s) copied

This is much faster than stopping Visual Studio, rebuilding and then run again and waiting for the same build and xcopy time. Its not instantaneous, since it takes a little time for the CLI build to run.

Use the copied CLI application on a page

Now that the CLI build is available in the MVC web application, you can now reference it on a page. Lets do this by creating a new MVC view and controller action. The view is a copy of the generated index.html with a small tweak to file paths.

// /Controllers/HomeController.cs
//Your typical default HomeController.cs with attribute routing and a few new routes
[RoutePrefix("")]
public class HomeController : Controller
{
    [Route("")]
    public ActionResult Index()
    {
        return View("NgApp");
    }
    [Route("trip")]
    [Route("location")]
    [Route("person")]
    public ActionResult AppBookmarkableRoutes()
    {
        return View("NgApp");
    }

    [Route("about")]
    public ActionResult About()
    {
        ViewBag.Message = "Your application description page.";

        return View();
    }

    [Route("contact")]
    public ActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";

        return View();
    }
}

The index route and the new routes to be used in the Angular app all reference the same new view to render the Angular app. If your routing isn’t working, check that your RouteConfig has enabled attribute routing.

// /AppStart/RouteConfig.cs
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapMvcAttributeRoutes();

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

The new view file is a copy of the index.html with only the paths of the script files changed to reflect where we copied them.

<!-- /Views/Home/NgApp.cshtml -->
{
    Layout = null;
}
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>CandorSampleNgCli</title>
    <base href="/">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
    <app-root>Loading...</app-root>
    <script type="text/javascript" src="~/Scripts/NgApp/inline.bundle.js"></script>
    <script type="text/javascript" src="~/Scripts/NgApp/polyfills.bundle.js"></script>
    <script type="text/javascript" src="~/Scripts/NgApp/styles.bundle.js"></script>
    <script type="text/javascript" src="~/Scripts/NgApp/vendor.bundle.js"></script>
    <script type="text/javascript" src="~/Scripts/NgApp/main.bundle.js"></script>
</body>
</html>

You should be able to run the MVC application now and see your app.



Moving forward

The app content and features still need to be developed. But you can now edit your app in the CLI project using Visual Studio and press the ‘Run’ button in VS and see your app run. If you prefer you can also run ‘ng serve’ on the command line to run the CLI project directly such as for debugging any routing issues.

The biggest issue remaining would be creation of the production build. When Angular CLI builds for production it doesn’t generate as many script files and it embeds some more content in the index.html file. Instead of hand-jamming the production bits into place when you deploy, you may want to update the script that copies the index.html contents into the NgApp.cshtml file, or maybe just copy the files into the root folder of the MVC web app and then the contents don’t need to be modified when moving into the NGApp.cshtml.

See the followup to this project, How To Create Dynamic Menu And Page Title With Angular Material and CLI

How to create dynamic menu and page title with Angular Material and CLI

References

Full source of the Sample project:
https://github.com/michael-lang/sample-ng2-mvc/

Angular CLI
https://github.com/angular/angular-cli

Post/Pre build events in Visual Studio to copy files
http://stackoverflow.com/questions/11001822/copy-files-from-one-project-to-another-using-post-build-event-vs2010

Alternative: “Running Multiple Websites in a Windows Azure Web Role”
http://www.wadewegner.com/2011/02/running-multiple-websites-in-a-windows-azure-web-role/

Prerequisite: Node Installation
https://nodejs.org/en/download/


Viewing all articles
Browse latest Browse all 37

Latest Images

Trending Articles





Latest Images