I am a danish programmer living in Bangkok.
Read more about me @ rasmus.rummel.dk.
Webmodelling Home > ASP.NET Core Angular2 in Visual Studio - how to
Share it if you like it

ASP.NET Core Angular2 in Visual Studio

21 Nov 2016. This tutorial will show step by step how to setup an ASP.NET Core Angular2 project in Visual Studio 2015.

This tutorial is intended as a Visual Studio ASP.NET Core version of the official and well written but generic Angular2 QuickStart tutorial on angular.io. In addition I have considered configurations so that then you are finished with this Visual Studio ASP.NET Core QuickStart tutorial, you can use the project to directly start following the angular.io Tour-of-Heroes tutorial to actually learn Angular2 but in the context of Visual Studio and ASP.NET Core.

Index :

  1. Create a Visual Studio Angular2 ASP.NET Core project
  2. Add Angular2 & Typescript to the ASP.NET Core project
  3. Automate build with Gulp
  4. Write the Angular2 Hello World code - at last

Appendixes :

Main references :




Introduction

SPA (Single-Page-Application) have been the ideal of web applications for some years already with still stronger support from programming tools.

As ASP.NET, Angular and many supplementary and competing tools are moving forward and the dust starts to settle, it is clear that one of the most popular tools combination for building tomorrows .NET SPA websites will be :

  • Visual Studio : the IDE.
  • ASP.NET Core : the web application framework.
  • Angular2 : the frontend framework.
  • TypeScript : the frontend coding language.
  • NPM : a package manager.
  • Gulp : a build automation tool.
Visual Studio logo Visual Studio the IDE.
ASP.NET Core logo ASP.NET Core the web application framework.
Angular logo Angular2 the frontend framework.
TypeScript logo TypeScript the frontend coding language.
NPM logo NPM a package manager.
Gulp logo Gulp a build automation tool.

To get a grasp on and integrate so many tools may seem challenging at first, but the advantages are significant and offers a SPA website building experience better than ever before (I have been a happy full stack programmer for more than 15 years and I have never been more happy than now). In addition the above programming tools combination is the perfect foundation to amend with Cordova & Ionic to build cross platform mobile applications.

Angular logoAngular2 is a MVW (Model-View-Whatever) frontend Javascript framework targeting the creation of SPAs. Angular2 is created by Google and is often pitted against React created by Facebook, however Angular2 is a full client-side framework while React is just a client-side View engine, indeed it is possible to use React within Angular2 as the View engine of Angular2 if so desired. So while people using React are typically also creating SPAs, React does not handle eg. data structures, server communication etc. Note that Google have developed Angular2 in Typescript and therefore all the Angular2 framework must itself be transpiled to Javascript before it can execute in the browser.

TypeScript logoTypeScript is created by Microsoft as a superset of Javascript. However Typescript is strongly typed, full object oriented (encapsulation, inheritance, classes, interfaces), can do lambda syntax, have compile-time erros and great Intellisense support - all resulting in higher productivity, reduced debug time and easier scale, maintenance & teaming comparing to developing in vanilla Javascript. Typescript does not execute in the browser but must first be transpiled to Javascript before the browser can execute it.




Create an ASP.NET Core project

  1. Open Visual Studio 2015 (must be update 3 or later).
  2. In Visual Studio select to create a new project.
  3. In the "New Project" dialog select "ASP.NET Core Web Application (.NET Core) and give the project a name, here QuickStart.
  4. In the "New ASP.NET Core Web Application (.NET Core) - xxx" dialog select the "Empty" template project.
  5. Visual Studio is creating your project and downloading Nuget packages from the project.json specified by the Empty template.
  6. In Solution Explorer open Program.cs. (If you have worked with ASP.NET Core beta, RC1 or RC2 you will know that Program.cs is new in ASP.NET Core RTM and the Empty template writing out Program.cs confirms that your Visual Studio 2015 is at least update 3).
  7. In Program.cs we have the application entry point, Program.Main, which will use WebHostBuilder to create an ASP.NET Core App Host in which out ASP.NET Core App will be loaded together with Kestrel and whatever .NET Framework is targeted. To understand ASP.NET Core App Host, the application entry point and hosting using IIS as a remote proxy, see ASP.NET Core deploy to IIS tutorial.
  8. In Solution Explorer open Startup.cs.
  9. In Startup.Configure notice the last middleware :
    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Hello World!");
    });
    

    In ASP.NET Classic the request pipeline was build using HTTP Handlers & HTTP Modules, however in ASP.NET Core the request pipeline is build using middleware. IApplicationBuilder comes with 2 extension functions to add anonymous middleware inline : (well explained comparision here)

    • .Use(..) : add a middleware that pass the request to the next middleware.
    • .Run(..) : add a middleware that does NOT pass the request to the next middleware - that is: ending the request pipeline.

    So the Empty template will write out a terminating middleware that write "Hello World!" to the Response.

  10. In Visual Studio press Ctrl+F5 to compile the project and load the project url in a new browser tab.
  11. Enable static files

    Most web applications and also this QuickStart project will need the ability to serve static files like html, css, Javascript and image files. While most web servers serves static files as default, in ASP.NET Core we need to manually add this capability by adding a middleware with this capability to the request pipeline - Microsoft have already created such a middleware for us in the Microsoft.AspNetCore.StaticFiles Nuget package.

    1. Open project.json from Solution Explorer and add the Microsoft.AspNetCore.StaticFiles package to the dependencies section :
      "Microsoft.AspNetCore.StaticFiles""1.0.0"
      

      Then you save the project.json file, Visual Studio will automatically add the Microsoft.AspNetCore.StaticFiles Nuget package to the project

    2. If not already open then open the Statup.cs file from Solution Explorer and in the Startup.Configure function insert the following code snippet just before the "Hello World!" inline middleware :
      app.UseStaticFiles();
      

      UseStaticFiles is an IApplicationBuilder extension function defined within the Microsoft.AspNetCore.StaticFiles Nuget package. The UseStaticFiles extension function will add a middleware to the request pipeline that tries to map a request to a file on the filesystem and if found then load the file as a byte stream and append it to the response object and then closing the pipeline. If on the other hand the StaticFiles middleware cannot map the request to a file on the filesystem, the StaticFiles middleware will forward the request to the next middleware in the request pipeline - in our case the "Hello World!" inline middleware.

      Ok, we can now serve static files, eg. index.html.

  12. In Solution Explorer add a new file, index.html, to the wwwroot folder.
  13. In index.html add the following to the body tag :
    Hello Static File
    
  14. In Visual Studio press Ctrl+F5 to compile the project and load the project url in a new browser tab.

    We still see the "Hello World!' message - that is because the StaticFiles middleware could not map the request url, http://localhost:38084, to any file on the filesystem, so the StaticFiles middleware just pass the request to the next middleware, which is the inline "Hello World!" middleware.

  15. In the browser change the request from http://localhost:38084 to http://localhost:38084/index.html - you should now see the "Hello Static File" message (be aware that the port number 38084 will most likely be different for you).
  16. Enable default files

    Actually if no resource is specified in the url, we would like our application to load a default file, in our case the index.html file. So what we want is :

    • http://localhost:38084/index.html : load the index.html file.
    • http://localhost:38084 : also load the index.html file.

    1. In Startup.Configure insert the following code just BEFORE the app.UseStaticFiles :
      app.UseDefaultFiles();
      

      UseDefaultFiles is an IApplicationBuilder extension function defined within the same Microsoft.AspNetCore.StaticFiles Nuget package that also hosts the UseStaticFiles function. The UseDefaultFiles extension function will add a middleware to the request pipeline that if the request match a folder will search the folder for default.htm, default.html, index.htm & index.html and if any found then rewriting the url to file first found. DefaultFiles will then pass the request (rewritten or not) to the next middleware.

    2. Ok, index.html was loaded as default.

    Note that the order of the DefaultFiles & StaticFiles middlewares is important :
    app.UseDefaultFiles(); MUST come BEFORE app.UseStaticFiles();
    The reason is because the DefaultFiles middleware is only a url-rewriter, it does not actually load any file. For the StaticFiles middleware to load a file, the url exposed to StaticFiles MUST match a file on the filesystem - http://localhost:38084 does NOT match any file on the filesystem, it is the responsibility of DefaultFiles to rewrite that url to http://localhost:38084/index.html.

    CORRECT : DefaultFiles -> StaticFiles
    1. http://localhost:38084 is passed to DefaultFiles
    2. DefaultFiles can find wwwroot\index.html and therefore rewrites the url to http://localhost:38084/index.html and passes the rewritten url to the next middleware in our case StaticFiles.
    3. StaticFiles can map http://localhost:38084/index.html to wwwroot\index.html and therefore load the index.html and append it as a bytestream to the response object and terminates the pipeline.
    4. RESULT : we see Hello Static Page in the browser.
    WRONG StaticFiles -> DefaultFiles
    1. http://localhost:38084 is passed to StaticFiles.
    2. StaticFiles cannot map http://localhost:38084 to any files so StaticFiles will pass the request to the next middleware in the pipeline say DefaultFiles.
    3. DefaultFiles can find wwwroot\index.html and therefore rewrites the url to http://localhost:38084/index.html and passes the rewritten url to the next middleware in our case the inline "Hello World!".
    4. RESULT : we see Hello World! in the browser.

  17. Delete the 'Hello World!' middleware

    While the template written 'Hello World!' middleware initially helpted us to confirm the workings of the ASP.NET Core Application and in addition in a traditional MVC project have potential for handling 404 errors, in our Angular2 project it is no longer of any use nor potential use, so we delete it.

    1. Delete the "Hello World!" inline middleware.

  18. Add support for Angular2 url paths

    :

    An Angular2 application will have many url paths for clientside use only, however then a browser is loaded on a specific url, it will try to match that url to a resource, eg. in the official Angular2 tutorial you will have a url like this : http://some.domain/heroes. This url will clientside instruct the Angular2 client application to load a component called Heroes (displaying the View of the Heroes component) - everything happening on the client. However if you try to load the browser on the http://some.domain/heroes url, the browser will of course try to go to the server some.domain and ask for a resource matching the /heroes path, which typically will lead to an error 404 Not found.

    What we need is therefore for the server to acknowledge such a path as an Angular path and instead of 404 Not found returning the index.html (in which our Angular application lives) so that the browser will create the Angular application which in turn will use the /heroes path to load the Heroes component.

    There are basically 2 ways we can recognize requests as Angular2 paths, either writing some intelligent code guessing it or write an array of all our Angular paths and match that array with incoming requests. Here we will do the guessing solution :

    1. Insert the following inline middleware just BEFORE app.UseDefaultFiles(); in Startup.Configure:
      app.Use(async (context, next) =>
      {
      	await next(); // then hit by the INCOMING request do nothing but pass the request to the next middleware (DefaultFiles)
       
      	// Then hit by the OUTGOING request test if the application have matched any resource
      	// - if a resource was found (200 Ok) then do nothing but let the request travel further out
      	if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value))
      	{
      		// - if on the other hand a resource was not found (404 Not found) then assume it is an Angular url so ..
      		// .. reset the path and call await next(); to make the request INCOMING again ..
      		// .. the request will now be passed to DefaultFiles and then to StaticFiles serving index.html
      		// .. after StaticFiles the request will turn to OUTGOING eventually coming back here this time with 200 Ok. 
       
      		context.Request.Path = new PathString("/index.html"); // the Angular application is loaded by index.html
      		await next();
      	}
      });
      

      To understand what happens in the inline middleware above, it is necessary to understand how an incoming request is passed through the middleware request pipeline : an incoming request will be passed through middleware in the order the middleware is registered and passed from one middleware to the next middleware by calling await next();. Eventually a middleware will NOT call await next(); and the request will start travelling backwards hitting the same middlewares again but this time in opposite order. However a call to await next(); will always move the request forward, so if a request is travelling backwards (is outgoing) and a middleware calls await next(); the request will be sent forward (incoming), that is : in this case changing the direction of the request.

      Note that in the above code any url returning 404 Not found on it's way backwards will if without an extension be regarded an Angular path, however most resource requests will have an extension, eg. .map, .css, .js, .png etc. (so requests to missing resources will typically NOT be guessed to Angular paths).

    2. Test a typical Angular path :
      1. Press ctrl+F5 to start the application and open index.html in a browser.
      2. Rewrite the url to http://your.domain/heroes - you should still see that index.html is loaded in the browser : the request pipeline is now ready to support Angular paths.

ASP.NET Core is now well setup to support an Angular2 application.


Add Angular2 & Typescript to the ASP.NET Core project

  1. NPM logo Install NPM :

    NPM (Node Package Manager) is the recommended tool for installing Angular & Typescript. To install NPM we need to install NodeJS and NPM will be automatically installed with it. While in theory we could do without NodeJS, we cannot install NPM without installing NodeJS and in addition as we will also use Gulp (which runs in the NodeJS Javascript runtime), we need the NodeJS installed anyway.

    1. Download the latest LTS version (as of current 4.6.0) of NodeJS installer here
    2. Double click on the downloaded installer file to install NodeJS
    3. Open a command shell
    4. shell> node -v : confirm your version of NodeJS.
    5. shell> npm -v : confirm your version of NPM.

    With NPM installed we now have the tool to install Angular & Typescript - even from within Visual Studio, which is wonderfully integrated with NPM (they are also proud about it themselves).

  2. Back to the QuickStart project in Visual Studio.
  3. Angular logo Add Angular2 :
    1. In Solution Explorer add a package.json file to the project root folder.
    2. In the package.json file delete the template code and insert the following code instead : (the packages added here is from the angular.io recommended package set, which is a lot more than needed just for this tutorial)
      {
        "version""1.0.0",
        "name""quickstart",
        "dependencies": {
          "@angular/common""~2.0.2",
          "@angular/compiler""~2.0.2",
          "@angular/core""~2.0.2",
          "@angular/forms""~2.0.2",
          "@angular/http""~2.0.2",
          "@angular/platform-browser""~2.0.2",
          "@angular/platform-browser-dynamic""~2.0.2",
          "@angular/router""~3.0.2",
          "@angular/upgrade""~2.0.2",
          "angular-in-memory-web-api""0.1.13",
          "bootstrap""^3.3.7",
          "core-js""^2.4.1",
          "reflect-metadata""^0.1.8",
          "rxjs""5.0.0-beta.12",
          "systemjs""0.19.39",
          "zone.js""^0.6.25"
        }
      }
      

      All the @angular namespaced packages are pretty self explanatory and they make up a basic Angular module combination probably relevant for most real-world projects. The other packages are :

      • angular-in-memory-web-api : enables writing to and reading from an in-memory database instead of using a real web api to read & write to files or database. This package is used for demo code only, however I have included it in case you are going to continue with the Tour-of-Heroes tutorial on the angular.io website, there the angular-in-memory-web-api is used in Chapter 7 Http. Also note that I have removed the tilde '~' the specifying the 0.1.13 version, that is because version 0.1.14 & 0.1.15 results in an error Cannot find type definition file for 'core-js'.
      • bootstrap : a CSS library making it easy to beautify and theme web page elements.
      • core-js : enable ES6 in ES5 compliant browsers (this makes it possible for us to compile Typescript to ES6 instead of ES5). Note that before core-js existed we used es6-shim as a ES6 pollyfill, however Angular team now recommends core-js.
      • reflect-metadata : a polyfill for handling meta data (we will later set the Typescript compiler to emit such meta data).
      • rxjs : enable reactive programming and especially observables, read more here.
      • systemjs : a module loader (how to find pieces of Javascript and load them in the browser).
      • zone.js : provides execution contexts for asyncronous operations (Angular2 depends on zone.js), read more here.
    3. Then you press ctrl+s to save the package.json file, you can see in Solution Explorer that Visual Studio executes NPM in the same folder the package.json file is saved - NPM will download the packages specified in package.json and store them in a node_modules sub-folder.
    4. After NPM is finished downloading all the packages in package.json, click on "Show All Files" in Solution Explorer to see the node_modules sub-folder - if you open node_modules you will see a looong list of packages.

    Note that in NPM the version numbers follows the semantic versioning system having a :

    • Major release : incrementing the major release means some changes are backward incompatible.
    • Minor release : incrementing the minor release means all changes are backward compatible (typically adding new functionality or refactoring).
    • Patch relase : incrementing the patch release means only bug fixes.

    Following the semantic versioning in the package.json file we can prefix versions with either :

    • Nothing : we want the exact version number that we specify.
    • Caret '^' : we want the maximum available minor release for the specified major release.
    • Tilde '~' : we want the maximum available patch release for the specified major & minor release.

    Using caret & tilde for package version in package.json is not without problems : especially note the above mentioned problem with upgrading angular-in-memory-web-api from 0.1.13 to either 0.1.14 or 0.1.15, however in general it could probably easy turn out that 2 packages cannot work together anymore if one of them were upgraded.

    For more information about package.json, see the authoritative documentation.

  4. TypeScript logo Add Typescript :

    Like Angular, Typescript packages are also added using NPM. However, Typescript is a development tool, our final product does not depend on Typescript, we use Typescript only to develop the Javascript code, therefore in the package.json file we place Typescript packages in the devDependencies section (in our setup it does not really matter, however I feel better following the semantics of package.json).

    1. If not already open then open the package.json file from Solution Explorer.
    2. Add a new section to the package.json file called devDependencies (don't forget to add a comma after the dependencies section).
      "devDependencies": {
       
      }
      
    3. In the new devDependencies section add the following code :
      "typescript""^2.0.3"
      
    4. In Solution Explorer add a new folder called Scripts - Visual Studio assumes Typescript files to be located in folder called Scripts in the project root.
    5. In the Scripts folder add a Typescript configuration file called tsconfig.json.
    6. In the tsconfig.json file delete the template code and insert the following code instead :
      {
        "compileOnSave"true,
        "compilerOptions": {
          "noImplicitAny"false,
          "noEmitOnError"false,
          "removeComments"true,
          "sourceMap"false,
          "target""es6",
          "experimentalDecorators"true,
          "emitDecoratorMetadata"true,
          "module""commonjs",
          "moduleResolution""node",
          "outDir""../wwwroot/app/"
        }
      }

      Let's fast review each section in our tsconfig.json file :

      • compileOnSave : we need to set this property to get Visual Studio to activate the Typescript compiler each time we save a .ts file. If we omitted this property or sat it to false then we would have to choose Rebuild Project each time we do a small update to a .ts file (in Visual Studio 2013 we could set this property in project properties, however it does not exists in Visual Studio 2015 update 3).
      • compilerOptions section : (how the Typescript compiler transpiles from Typescript (.ts) to Javascript (.js))
        • noImplicitAny : whether to raise error on expressions and declarations that have no explicit type. If noImplicitAny=false any expression or declaration with no explicit type will by the Typescript compiler SILENTLY (no error message) be assigned the 'any' type. If noImplicitAny=true any expression or declaration with no explicit type will by the Typescript compiler VOCALY (there will be an error message) be assigned the 'any' type. False or true it will not affect whether the Typescript compiler will emit transpiled Javascript files.
        • noEmitOnError : whether to create Javascript files if there are errors. While it is popuplar to set this compiler option to true, you are doing yourself a disservice if you do because then compileOnSave=true and you save a Typescript file and there is a Typescript compiler error you will unfortunately get no compiler errors in Visual Studio Output window (so you don't know that there is an error) - you then run your browser to test your code, however no Javascript code was created by the Typescript compiler so the browser will be running your old Javascript code. Therefore setting noEmitOnError=true means it will take longer time for you to realize a compile error.

          It is true that if you choose Visual Studio -> Build -> Rebuild xxx then you will get Typescript compiler errors in the Output window and setting noEmitOnError=true would then be a great option, however it is very cumbersome to go through a full rebuild for each Typescript change you make.

        • removeComments : whether to remove all comments from the transpiled Javascript (both inline '//' & block '/**/' comments are affected).
        • sourceMap : whether to generate .map files. .map files makes it possible for the browser to map Javascript code runtime to the original Typescript code including runtime variable values. In the Appendix : How to debug Typescript we will change sourceMap from false to true.
        • target : specify the target Javascript version, either ES3, ES5 or ES6.
        • experimentalDecorators : whether to enable experimental support for ES7 decorators.
        • emitDecoratorMetadata : whether to emit ES7 meta data for decorated classes & methods - it seems Angular2 DI depends on it, read more here.
        • module : specify format of Javascript modules, there are many formats to choose between, however the commonjs format allows a convenient way to ID modules (making it easier to reference .html templates and related .css files within Angular Components). Read more about different module formats here.
        • moduleResolution : determines the first step in how modules are found, read more here here.
        • outDir : specifies there to put the transpiled Javascript files (if sourceMap=true then .map files will also be saved in the outDir folder).

        See full list of tsconfig.json compilerOptions here.

  5. TypeScript logo Add Typings :

    In Typescript we cannot just use a variable before it is declared. If you have a Javascript library to be loaded and executed in the browser, you can write some Javascript that uses the library, however you cannot write Typescript using that library because the Typescript compiler does not run in the context of the browser and knows nothing of what you will eventually load in the browser. Therefore to use a Javascript library in Typescript you MUST in your Typescript files declare at least the top variables of the Javascript library - while you can declare these variables inline in the Typescript files there you are using the variables, it is often preferable to declare them in their own .d.ts files, eg. MyTree.d.ts could hold declarations of variables from the MyTree.js library file. If you declare external Javascript library variables in their own .d.ts files such declarations are called Typescript definitions or typings (though often Typescript definitions or typings are referring to the .d.ts files themselves).

    Most of the packages added in the package.json dependencies section contains both Javascript (.js) files and Typescript definition (.d.ts) files, however the core-js package contains only Javascript files without the corresponding Typescript definitions and therfore core-js is NOT immediately available for Typescript - well, actually most of the core-js typings are available because the typescript package itself (which we just installed above) is coming with typings for different Javascript versions including ES6 and since core-js is an ES6 shim, the typescript package builtin typings for ES6 covers most of core-js.

    Since the typescript package comes pre-installed with typings for ES6 (in the node_modules\typescript\lib\lib.es6.d.ts file), we could probably in many an Angular2 application get away with NOT installing the core-js type definitions and in this QuickStart tutorial we definitely can. However for the sake of covering typings as well as for full Typescript support for core-js, we will install core-js typings anyway.

    There are numerous ways to install the core-js typings and had I wrote this tutorial a little earlier I would have used the typings tool, however as of Typescript 2 we can now use the much better NPM @types method. Here I will use the commandline (however we could just as easily just directly edit the package.json file) :

    1. Open a command shell and navigate to the QuickStart project folder.
    2. shell> npm install @types/core-js --save-dev : the new and easy way to install typings using the NPM tool. --save-dev will write the package to package.json devDependencies section (typings are in use only then developing, not part of the compiled Javascript product running in the browser nor part of any server-side code running on requests from the browser).
    3. Open the node_modules folder to confirm that @types/core-js have been added - the Typescript compiler can now pull core-js variable declarataions from node_modules\@types\core-js\index.d.ts.
    4. From Solution Explorer open package.json to confirm that we got @types/core-js added to the devDependencies section.

    Since our tsconfig.json sets the Javascript target to ES6, the Typescript compiler will include type definitions from lib.es6.d.ts, which will collide with the core-js types just installed resulting in a lot of Duplicate identifer errors. There are currently no good solution to the problem, however we can circumvent it by explicitly tell the Typescript compiler to use the ES5 typings instead of the ES6 - we should be able to rely on core-js typings to declare all ES6 specific types :

    1. From Solution Explorer open the tsconfig.json file.
    2. In tsconfig.json compilerOptions section insert the following parameter :
      "lib": [ "es5""dom" ]
      

      Note that then specifying the lib parameter, the Typescript compiler will use ONLY the typings specified, therefore we need to specify all the typings needed which inlucdes the dom typings (without which no HTML element whould be declared and therefore not available in our own nor any library Typescript code).

    3. Rebuild the QuickStart project - you should get no errors.



Automate build with Gulp

Gulp is a popular tool to automate some boring repetitive tasks in many a NodeJS workflow and also handy in a Visual Studio ASP.NET Core Angular2 Typescript workflow. While typically Gulp is used to compile SASS or LESS, minifying Javascript, optimizing images, refreshing the browser on file changes and many more, in this QuickStart tutorial we will only touch the surface using Gulp for moving a few files - which by itself is good but equally importantly will teach you the basics to get started on Gulp.

Technically Gulp is just a Javascript module written for NodeJS - Gulp and any Gulp plugin is run by the NodeJS Javascript runtime. Our job is to write another Javascript program in a file called gulp.js which will also be executed by the NodeJS Javascript runtime. What make the gulp.js file become a Gulp program is that in gulp.js we write code to load the Gulp module as a Javascript object and use the methods of that Gulp object - so now we have a Javascript file using the Gulp object and therefore we call the code in the Javascript file a Gulp program.

  1. Install Gulp

    Then we installed the core-js typings above, we used the commandline, however we could also just directly have edited the package.json file (letting Visual Studio call npm install for us automatically) - the result is the same. Then installing Gulp we also have these 2 different ways to choose from : commandline or direct package.json editing. The commandline approach is more flexible though, eg. we do not necessarily need an entry in the package.json file (though we chose to do so with --save-dev), but more importantly using the commandline it is possible to make a global install making the package available not only for current project but for all projects on this computer.

    Directly editing package.json is often the most easy solution (especially if we need to add many packages), but since using the commandline is more flexible, it is best if we are comfortable with both methods. Here I will show both methods - commandline and package.json editing : (you just choose one of them)

      • Install Gulp from the command prompt :
        1. Open a command shell in the project root directory, for me that is C:\inetpub\wwwroot\_sandbox\AngularVS\QuckStart\src\QuckStart
        2. shell> npm install gulp --save-dev :
          • --save-dev will add gulp to the devDependencies section of package.json (--save will add packages to the dependencies section).

          Ignore the warnings as long as there aren't any errors.

        3. shell> npm install del --save-dev :
        4. In Visual Studio Solution Explorer open the package.json file to confirm that Gulp & Del was indeed added to the devDependencies section.
      • OR Install Gulp by directly editing package.json :
        1. In Visual Studio Solution Explorer open the package.json file.
        2. In package.json insert the following code at the top of the devDependencies section :
          "del""^2.2.2",
          "gulp""3.9.1",
          

          Then you press save on the project.json file Visual Studio will automatically execute npm install in this case installing the Gulp & Del packages.

          Again note that Gulp is a development tool, the projects final output does NOT have a dependency on Gulp - that's why we use the devDependencies section.

      In addition to the Gulp package we also installed a package called Del (del a newer alternative to the old rimraf package), which we will use in a Gulp task to easy delete some files. Not only are there many Gulp plugins helping expanding the capabilities of Gulp, but since gulp.js is just a Javascript program executed in the NodeJS Javascript runtime we can use any NodeJS package in gulp.js greatly expanding what can be achieved with Gulp - in this tutorial we will not use any Gulp plugin but we will demonstrate how a non-related NodeJS program, here the Del program, can be used by Gulp.

    1. Open the node_modules folder and scroll down until you find the Gulp folder to confirm that the Gulp package have been downloaded.

      Gulp itself have a huge load of dependencies totally filling up the node_modules folder. If we had installed Gulp globally, we would have avoided all these many NPM packages in the local node_modules folder, however imagine you work in a team and half a year from now a new team member is added. The new team member will download the project code from your source control and will have to install Gulp on his dev machine, but that will probably be a newer version of Gulp with the potential to break the Gulp code resulting in errors. By installing Gulp locally and saving the version to package.json, the new team member will get the same Gulp version as all others.

  2. Configure Gulp

    The meaning of the Gulp program we are about to write in a gulp.js file is to setup tasks that we want Gulp to run for us - we therefore call the gulp.js file a Gulp configuration file.

    In gulp.js we will first need to load the Gulp Javascript object into a local Javascript variable by convention called gulp, like this :

    var gulp = require('gulp');// NodeJS runtime defines require which can find and load gulp from node_modules.

    The Gulp object have various functions, the most important being (we will use all these) :

    • .task() : a function that defines a named group of things to do (typically each action have it's own task though).
    • .src() : a function that creates a stream of files.
    • .dest() : a function that writes a stream of files to disk.
    • .pipe() : a function that can pass the output of one command as input to another command (like the linux | symbol).

    In Gulp all actions are defined within tasks and we can then execute one or more tasks to get something done. A typically Gulp task will select some files, then pipe these files through one or more plugins for the purpose of creating a new set of files (eg. obfuscating some Javascript files or compile SASS files to a CSS file) and lastly write the result files to disk, something like this :

    gulp.task('myTask'function () {
    	var stream = gulp.src('sourceFiles')
    		    .pipe(aGulpPlugin())
    		    .pipe(gulp.dest('targetLocation'));
     
    	return stream;
    });

    Note the gulp.task signature, there are 2 different :

    • gulp.task('task-name', anonymous-function) : the signature used in the above example.
    • gulp.task('task-name', ['array', 'of', 'tasks', 'that', 'this', 'task', 'depends', 'on'], anonymous-function) : so we can specify task dependenices, eg. :
      • gulp.task('task3', ['task2'], function(){//do something}); : run task2 before starting task3.

    With that basic introduction it is time to write the gulp.js file :

    1. In Visual Studio Solution Explorer create a new gulp configuration file called gulp.js in the project root folder.
    2. Delete the gulp.js template code and insert the following code instead :
      /// <binding AfterBuild='copyApp' />
       
      // Load all libraries that we need into local variables (in this QuickStart we only want 2).
      var gulp = require('gulp'); // we of course need gulp itself.
      var del = require('del'); // del enable us to delete files (del replaces the old rimraf).
       
      // Defining some handy paths
      var paths = {
      	npmInstall: "./node_modules/",
      	npmClient: "./wwwroot/libs/",
      	appDevelopment: ['./Scripts/**/*.html''./Scripts/**/*.css'],
      	appClient: "./wwwroot/app/",
      };
       
      // To make all the Angular application relevant packages accessible to the browser, we need to copy
      // -- the packages from node_modules to somewhere inside of wwwroot, here wwwroot\libs.
      // ('**' means all folders and their subfolder trees)
      gulp.task('copyLibs', ['clean'], function () {
      	gulp.src(paths.npmInstall + '@angular/**/*.js').pipe(gulp.dest(paths.npmClient + '@angular'));
      	gulp.src(paths.npmInstall + 'core-js/**/*.js').pipe(gulp.dest(paths.npmClient + 'core-js'));
      	gulp.src(paths.npmInstall + 'zone.js/**/*.js').pipe(gulp.dest(paths.npmClient + 'zone.js'));
      	gulp.src(paths.npmInstall + 'systemjs/**/*.js').pipe(gulp.dest(paths.npmClient + 'systemjs'));
      	gulp.src(paths.npmInstall + 'reflect-metadata/**/*.js').pipe(gulp.dest(paths.npmClient + 'reflect-metadata'));
      	gulp.src(paths.npmInstall + 'rxjs/**/*.js').pipe(gulp.dest(paths.npmClient + 'rxjs'));
      	gulp.src(paths.npmInstall + 'angular-in-memory-web-api//**/*.js').pipe(gulp.dest(paths.npmClient + 'angular-in-memory-web-api'));
       
      	//Eg. for the systemjs packages :
      	// -- 1) gulp.src() :  in ./node_modules/systemjs/ from all directories and their subtrees (**) select all Javascript files (*.js).
      	// -- 2) gulp.pipe() : send that file collection to something - in our case send the file collection to gulp.dest().
      	// -- 3) gulp.dest() : take the incoming file collection and write it to ./wwwroot/libs/systemjs folder.
       
      });
       
      // While of no relevance in this QuickStart tutorial, a real Angular application will have many .html templates and related .css files
      // -- these files are not automatically copied by the Typescript compiler and
      // -- therefore needs to be separately copied to the wwwroot\app folder.
      gulp.task('copyApp'function () {
      	gulp.src(paths.appDevelopment).pipe(gulp.dest(paths.appClient));
      });
       
      // It will happen that we want to start on a fresh in the wwwroot folder or areas of the wwwroot folder
      // -- here we make a task so it is easy to delete all the Angular application relevant packages.
      gulp.task('clean'function () {
      	return del([paths.npmClient]);
       
      	// 'clean' is a dependency task (of copyLibs), it is a good habit to return from dependency tasks
      	// -- not because it is necessary as a signal to Gulp that the task have finished
      	// -- but because that is the only way to transfer a stream to the next task.
      });
       
      // We don't need this 'default' task especially then using Visual Studio, however it is common to
      // start off the main task chain using a task called 'default', so I include a 'default' task here just for shows.
      // (if you write gulp at the command prompt, the gulp command will look for a task named 'default' and start it if it finds it).
      gulp.task('default', ['copyLibs'], function () {
      	// we could place some code here as well.
      });

      Press ctrl+s to save the gulp.js file.

      Note that Visual Studio supports binding a few Visual Studio events to any of the tasks defined in gulp.js. Especially the Visual Studio Clean & AfterBuild events looks attractive, unfortunately for me it have turned out that the event binding (including Clean) typically slows down the Visual Studio build time too much to be worth it. However, in the above gulp.js file I have added a small event binding just for shows - the event binding instruction must start with 3 slashes, '///', and be located at the very top of gulp.js for Visual Studio to pick it up. In the above gulp.js file the Visual Studio AfterBuild event is bound to the Gulp 'copyApp' task so that immediately after each time Visual Studio build the QuickStart project all Angular .html templates and associated .css files are copied to the wwwroot\app folder - this ensures that then you press ctrl+F5 in Visual Studio and the browser load the Angular application, that all of the Angular application files are available to the browser. Instead of manually writing the binding code, you can use the Task Runner window to manage your bindings :
    3. In Solution Explorer right-click on the gulp.js file and select Task Runner Explorer from the short-cut menu.
    4. In the Task Runner window press the refresh icon to get Visual Studio to parse gulp.js and show all our Gulp tasks in the Task Runner window : default, clean, copyApp & copyLibs.
    5. Right click on the copyLibs task and choose Run.
    6. Confirm that all the libraries are copied to wwwroot\libs\ - this is all we want in this QuickStart tutorial.

While copying the Angular relevant packages is all we want to do in this QuickStart tutorial, the above gulp.js file is a good starting point for a real Angular application and especially the above gulp.js is a perfect match for the Tour of Heroes tutorial on angular.io should you try out that tutorial using Visual Studio.

Here are 2 good links to learn more about Gulp :




Write the Angular2 Hello World code

With an ASP.NET Core project prepared, Angular & Typescript installed and Gulp automation configured at last we can start doing some Angular code.

  1. Create the root component - AppComponent

    Components are the basic building blocks of Angular 2 and are in control of a portion of the screen - a view - through its associated template. All Angular 2 applications must have a root component by convention called AppComponent.

    To specify that a class is a component, we decorate the class with the Component decorator.

    1. Create a new file called app.component.ts in the Scripts folder.
    2. Add the following code to the app.component.ts file :
      import { Component } from '@angular/core';
       
      @Component({
      	selector: 'my-app',
      	template: '<h1>My First Angular App</h1>'
      })
      export class AppComponent { }

      The Component decorator have 2 properties :

      • selector : Angular will scan the host file (in our case index.html) for a tag with the selector value - here <my-app>..whatever.. </my-app> and replace it with the template value.
      • template : the template value is to replace the tag in the host file matched by the selector value (here the template is very simple, however it can be very complex).

      The AppComponent is exported so that we can potentially import it in a module.

  2. Create the root module - AppModule

    Angular 2 applications are on the highest level composed of modules and all Angular 2 applications must have at least a root module by convention called AppModule.

    To specify that a class is a module, we decorate the class with the NgModule decorator.

    1. Create a new file called app.module.ts in the Scripts folder.
    2. Add the following code to the app.module.ts file :
      import { NgModule } from '@angular/core';
      import { BrowserModule } from '@angular/platform-browser';
       
      import { AppComponent } from './app.component';
       
      @NgModule({
      	imports: [BrowserModule],
      	declarations: [AppComponent],
      	bootstrap: [AppComponent]
      })
      export class AppModule { }

      The BrowserModule is added to the AppModule imports array, otherwise the application cannot run in a browser.

      The AppModule is exported so that we can potentially import it in another module.

  3. Create the bootstrap code - main.ts

    1. Create a new file called main.ts in the Scripts folder.
    2. Add the following code to the main.ts file :
      import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
      import { enableProdMode } from '@angular/core';
       
      import { AppModule } from './app.module';
       
      //enableProdMode();
       
      const platform = platformBrowserDynamic();
      platform.bootstrapModule(AppModule); 

      The main.ts file have 3 imports :

      • platformBrowserDynamic : to bootstrap an Angular 2 application we need to pass the root module, AppModule, object to some Angular .bootstrapModule() function and since this QuickStart project is intended to run in a browser we need to use the .bootstrapModule() function defined in the platformBrowserDynamic module.
      • enableProdMode : this module allows us to change the Angular application from development mode to production mode by calling enableProdMode(). However while we develop we should out-comment enableProdMode() to better identify errors.

        The difference between development & production mode is that production mode is optimized for speed especially in production mode Angular change detection is only running through the component tree once (hierarchy order top-down) therefore not catching child caused changes to parents : In this case, Angular throws an error, since an Angular application can only have one change detection pass during which all change detection must complete., which means we can only be notified of such errors in development mode. (That's the only difference I know of, however there should be other differences as well).

      • AppModule : well we cannot pass AppModule to .bootstrapModule() if we do not import AppModule.
  4. Compile the Typescript code

    The main flow of the Typescript compiling process is to take a group of .ts files and output a group of .js files.

    Typescript compilation flow
    In tsconfig.json we specified a host of paramters to the Typescript compiler, especially :
    • outDir : which we sat to '../wwwroot/app/ to get the Typescript compiler to save the transpiled .js files in wwwroot\app\.
    • sourceMap : which we sat to false to avoid the Typescript compiler to create .map files in addition to the .js files.

    Ok, let's build and see what happens :

    1. In Visual Studio choose "Rebuild QuickStart" from the Build menu.
    2. In Solution Explorer open wwwroot, you should see an app folder. Open the app folder, you should see 3 Javascript files, one for each Typescript file. There should be no .map files at this time.

    While we now have coded and compiled the Angular application, the application cannot yet be loaded by the browser - we still need to configure module loading and to update the index.html file to load the Angular application.

  5. Update index.html to load the Angular application

    The Angular framework, the supporting libraries and our Angular application code all needs to be loaded by the browser, that is : the loading needs to start from the index.html file.

    Some of the framework libraries, the supporting libraries and our Angular application code can be loaded on demand, however some we will load directly from the index.html. All modules that are loaded by the Angular application code will be loaded on demand, the rest will load directly from the index.html file :

    • Shims : core-js
    • Some Angular dependencies : Zone & Reflect-Metadata
    • Module loader : SystemJS

    Ok, so let's update our index.html from showing "Hello Static Page" to support an Angular application and to startup our Angular application :

    1. From Solution Explorer open the index.html file in the wwwroot folder.
    2. Remove the old index.html code and insert the following code instead : (based on the angular.io QuickStart tutorial)
      <!DOCTYPE html>
      <html>
      <head>
      	<title>ASP.NET Core Angular 2 QuickStart</title>
      	<meta charset="UTF-8">
      	<meta name="viewport" content="width=device-width, initial-scale=1">
      	<link rel="stylesheet" href="styles.css">
       
      	<!-- 1. Load libraries -->
      	<script src="libs/core-js/client/shim.min.js"></script>
      	<script src="libs/zone.js/dist/zone.js"></script>
      	<script src="libs/reflect-metadata/Reflect.js"></script>
      	<script src="libs/systemjs/dist/system.src.js"></script>
       
      	<!-- 2. Configure SystemJS -->
      	<script src="systemjs.config.js"></script>
       
      	<script>
      		System.import('app').catch(function (err) { console.error(err); });
      	</script>
      </head>
      <body>
      	<my-app>QuckStart Loading...</my-app>
      </body>
      </html>
    3. Understanding index.html

      • <meta name="viewport" content="width=device-width, initial-scale=1">
        

        Set window width to the maximum width supported by the device and set the initial zoom factor to 1 (no zoom). This meta-tag should only be used if the design is specifically made for responsiveness, eg. if you have a column set to 1,000 px fixed width (like this tutorial page), then you should NOT use this meta-tag.

      • <link rel="stylesheet" href="styles.css">
        

        The styles.css reference is for familiarity and while we don't have any styles.css file in this tutorial, any real project would have a styles.css file.

      • <script src="libs/core-js/client/shim.min.js"></script>
        <script src="libs/zone.js/dist/zone.js"></script>
        <script src="libs/reflect-metadata/Reflect.js"></script>

        core-js, Zone & Reflect-Metadata are libraries necessary both for the Angular Javascript framework and for our Angular Javascript code, there is nothing won by loading them on-demand.

      • <script src="libs/systemjs/dist/system.src.js"></script>
        

        SystemJS is the module loader library that allows us to load Javascript code on-demand consistent with the Javascript library references transpiled by the Typescript compiler. In addition we will also use SystemJS to load our Angular application.

      • <script src="systemjs.config.js"></script>
        

        systemjs.config.js is a Javascript file that we use to configure the module loader especially how to match module names in the Javascript code to filenames containing these modules. We will write systemjs.config.js in the next section.

      • <script>
        	System.import('app').catch(function (err) { console.error(err); });
        </script>

        SystemJS also exposes a global object called System, which have an .import() function that can load Javascript code in different ways, here the 'app' argument will match an app-property in systemjs.config.js starting up our Angular application.

      • <my-app>QuckStart Loading...</my-app>
        

        The HTML markup MUST contain a tag in which an Angular View can be displayed. In Angular Views are owned by Components and it is also the Component that specifies in which HTML tag the Components View should be displayed - a Component will use the selector property of its @Component meta description to specify that HTML tag. Say we have the following Component code :

        @Component({
        	selector: 'my-app',
        	template: '<h1>My First ASP.NET Core Angular 2 App</h1>'
        })
        export class AppComponent { } 

        , then Angular will search the index.html file for <my-app> ..whatever.. </my-app> and replace that tag with the Components View (the View is specified by the template property of the @Component meta description).

    .

  6. Configure module loading - systemjs.config.js

    Any Angular application will do a lot of module loading. To load modules in Typescript we use import from, which by the Typescript compiler (tsc) will, then setting the Typescript compilerOptions.module='commonjs' (which indeed we do in this tutorial), be transpiled to Javascript require(), here are some examples :

    Ex Typescript tsc Javascript JS file location
    1 import { NgModule } from '@angular/core'; typescript compiler icon const core_1 = require('@angular/core'); wwwroot\libs\@angular\core\bundles\core.umd.js
    2 import { AppComponent } from './app.component'; const app_component_1 = require('./app.component'); wwwroot\app\app.component.js

    Note that while require is built into NodeJS require is NOT built into the browser - instead we need to load some Javascript into the browser that defines the require function, in our case it is SystemJS.

    The Javascript require() function will need a way to find the file in which the Javascript module is coded, eg. in example 1 the @angular/core module is coded in the wwwroot\libs\@angular\core\bundles\core.umd.js file.

    The main purpose of systemjs.config.js is to tell SystemJS how the require function map a module name to the file in which the module is coded, eg. for the 2 examples above :

    • '@angular/core' MUST MAP TO wwwroot\libs\@angular\core\bundles\core.umd.js
    • './app.component' MUST MAP TO wwwroot\app\app.component.js

    Ok, let's create the systemjs.config.js file :

    1. Add a new file called systemjs.config.js to the wwwroot folder.
    2. In the systemjs.config.js file insert the following code :
      (function (global) {
      	System.config({
      		paths: {
      			// paths serve as alias
      			
      			//'npm:': 'https://unpkg.com/' // external path to libraries for CDN (Content Delivery Network) delivery.
      			'npm:''libs/' // custom local path to libraries (I like this better).
      		},
       
      		// map tells the System loader where to look for things
      		map: {
      			//System.import('identifier') will try to match any property name in the map object
      			//, so System.import('app') will map the app property.
      			//The map property must specify the location of our Angular application
      			//, so app: 'app' means that our application is located in wwwroot\app.
       
      			//Say that our application was located in wwwroot\dist
      			//, then we would need to have app: 'dist'
      			//, and still System.import('app').
      			app: 'app',
       
      			// angular bundles
      			'@angular/core''npm:@angular/core/bundles/core.umd.js',
      			'@angular/common''npm:@angular/common/bundles/common.umd.js',
      			'@angular/compiler''npm:@angular/compiler/bundles/compiler.umd.js',
      			'@angular/platform-browser''npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      			'@angular/platform-browser-dynamic''npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      			'@angular/http''npm:@angular/http/bundles/http.umd.js',
      			'@angular/router''npm:@angular/router/bundles/router.umd.js',
      			'@angular/forms''npm:@angular/forms/bundles/forms.umd.js',
      			// other libraries
      			'rxjs''npm:rxjs',
      			'angular-in-memory-web-api''npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js'
      		},
       
      		// packages tells the System loader how to load when no filename and/or no extension
      		packages: {
      			//This app must match the map.app so that we can specify the main entry point: ./main.js
      			//, so if we had map.myApp then we would need to have packages.myApp.
      			app: {
      				main: './main.js',
      				//any module argument in code belonging to the wwwroot\app folder must be postfixed .js to match the filename.
      				defaultExtension: 'js'
      			},
      			rxjs: {
      				defaultExtension: 'js'
      			}
      		}
      	});
      })(this);

    The above systemjs.config.js consists of a single self-executing anonymous function in which we call System.config({ConfigurationObject}) to set various configuration properties of the System object (I don't know why the official Angular 2 tutorial embed the System.config({..}) call in a self-executing anonymous function, however self-executing anonymous functions seems to be wildly popular these days).

    Our configuration object consists of 3 property objects :

    • paths : in the paths object we can define some aliases to help building paths in the map object, we define only one "npm:" with the value of "libs\" so that we can prefix paths in the map object with "npm:" and get it normalized to "libs\". The reason for doing this is because it is more nice to read and more easy to change.
    • map : in the map object we specify the application folder of our application as well as a lot of static module mappings for various bundles of the Angular framework, a static mapping for the angular-in-memory-web-api module and an entry path for various rxjs modules.
    • packages : in the packages object we can define a package for any of the identifiers under map and we have defined 2 packages :
      • app : for the application hosted in the wwwroot\app folder we set the application entry point to ./main.js and in addition specify that any modules in that application have a file extension of .js - this means that SystemJS is able to match the our transpiled module names to the filenames in which the modules are coded (we code a module in a file with the same name as the module but with an .js extension).
      • rxjs : rxjs is not an application so it have no entry point, however it does consist of multiple modules - to reach each module we would again pass the module name as an argument to the require() function (transpiled from the Typescript import statement) and SystemJS will need to add .js to the module name to match the rxjs module file in which the module is coded.

      Note that there is no need to specify any defaultExtension for any of the @Angular bundles because the transpiled module names are mapped directly to Javascript files in map object.

    System.config() will pass these 3 property objects to initialize the corresponding properties on the System object like this : (System.config() will do some extra stuff on the property objects before using them for initialization so that is why we do not set the properties directly)

    • System.paths = paths;
    • System.map = map;
    • System.packages = packages;

    From the above systemjs.config.js it is easy to see how SystemJS map the @angular/core to the correct file : we have simply hardcoded the path. However mapping of ./app/app.component is less intuitive - let's break it down :

    • The './app/' is simply a path starting in the base directory wwwroot giving us wwwroot\app\.
    • In the packages.app property (setting the rules for the wwwroot\app\ folder) we specify that module names should be postfixed .js - this gives us app.component.js.
    • The whole path to the app.component module is therefore wwwroot\app\app.component.js.

    If this explanation of systemjs.config.js why & how is not enough for you, you can read more at the authoritative SystemJS documentation.

  7. Understanding the Angular startup process :

    Note that for the Javascript files below I show the Typescript code before transpilation (not the actual Javascript code).

    1. index.html : System.import('app') is executed, which maps to systemjs.config.js map.app for which we have defined packages.app in which we specify to execute main.js.
      System.import('app').catch(function (err) { console.error(err); });
      
    2. main.js : executes AppModule
      platform.bootstrapModule(AppModule);
      
    3. app.module.js : executes AppComponent
      @NgModule({
      	...
      	bootstrap: [AppComponent]
    4. app.component.js : specifies the HTML tag (selector property) to be replaced with the View (template property)
      @Component({
      	selector: 'my-app',
      	template: '<h1>My First ASP.NET Core Angular 2 App</h1>'
      
  8. Test the application

    1. In Visual Studio press ctrl+F5 to load the asp.net core application in a new browser tab - index.html should be loaded (qua the DefaultFiles & StaticFiles middlewares we added to the request pipeline in Startup.Configure()) and index.html should load our Angular 2 application in the <my-app> tag.

    CONGRATULATIONS - you should now have an ASP.NET Core Angular2 project running.




Appendix : How to debug Typescript

While the browser is executing Javascript not Typescript, we can use .map files to tell the browser how to map Javascript code to Typescript code thereby stepping through our Typescript files one Javascript instruction at a time.

  1. Write a small button action to demonstrate the debugging.
    1. From Solution Explorer open app.component.ts
    2. Make app.component.ts look like this : (adding the html button and the testDebug function)
      import { Component } from '@angular/core';
       
      @Component({
      	selector: 'my-app',
      	template: '<h1>My Second {{tutorial}}</h1><button (click)="testDebug()">Test Debug</button>'
      })
      export class AppComponent {
      	tutorial = 'ASP.NET Core Angular2 App';
       
      	testDebug(): void {
      		alert('breakpoint on this');
      	}
      } 
  2. Change Typescript compiler to emit .map files :
    1. From Solution Explorer open tsconfig.json
    2. Change sourceMap from false to true, the full tsconfig.json should look like this :
      {
        "compileOnSave"true,
        "compilerOptions": {
          "noImplicitAny"false,
          "noEmitOnError"false,
          "removeComments"true,
          "sourceMap"true,
          "target""es6",
          "experimentalDecorators"true,
          "emitDecoratorMetadata"true,
          "module""commonjs",
          "moduleResolution""node",
          "outDir""../wwwroot/app/",
          "lib": [ "es5""dom" ]
        }
      }
      

      For the Chrome browser to be able to use the .map files, they must be in the same location as the .js files. Luckily the Typescript compiler will send the .map files to outDir location along with the .js files - this means that we do NOT need to setup any Gulp task to move the .map files.

    3. In Visual Studio choose Rebuild QuickStart to be sure to activate the Typescript compiler.
    4. In Solution Explorer open wwwroot\app folder and check that the .map files was created and located correctly.
  3. Be sure that Visual Studio will load the Chrome browser.
  4. Press ctrl+F5 to compile and load the index.html (Angular2 application) in the Chrome browser.
  5. In Chrome press F12 to open DevTools and then select the Sources tab.
  6. Open DevTools settings and be sure that Javascript source maps is enabled.
  7. Add your Typescript folder to DevTools workspace :
    1. In the Sources left most pane right-click on empty space and select "Add folder to workspace" from the popup menu.
    2. Select the QuickStart Scripts folder (there you have your Typescript files).
    3. Give DevTools full access to the Scripts folder.
    4. The Scripts folder should now be added to the Chrome DevTools workspace.
    5. Refresh Chrome :
      1. Close all tabs that have the QuickStart application loaded.
      2. Close Chrome.
      3. In Visual Studio press ctrl+F5 top open the index.html in a Chrome tab.
      4. In Chrome press F12 to open the DevTools again and be sure the Sources tab is selected.
  8. Start debugging :
    1. From DevTools->Sources left most pane open the Scripts\app.component.ts file.
    2. In the app.component.ts code set a breakpoint on the alert statement by clicking on the line-number (in my case 11).
    3. Click the "Test debug" button.
    4. And there you have it - execution is paused in debugger. In a real application you would now be able to step through each instruct examining flow and variable values.
    5. Either click the Resume script icon or press F8 to resume execution.
    6. After resuming execution the alert should display.



Appendix : A few CLI commands

  • NPM @types : ideally the new way to install typings.
    • shell> npm install @types/core-js --save-dev --global :
      • @types : subfolder of node_modules into which all typing packages are installed.
      • core-js : the typings package to download.
      • --save-dev : add an entry for the package in package.json (in the devDependencies section).
      • --global : this package is globally available on this computer.
    • shell> npm uninstall @types/core-js --save --global : note that if you installed only for a specific project, you should also uninstall without the --global switch.
  • Typings commands : ideally on it's way out in favour of NPM @types, however NPM @types may not yet work for as many packages as the Typings tool.
    • shell> typings install dt~core-js --save --global :
      • dt : specifies to download from DefinitelyTyped.
      • core-js : the typings package to download.
      • --save : add an entry for the package in typings.json (a specific Typings configuration file soon obsolete).
      • --global : this library is on the (browsers) global (window) object (a very different meaning of global comapred to NPM @types).
    • shell> typings uninstall core-js --save --global :



Appendix : Configuration files

In an ASP.NET Core Angular project there are typically quite a lot of configuration in quite a lot of files, here is a list of these files :

Technology Configuration file Main purpose
ASP.NET Core project.json Specifies ASP.NET Core package dependencies like eg. StaticFiles, WebServer to use, MVC, Logging etc.
ASP.NET Core appsettings.json Specifies application specific configuration like eg. database connection, social login secrets etc. While not in use in this QuickStart tutorial, appsettings.json will be in use in any real project.
NPM package.json Specifies package dependencies of our Angular program like eg. Angular libraries, polyfills, various tools etc.
TypeScript tsconfig.json Configures Typescript compiler behaviour like eg. Javascript target version, whether to emit decorations etc.
TypeScript typings.json Specifies where to find typings for Typescript libraries (packages) that does not themselves embed typings. This configuration file is about to be obsolete (in favor of a streamlined NPM @types method), however it is still heavily used and you will also see it in the official angular.io QuickStart tutorial.
Gulp gulpfile.js Specifies automation tasks like eg. copying various program files to browser accessible folders.
Angular systemjs.config.js Specifies how Angular find modules.



Appendix : Common errors and solutions

  1. Cannot find type definition file for 'core-js'.

When : I got this error even if I had already installed type definitions for core-js, it turned out that angular-in-memory-web-api version 0.1.14 & 0.1.15 both have a bug that results in this error.

Solution : The best solution to this problem is to use the older 0.1.13 version of angular-in-memory-web-api :

  1. From Solution Explorer open your package.json file.
  2. Change the version of angular-in-memory-web-api package to 0.1.13 without using tilde '~' or caret '^'.

The angular-in-memory-web-api "cannot find type definition file for 'core-js'" error is discussed here.

  1. Duplicate identifier 'PropertyKey'.

Reason : PropertyKey is declared more one once - for me it happened then installing both Typescript and @types/core-js in the node_modules folder.

Explanation : Both Typescript & @types/core-js declares a global scoped variable called PropertyKey :

  • declare type PropertyKey = string | number | symbol; in node_modules\@types\core-js\index.d.ts line 21.
  • declare type PropertyKey = string | number | symbol; in node_modules\typescript\lib\lib.es6.d.ts line 4133.

The Typescript compiler can of course not accept 2 declarations of the same variable in the same scope and therefore have to emit the
Duplicate identifer error.

Solution : One of the duplicate declarations have to be excluded from the compiler. The solution I ended up with was to explicitly tell the Typescript compiler which typings to use from the typescript package by using the lib parameter of the tsconfig.json compilerOptions section :

  1. Open tsconfig.json and add the lib parameter :
    "lib": [ "es5""dom" ]
    

    Since we set the target parameter to es6, the Typescript compiler will default load typings from the lib.es6.d.ts file, however then using the lib parameter the Typescript compiler will load ONLY the typings we tell the compiler to load - in our case es5 and dom typings. Now we have all the standard es5 typings, all the extra es6 typings from core-js and we have all the dom typings (which we of course need, otherwise no Typescript library would be able to write code to manipulate the DOM).




Comments

You can comment without logging in
 
 B  U  I  S 
Words: Chars: Chars left: 
 Captcha 
 Nickname
Facebook
    
Tom
--------------
      report  reply  
nice, really appreciate the detailed explanations
Rasmus
User type : Admin
Register : 2012-Dec-21
Topics : 0
Replies : 108
--------------
      report  reply  
Thanks a lot Tom.
I was and still am in doubt whether I have made it too detailed.

web fiddler by nature

James
--------------
      report  reply  
It's better to have too much information and skip past them than to not have enough information and left wanting more.
rood
--------------
      report  reply  
Fantastic job ! Thanks!
wolfre
User type : Standard
Register : 2017-Apr-26
Topics : 0
Replies : 2
--------------
      report  reply  
Good post very very Thanks man!!!!!

bro god bless u :)
yo bro
wolfre
User type : Standard
Register : 2017-Apr-26
Topics : 0
Replies : 2
--------------
      report  reply  
one Error comment

IE 11 "systemjs syntaxError"

"main.ts" add "/// <reference path= "../node_modules/typescript/lib/lib.es6.d.ts" />"


yo bro



click to top