Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Asp.Net Core RC2 web-api and Angular 2 Cross Platfrom - Getting Started on Linux/Mac/Windows & VSCode Part-1

5.00/5 (4 votes)
8 Jun 2016CPOL18 min read 25.1K   109  
Cross platform Asp.Net Core RC2 with Angular 2 - getting started on Linux/Mac & VSCode - Part 1

Introduction

Asp.Net Core Rc2 is available and so is Angular 2 RC1. In this article ) and leverage the Cross Platform feature. We would be using Visual Studio Code which is available cross platform. (I am developing this app primarily on Mac or Linux Virtual Machine. And I do not have Visual Studio installed on these machines. But Visual Studio Code with its respective extensions is pretty much a functional editor for Linux/Mac for me. )

Let's get started hands on to get a simple CRUD application working with these technologies.

What is in the contents below?

This part (Part 1) of the article focusses primarily on Setting up our system and App. We want to refine the app and look into the details of the application in the next part (Part 2 ) of the series. In the contents below we focus upon installation of necessary tools and setting up the basic structure of our application. By the end of this article we should have a basic cross platform Asp.Net Core RC2 and Angular 2 CRUD Application working.

The article is divided into 3 sections:-

  • Section 1 describes basically how to use of the code cloned from GitHub repository or downloaded from Zip.
  • Section 2 describes the steps to follow in order to install the necessary tools - basically getting our system ready.
  • Section 3 describes the actual setup of our application. Section 3 is divided into two Sub Sections.
    • The 3.1 describes the setup of Asp.Net Core RC2 Web-Api (the server side).
    • Section 3.2 describes the setup of UI portion of our application.

We also take a quick look at different tools and technologies we are using and the reason behind using them.

 

Background

Microsoft Asp.Net Core RC2 was released in mid-may 2016.  Asp.Net Core 1.0 RTM is expected to be available by end June 2016. Angular 2 RC 1 is also available now. In this series of articles we try our hands on making them work together.

1 Using the code

If the Necessary tools (Dotnet Core, NodeJs, TypeScript) mentioned in below section are not installed on your machine please install install the tools first from information in respective sections.

Watch me perform the compile of the app in this video. Here I run the commands mentioned below to compile the app.

To grab the source code directly from github into a directory of your choice (testapp directory in below command) use the following commands:-

mkdir testapp
cd testapp


git init
git remote add origin https://github.com/amitthk/angdnx.git
git pull origin master

Once the source is downloaded (or contents of the Zip downloaded from above are unzipped) to a directory we should be able to run the app after installing the necessary packages using following commands:-

#Restore dotnet packages
dotnet restore

#Move in to the Web Project directory
cd angdnx

#Install NodeJs Packages from the defined package.json
npm install

#Build the UI using gulp (for full package only)
gulp

#Run the application
dotnet run

Note: If build:ts task in "gulp" command above is giving error on the first run. Please compile the typescript files for the first time using Typescript command line "tsc" from \angdnx\app folder using the command below.  After that gulp browserify will be able to pick the contents correctly . Please run the gulp command after tsc to run the default gulp task:-

cd \angdnx\app
tsc

cd ..
gulp

dotnet run

After the above commands Dotnet should respond with the message "Now listening on: http://localhost:5000".  We can now browse to "http://localhost:5000/Index.html" to see the demo application's landing page.

1.1 Installing necessary tools

Please make sure that you have the essential tools installed. Following tools are necessary to build the application:-

  • Dotnet Core
  • Nodejs
  • Typescript

Steps to install the above tools along with other necessary packages/extensions are outlined below in Installation section.

We can rather create a shell script to execute the below commands for us. But we want to take a look at what we are doing and below there is some breif information about why we are using these tools. Hope this gives a bit of understanding into what's going on below - rather than doing it using automation script. Later we would delegate most of our repetitive tasks to automation tools. For now, please follow the steps below.

1.2 Download the source code

Here is the sequence of steps we followed

1.2.1 clone the git repository

mkdir testapp
cd testapp

git init
git remote add origin https://github.com/amitthk/angdnx.git
git pull origin master

1.3 restore dotnet packages

dotnet restore

1.4 restore npm packages

cd angdnx
npm install

1.5 transpile the Typescript

For some reason the gulp-typescript is skipping some of the typescript files while transpiling the app directory. That is why we need to do it manually once. To do this:-

cd app
tsc

1.6 Run the gulp default task

cd ..
gulp

Above command would run the "default" gulp.task defined in the gulpfile.js in the directory \angdnx\angdnx . We can run other tasks as well. For example to build Dotnet application we can use command gulp build:csharp.

Note: gulp-tslint task may respond with many potential errors. However, as long as our main task build:ts (build typescript) succeeds we take TypeScript compile successful. We can beautify our typescript to follow all the tslint rules but for now we are keeping this simple.

Note: If you have already installed gulp but still seeing error like this "Local gulp not found in ...." then run the following command:-

npm link gulp 

1.7 And finally Run the Application

Finally build the application using "dotnet build" (optional in our case because "dotnet run" would build the app before running). And Run the app using "dotnet run"

dotnet build
dotnet run

2 Installations

This section describes the installations we require in order to compile, run & edit our app.

2.1 Install dotnet core

There's downloadable installer packages available for Windows/Mac. The process is pretty simple Just download the installer package, double click and follow the prompts.

I'm on linux so I used the standard install instructions from here:

https://www.microsoft.com/net/core#ubuntu

Run following commands one by one:-

sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet/ trusty main" > /etc/apt/sources.list.d/dotnetdev.list'
sudo apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893
sudo apt-get update

Moreover, while trying to run the installer for .net core rc2 had a minor dependency issue(missing dependencies) which were installed using following commands from Donovan Brown's blog:- http://www.donovanbrown.com/post/2016/05/29/Installing-NET-Core-RC2-on-Ubuntu-1604

Basically run following commands in terminal:-

sudo apt-get install -y libunwind8 libcurl3
wget http://security.ubuntu.com/ubuntu/pool/main/i/icu/libicu52_52.1-8ubuntu0.2_amd64.deb
sudo dpkg -i libicu52_52.1-8ubuntu0.2_amd64.deb

Then proceed to install .Net Core RC2. Open a new window and type the command:-

sudo apt-get install dotnet-dev-1.0.0-preview1-002702

2.2 Install Nodejs (and required Nodejs packages)

Just follow the instructions here:- https://github.com/nodesource/distributions

# Using Ubuntu
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install -y nodejs

2.2.1 What is NodeJs? Why use NodeJs?

NodeJs is Javascript based server side development platform built on Google Chrome's (V8) Javascript Engine. Being very fast, highly scalable, Asynchronous/Non-Blocking, cross-platfrom are among few of many compelling reasons to choose NodeJs.

2.2.2 What is Npm? Why use Npm?

NPM stands for NodeJs Package Manager. NPM is as its name suggest a package manager for NodeJs Modules. NPM simplifies the process of specifying, downloading, installing, linking and managing our package NodeJs Package dependencies.

2.3 Install required node packages

 

2.3.1 Typescript (and related packages)

We want to use typescript so we install typescript related packages using Node Package Manager npm:-

npm install -g typescript tslint typings
  1. What is Typescript?

    Typescript is superset of Javascript. We can basically write various object oriented constructs like Classes, Interfaces etc. in typescript and Typescript compiler will transpile them into standard javascript which can be understood by browsers.

  2. Why Typescript?

    Typescript makes writing Javascript very easy as well as supports various Object Oriented constructs like Classes, Interfaces etc. which would be difficult to handwrite otherwise.

    We would be using typescript. Typescript is also preferred by Angular 2 team. It transpiles to Ecma 5 javascript which most of the browsers understand.

2.3.2 Yo, Bower, Grunt and Gulp

npm install -g yo bower grunt-cli gulp

Note: If your're getting permission errors - run the above commands in Administrator mode. sudo npm install -g <package names>

  1. What is Yo (or Yeoman)? Why use Yeoman?

    Yeoman is a generic scaffolding system allowing the creation of any kind of app. It allows for rapidly getting started on new projects and streamlines the maintenance of existing projects. We want to use Yo (or Yeoman) to quickly scaffold our .Net Core RC2 boilerplate code.

  2. What is Gulp? Why use Gulp?

    Gulp is a Javascript based task runner or build automation tool. We use gulp automation for simplifying many of our repetitive tasks such as - bundling, minifying, copying, packaging our artefacts(libraries, javascript stylesheets), for running unit tests, code analysis, LESS/SASS compilation etc.

  3. What is Bower (or BowerJs)? Why use Bower?

    Bower Js is yet another package manager. The difference between NPM and bower is that Bower is used for managing front end components like html,css,js etc. We may want to use Bower so that we can separate our front end dependencies from server side (or NodeJs dependencies) when we use NodeJs for server-side development.

  4. What is grunt? Why use Grunt?

    Grunt is yet another task-based command line build tool for Javascript Projects. The difference between Gulp and Grunt is that Grunt primarily has more focus on Configuration (commonly used, built-in tasks configurable through JSON based configuration files) - compared to Gulp where the tasks are basically javascript code. Both the tools have a definite advantage and flourishing communities. We would be using Gulp in this project.

2.3.3 Aspnet-generator for Yo

npm install -g generator-aspnet

This is a generator package for Yeoman to scaffold different types of Asp.Net applications for us. It includes templates for scaffolding various type of .Net projects like Console, Web, Web-Application, Class Library, Unit Test Projects etc.

2.4 Install Visual Studio Code (optional)

Follow the instructions here (http://askubuntu.com/questions/616075/how-to-install-visual-studio-code-on-ubuntu)

Simply "Download the Visual Studio Code" zip from here (http://go.microsoft.com/fwlink/?LinkID=534108) . Extract the Zip. We can run the script VSCode\code from Unzipped location or move it to any desired location.

Optionally create a symbolic link to code executable for convenience:- 

ln -s ~/Downloads/VSCode/code /usr/loca/bin/code

2.4.1 Visual studio code extensions

We simply run the command console using shortcut Ctrl+Shift+P or choose from (top menu) => View => Command Pallete => (search and select "Install Extension") => find following extensions and click the cloud button to download.<code>
  • C#
  • Angular 2 TypeScript Snippets (from johnpapa)
  • TSLint(optional)

Our machine is pretty much ready. Lets go ahead and setup our application.

3 Setup

This topic is divided into two sub-topics/sup-sections. The first sub-section 3.1 deals with Server Side setup of our application. The second sub-section 3.2 deals with Client Side setup of our application.

3.1 .Net Core (The server side/C#)

After this step, this is how our Directory structure is going to look like (I am adding this image at the start here so that we have a reference point for the structure that we are creating):-
 
Image 1

3.1.1 Install the aspnet-generator for yo

In terminal we created a directory for our application (mkdir angdnx) "angdnx". Then we type the command:

yo aspnet

Select Web API application, enter the name of your app, press Enter.

We type "yo aspnet" again and created 2 more "Class Library" projects:- angdnx.Domain  and  angdnx.Data

Then we added global.json with following contents to the root of our solution:-

{ 
"projects": [ "angdnx", "angdnx.Domain", "angdnx.Data"], 
"sdk": { "version": "1.0.0-rc2-final"}
}

Then we updated our project references like this:- angdnx references => angdnx.Domain, angdnx.Data 

//File: \angdnx\angdnx\project.json
{
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0-rc2-3002702",
"type": "platform"
},
// ...... truncated for brevity
"Microsoft.Extensions.Logging.Debug": "1.0.0-rc2-final",
"angdnx.Domain":"*",
"angdnx.Data":"*"
},
"tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": {
"version": "1.0.0-preview1-final",
"imports": "portable-net45+win8+dnxcore50"
}
},
//...... continues.....
}

angdnx.Data references => angdnx.Domain

//File: angdnx\angdnx.Data
{
"version": "1.0.0-*",
"dependencies": {
"NETStandard.Library": "1.5.0-rc2-24027",
"angdnx.Domain":"*"
},
"frameworks": {
"netstandard1.5": {
"imports": "dnxcore50"
}
},
"tooling": {
"defaultNamespace": "angdnx.Data"
}
}

And from top level directory we run the following command:- 

dotnet restore

If we go now to angdnx and run dotnet build it would build angdnx.Domain and angdnx.Data projects first before building angdnx

cd angdnx
dotnet build

 

3.1.2 Add Domain, Repository and Controller Classes

We go ahead and added following classes to our projects:-

  1. Note Class, and INotesRepository domain in angdnx.Domain project (domain objects)
  2. NotesRepository Class in angdnx.Data (data layer)
  3. NoteController in our angdnx Project \Controllers directory ( our Web Api Layer)

1.1.  Domain model class Note in Domain Class Library Project.

//File: \angdnx.Domain\Note.cs
using System;
using System.Collections.Generic;

namespace angdnx.Domain
{
   public class Note
    {
        private Guid _Id;
        public Guid Id
        {
            get { return _Id;}
            set { _Id = value;}
        }
        
        private string _Title;
        public string Title
        {
            get { return _Title;}
            set { _Title = value;}
        }
        
        private string _Text;
        public string Text
        {
            get { return _Text;}
            set { _Text = value;}
        }
        
        
    }
}

1.2. INotesRepository interface in angdnx.Domain Class Library project. This is the contract for NotesRepository:-

using System.Collections.Generic;
using angdnx.Domain;
using System;
using System.Linq;

namespace angdnx.Domain
{
    public interface INotesRepository
        {
            List<Note> GetAll();
            Note Get(Guid id);
            Guid Add(Note note);
            bool Update(Guid id, Note note);
            bool Delete(Guid id);
        }
}

2. NotesRepository class in our Data layer (i.e. angdnx.Data Class Library project):-

//File: \angdnx.Data\NotesRepository.cs<note><note><note></note></note></note>
using System.Collections.Generic;
using angdnx.Domain;
using System;
using System.Linq;

namespace angdnx.Data
{
    public class NotesRepository : INotesRepository
    {
        List<Note> _notesList;
        public NotesRepository(){
            _notesList= new List<Note>();
            
            seed();
        }
        
        public List<Note> GetAll(){
            return(_notesList);
        }
        
        public Note Get(Guid id){
            return _notesList.FirstOrDefault(x=>x.Id==id);
        }
        
        public Guid Add(Note note){
            _notesList.Add(note);
            return note.Id;
        }

        public bool Update(Guid id, Note note){
            int idx=_notesList.FindIndex(x=>x.Id==id);
            if(idx!=-1){
                note.Id=id;
                _notesList[idx]=note;
                return true;
            }else{
                return false;
            }
        }
        
        public bool Delete(Guid id){
            var note= _notesList.FirstOrDefault(x=>x.Id==id);
            if (note!=null)
            {
                _notesList.Remove(note);
                return(true);
            }else
            {
                return false;
            }
        }
        
        private void seed(){
            _notesList.Add(new Note(){
                Id=Guid.NewGuid(),
                Title="One",
                Text="1. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.  1"
            });
                        _notesList.Add(new Note(){
                Id=Guid.NewGuid(),
                Title="2",
                Text="2. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.  2"
            });
                        _notesList.Add(new Note(){
                Id=Guid.NewGuid(),
                Title="3",
                Text="3. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.  3"
            });
                        _notesList.Add(new Note(){
                Id=Guid.NewGuid(),
                Title="4",
                Text="4. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.  4"
            });
                        _notesList.Add(new Note(){
                Id=Guid.NewGuid(),
                Title="5",
                Text="5. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.  5"
            });
        }
    }
}

3. And here's how our NoteController looks like in our Web project:-

 //File: \angdnx\Controllers\NoteController.cs 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using angdnx.Data;
using angdnx.Domain;
using Microsoft.Extensions.Logging;

namespace angdnx.Controllers
{
    [Route("api/[controller]")]
    public class NoteController : Controller
    {
        
        INotesRepository _repo;
        private readonly ILogger<NoteController> _logger;
        
        public NoteController(ILogger<NoteController> logger, INotesRepository noteRepository){
            _logger=logger;
            _repo= noteRepository;          
        }
        
        // GET api/note
        [HttpGet]
        public List<Note> Get()
        {
            return _repo.GetAll();
        }

        // GET api/note/{guid}
        [HttpGet("{id}")]
        public Note Get(string id)
        {
            var _guid = Guid.Empty;
            if((Guid.TryParse(id, out _guid))&&(_guid!=Guid.Empty)){
            return _repo.Get(_guid);
            }else
            {
                return null;
            }
        }

        // POST api/note
        [HttpPost]
        public Guid Post(Note value)
        {
            return _repo.Add(value);
        }

        // PUT api/note/{guid}
        [HttpPut("{id}")]
        public bool Put(string id, [FromBody]Note value)
        {
            var _id=Guid.Parse(id);
            _logger.LogInformation(_id.ToString());
            _logger.LogInformation("Values: "+value.Id.ToString());
             _logger.LogInformation("Title:"+value.Title);
             _logger.LogInformation("Text:"+ value.Text);
            return _repo.Update(_id,value);
        }

        // DELETE api/note/{guid}
        [HttpDelete("{id}")]
        public bool Delete(string id)
        {
            var _id= Guid.Parse(id);
            _logger.LogInformation(_id.ToString());
            return _repo.Delete(_id);
        }
    }
}

We also need to update the ConfigureServices method in Startup class in our web project to inject the NotesRepository wherever we're asking for INotesRepository like below:-

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();
    services.Add(new ServiceDescriptor(typeof(INotesRepository), p=> new NotesRepository(), ServiceLifetime.Singleton));
}

and we're ready with new app created in respective directory.

Type "dotnet restore" and "dotnet run" the app should be running on port 5000. Use any Rest Client to access http://localhost:5000/api/note we should be able to see all our objects being fetched. That's cool our API is ready. Let's do some more work on server side then we proceed to UI Layer.

3.1.3 Add Landing Page (Index.html) to our Web Project

We want to add the basic landing page for our app. But currently our app is not configured to serve static pages. Below is the configuration we do to serve static pages. Also  for demo we would create a basic HTML 5 page as in below listing. We would update this later on.

  1. Serve static files

    But hold on, our app is still not configured to serve static files yet. Let's add the following dependency to our project.json

    "Microsoft.AspNetCore.StaticFiles":"1.0.0-rc2-final",

    and in our Starutup.cs Configure method add the following line

    app.UseStaticFiles(); //Just before app.UseMvc()

    Then we added following lines to the end of our Middleware stack in Configure method - so that if no middleware processes the request it finally lands back to "Index.html".

    // app.Run(ctx=>{
    //    ctx.Response.Redirect("/Index.html");
    //    return Task.FromResult(0);
    // });
    

    WARNING: Be very careful with this since - if angularjs is not able to find any templates "Index.html" may be returned. This may lead to errors messages which are completely nightmare to understand. Later we may prefer to land the user in proper error handling page and log/email the errors here. For now we use this short cut method.

    do a dotnet restore and dotnet run and we should have our app running.

    2. Add the \angdnx\wwwroot\Index.html page

    <html>
    <head>
      <base href="/">
        <title>Hello world</title>
        <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
    <style type="text/css">
        .main_content,.rhs_nav{
            border:solid 1px #ccc;
        }
    </style>
        <!-- 1. Load libraries -->
         <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
    </head>
    <body class="container">
    <div class="navbar navbar-default" role="navigation">
    <button class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
    <span class="sr-only">Toggle navigation</span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
        </button>
    <div class="collapse navbar-collapse">
        <ul class="nav navbar-nav">
        <li><a href="#">Home</a></li>
        <li><a href="#">About Us</a></li>
        <li><a href="#">Contact Us</a></li>
    </ul>
    </div>
    </div>
    <div class="row">
    <div class="col-sm-4 col-offset-4">
        <div class="container">
      <notes-list>
          <!-- remove this later -->
          <div id="notes-list" class="row">
              </div>
              <script>
                  $.ajax({
                    type: 'GET',
                    url: '/api/note',
                    dataType: 'json',
                    success: function (data) {
                        $.each(data, function(index, element) {
                            $('#notes-list').append($('<div>', {
                                text: element.Id+" : "+element.Title+" : "+element.Text,  
                                class: 'col-sm-12'
                            }));
                        });
                    }
                });
                 </script>
                 <!-- remove this later -->
      <hr>
    </div>
    </div>
    </div>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
    </body>
    </html>

     

    Now if we visit the url:   http://localhost:5000/Index.html in our browser we should be able to see our Index.html page along with the JSON data pulled via Ajax by calling our Web-Api.

3.2 Angular 2 (UI or Client Side infrastructure)

In this section we want to set up our  UI or Client Side Infrastructure. of our application. For this we would follow the instructions from Angular.IO typescript QuickStart tutorials. We would do some minor updates to our application along the way.

https://angular.io/docs/ts/latest/quickstart.html

We copy the 4 files as it is from the tutorial without any modification.

  1. package.json,
  2. tsconfig.json,
  3. typings.json,
  4. systemjs.config.js

Only difference is we copied systemjs.config.js to the \app folder in our WebApi project root. i.e. \angdnx\app\systemjs.config.js . We would also do some minor modifications to this file so that it is able to pick up things from proper locations.

3.2.1 Install required NodeJs modules (npm install)

The package.json we copied from Angular.IO Quickstart lists down some packages which are necessary to compile & run our Angular 2 Typescript App.  We install these NodeJs modules using following command from \angdnx directory (Web App Project - where we copied our package.json file):-

cd angdnx
npm install

We would immediately see the \wwwroot\node_modules directory would be filled up with packages.

3.2.2 Install typings (typings install)

cd angdnx
typings install

This will install typings to our app. We should see a folder named typings in our web api project. We also notice that there's a typescript file index.d.ts which references all the installed typings in its subdirectory.

3.2.3 Let's creat our Angular 2 (Typescript) App. now (in \angdnx\app folder)

We're going to add the following files to setup our Angular 2 UI :-

  1. \app\main.ts (Bootstrap code for our application)
  2. \app\NotesListComponent.ts (This is our component class we're using for CRUD)
  3. \app\Services\NotesService.ts (Service layer to interact with Web-Api via Http REST)
  4. \app\Models\Note.ts (Our UI model class we would be using as Type in our typescript)
  5. \app\systemjs.config.js

1. In our \app folder we add a typescript file named main.ts. Then in this file we type ng2 and we should see the Angular 2 code snippets as we installed from extension as in below image.

Image 2

We select ng2-bootstrap template and the code will scaffold into Angular 2 bootstrap template. We do few updates to this file something like below.

/// <reference path='../typings/index.d.ts' />

import { enableProdMode } from '@angular/core';
import { bootstrap } from '@angular/platform-browser-dynamic';
import { HTTP_PROVIDERS } from '@angular/http';

import { NotesListComponent } from './noteslistcomponent';

// enableProdMode();

bootstrap(NotesListComponent, [HTTP_PROVIDERS])
    .then(success => console.log('Bootstrap success'))
    .catch(error => console.log(error));

2. Up next we created our component NotesListComponent in its own file noteslistcomponent.ts. This is the component which our main.ts class is importing above. We can use ng2-component template to scaffold this Component for us.  @Component is an Angular 2 decorator. It takes Metadata as argument - which is used to describle how our component classes will be used by Angular 2.

import { Component, OnInit } from '@angular/core';
import { NotesService } from './Services/NotesService';
import { Note } from './Models/Note';

@Component({
    moduleId: module.id,
    selector: 'notes-list',
    templateUrl: '/app/Views/noteslistcomponent.html',
    providers:[NotesService]
})
export class NotesListComponent implements OnInit {
            allnotes:Note[];
        selectedNote:Note;
        newNote:Note;
        isUpdated:boolean;
    constructor(private notesService:NotesService) { }
    ngOnInit() {
        this.notesService.getNotes().subscribe(lst=>this.allnotes=lst.json());
        this.selectedNote = new Note('','','');;
        this.newNote = new Note('','','');;
     }
    onSelect(note){
         this.selectedNote=note;
     }

    onAdd(note){
        if(note.Text.trim()==''){
            return;
        }
        this.notesService.addNote(note).subscribe(rsp=>{
            note.Id=this.trim(rsp.text(),'"');
            this.allnotes.push(note);
            let fresh=new Note('','','');
            this.newNote = fresh;
        });
    }
    onUpdate(note){
            if(note.Id.trim()==''){
                return;
            }
        this.notesService.updateNote(note.Id,note).subscribe(rsp=>{
           //var idx= this.allnotes.indexOf(note);
           //this.allnotes[idx].Id=rsp.text();
            console.log(rsp.text());
            if (rsp.text()=='true')
            {
                let fresh=new Note('','','');
                this.selectedNote = fresh;
            }
        });
    }
    onDelete(note){
        if(note.Id.trim()==''){
            return;
        }
        this.notesService.deleteNote(note.Id).subscribe(rsp=>{
            console.log(rsp.text());
            if (rsp.text()=='true') {
                this.deleteItem(this.allnotes,note);
                let fresh=new Note('','','');
                this.selectedNote = fresh;
            }
        });
    }

    trim(str, chr) {
    var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^'+chr+'+|'+chr+'+$', 'g');
    return str.replace(rgxtrim, '');
    }

    deleteItem(arr:Note[],itm:Note){
        for (var idx = 0; idx < arr.length; idx++) {
            var elm = arr[idx];
            if (elm.Id==itm.Id) {
                arr.splice(idx,1);
            }
        }
    }

    isEquivalent(a, b) {
        var bVal=JSON.stringify(a) === JSON.stringify(b);
        return (bVal);
    }
}

2.b

And here is the CRUD view \angdnx\app\Views\noteslistcomponent.html we are refering to in above component:-

<div class="row">
    <div class="col-sm-12">
        <table class="table table-bordered">
            <tr>
                <td colspan="4">
                    Edit (Select an Item below first)
                </td>
            </tr>
            <tr>
                <th>Title</th>
                <th>Text</th>
                <th>Edit</th>
                <th>Delete</th>
            </tr>
            <tr>
                <td><input class="form-control" placeholder="Title" type="text" [(ngModel)]="selectedNote.Title" /></td>
                <td><textarea class="form-control" placeholder="Text" type="text" [(ngModel)]="selectedNote.Text"></textarea></td>
                <td><input type="button" value="Update" (click)="onUpdate(selectedNote)"/></td>
                <td><input type="button" value="Delete" (click)="onDelete(selectedNote)"/></td>
            </tr>
        </table>
    </div>
</div>
<div class="row">
<div class="col-sm-12">
<table class="table table-bordered">
    <tr>
        <th>Title</th>
        <th>Text(Click to Select)</th>
        <th>Id(Click To Select)</th>
    </tr>
<tr *ngFor="let note of allnotes">
<td class="col-sm-2" (click)="onSelect(note)">{{note.Title}}</td><td class="col-sm-8" (click)="onSelect(note)"> {{note.Text}}</td><td (click)="onSelect(note)">{{note.Id}}</td>
</tr>
</table>
</div>
</div>
<hr/>
<div class="row">
    <div class="col-sm-12">
        <table class="table table-bordered">
            <tr>
            <th colspan="3">
                Add
            </th>
            </tr>
            <tr>
                <th>Title</th>
                <th>Text</th>
                <th>(Add)</th>
            </tr>
            <tr>
                <td>
                    <input class="form-control" placeholder="Title" type="text" [(ngModel)]="newNote.Title" />
                </td>
                <td>
                    <textarea class="form-control" placeholder="Text" type="text" [(ngModel)]="newNote.Text"></textarea>
                </td>
                <td>
                    <input type="button" value="add" (click)="onAdd(newNote)">
                </td>
            </tr>
        </table>
    </div>

</div>

3. Here is our \app\Services\NotesService.ts class which is reference above in NotesListComponent.ts:-

import { Injectable } from '@angular/core';
import { Http, Response, Headers, RequestOptions  } from '@angular/http';
import "rxjs/add/operator/map";
import { Note } from '../Models/Note';

@Injectable()
export class NotesService {

    apiUrl:string;

    constructor(private http:Http) {
        this.apiUrl='/api/note';
    }
    
     getNotes(){
         return this.http.get(this.apiUrl).map((res: Response) => res);
     }

     updateNote(id:string,note:Note){
        let body = JSON.stringify(note);
        let headers = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: headers });
         return this.http.put(this.apiUrl+'/'+id,body, options).map((res:Response)=>res);
     }
     
     addNote(note:Note){
         return this.http.post(this.apiUrl,JSON.stringify(note)).map((res:Response)=>res);
     }

     deleteNote(id:string){
        return this.http.delete(this.apiUrl+'/'+id).map((res:Response)=>res);
     }

}

4. And of course here is our simple UI Model class which we're using as type in above classes.

/**
 * \angdnx\app\Models\Note.ts
 */
export class Note {
    Id:string;
    Title:string;
    Text:string;
    constructor(id:string,title:string,text:string) {
        this.Id=id;
        this.Title=title;
        this.Text=text;
    }
}

If our typescript is showing reference errors in VSCode, we add the following line to the top of or main.ts:-

/// <reference path='../typings/index.d.ts' />

This references the typings and we should immediately see the the typescript error markers disappear.

There's various commands listed in Angular 2 help page to transpile and play around our typescript files. We simply go to our project directory in Terminal and typed following:-

tsc -p .

We would see for each of our ts file there would be a transpiled js(and corresponding .map file) generated. If not, possibly you've missed installing npm typescipt/typings - please verify again.

3.2.4 Update systemjs.config.js

We wanna do minor updates to our systemjs.config.js so that this file can correctly locate our modules once loaded. So in our final app we're gonna transpile and bundle our javascript to one file named myapp.min.js. so our var packages ={ 'app': {main: 'myapp.min.js',…. in below file.

Second, we also need to tell our app where to look for libraries. Our map should locate correct directories so our var map = { 'app': 'js', '@angular': 'js/lib/@angular' in the below file. (This is the place where we would use Gulp to copy our Javascript libraries to)

/** File: \angdnx\app\systemjs.config.js
 * System configuration for Angular 2 samples
 * Adjust as necessary for your application needs.
 */
(function(global) {
  // map tells the System loader where to look for things
  var map = {
    'app':                        'app', // 'dist',
    '@angular':                   'app/lib/@angular',
    'angular2-in-memory-web-api': 'app/lib/angular2-in-memory-web-api',
    'rxjs':                       'app/lib/rxjs'
  };
  // packages tells the System loader how to load when no filename and/or no extension
  var packages = {
    'app':                        { main: 'angdnxapp.min.js',  defaultExtension: 'js' },
    'rxjs':                       { defaultExtension: 'js' },
    'angular2-in-memory-web-api': { defaultExtension: 'js' },
  };
  var ngPackageNames = [
    'common',
    'compiler',
    'core',
    'http',
    'platform-browser',
    'platform-browser-dynamic',
    'router',
    'router-deprecated',
    'upgrade',
  ];
  // Add package entries for angular packages
  ngPackageNames.forEach(function(pkgName) {
    packages['@angular/'+pkgName] = { main: pkgName + '.umd.js', defaultExtension: 'js' };
  });
  var config = {
    map: map,
    packages: packages
  }
  System.config(config);
})(this);

3.2.5 Update the Landing page (Index.html) and link the application to it

Up here we're going to update the landing page of our Application. Below is how our landing page (Index.html) would look like. Basically we are only referencing the required libraries. Then we reference our 'systemjs.config.js" which pull up our application from minified Javascript file. (This minified Javascript file we are going to bundle using gulp as in below section 3.2.6 The gulpfile.js. ) Then we call our Angular 2 component using the selector <notes-list>

<html>
<head>
  <base href="/">
    <title>Hello world</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous" />
<style type="text/css">
    .main_content,.rhs_nav{
        border:solid 1px #ccc;
    }
</style>
    <script src="app/lib/shim.min.js"></script>
    <script src="app/lib/zone.js"></script>
    <script src="app/lib/Reflect.js"></script>
    <script src="app/lib/system.src.js"></script>
    <script src="app/lib/systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script
</head>
<body>
<div class="container">
  <notes-list>
</div>
</body>
</html>

 

3.2.6 The gulpfile.js

We want to make use of some gulp automation now in order to package our application. We want gulp to basically do following tasks for us:-

  1. Copy over the required javascript libraries
  2. Copy (and bundle) the stylesheets for us
  3. Compile the typescript files
  4. Bundle the typescript files and pakcage them into one minifile file
  5. Copy Assets (Views, Styles and other site assets)

In addition we want our gulpfile to:-

  1. Watch for changes in typescript files and automatically run the above packaging as soon as change is detected.
  2. (Optionally) watch for changes in CSharp(C#) files and perform the rebuild and redeploy of our app as soon as change is detected.

We added following gulpfile.js to our app. The tasks in the gulpfile serve the same purpose as defined above.

  1. update package.json => devDependencies

    We want to install our required gulp modules. Please update the devDependencies in the package.json:-

    {
      "name": "angular2-quickstart",
      "version": "1.0.0",
      "scripts": {
        "start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
        "lite": "lite-server",
        "postinstall": "typings install",
        "tsc": "tsc",
        "tsc:w": "tsc -w",
        "typings": "typings"
      },
      "license": "ISC",
      "dependencies": {
        "@angular/common":  "2.0.0-rc.1",
        "@angular/compiler":  "2.0.0-rc.1",
        "@angular/core":  "2.0.0-rc.1",
        "@angular/http":  "2.0.0-rc.1",
        "@angular/platform-browser":  "2.0.0-rc.1",
        "@angular/platform-browser-dynamic":  "2.0.0-rc.1",
        "@angular/router":  "2.0.0-rc.1",
        "@angular/router-deprecated":  "2.0.0-rc.1",
        "@angular/upgrade":  "2.0.0-rc.1",
        "systemjs": "0.19.27",
        "core-js": "^2.4.0",
        "reflect-metadata": "^0.1.3",
        "rxjs": "5.0.0-beta.6",
        "zone.js": "^0.6.12",
        "angular2-in-memory-web-api": "0.0.10",
        "bootstrap": "^3.3.6"
      },
      "devDependencies": {
        "concurrently": "^2.0.0",
        "lite-server": "^2.2.0",
        "typescript": "^1.8.10",
        "typings":"^1.0.4",
        "browser-sync": "^2.12.8",
        "browserify": "^13.0.1",
        "gulp": "^3.9.1",
        "gulp-concat": "^2.6.0",
        "gulp-cssmin": "^0.1.7",
        "gulp-debug": "^2.1.2",
        "gulp-dotnet": "^2.0.0",
        "gulp-install": "^0.6.0",
        "gulp-mocha": "^2.2.0",
        "gulp-sourcemaps": "^1.6.0",
        "gulp-tslint": "^5.0.0",
        "gulp-typescript": "^2.13.4",
        "gulp-uglify": "^1.5.3",
        "gulp-util": "^3.0.7",
        "rimraf": "^2.5.2",
        "run-sequence": "^1.2.1",
        "tslint": "^3.10.2",
        "vinyl-buffer": "^1.0.0",
        "vinyl-source-stream": "^1.1.0"
      }
    }

    Go to angdnx\angdnx and run npm install. This should install all the devDependencies. At the end of process we should be able to see a tree structure like below:-

    Image 3

  2. Add gulpfile.js

    Let's add the following gulpfile.js

    var gulp        = require("gulp"),
        rimraf      = require("rimraf"),
        gutil      = require("gulp-util"),
        browserify  = require("browserify"),
        source      = require("vinyl-source-stream"),
        buffer      = require("vinyl-buffer"),
        tslint      = require("gulp-tslint"),
        tsc         = require("gulp-typescript"),
        sourcemaps  = require("gulp-sourcemaps"),
        concat      = require("gulp-concat"),
        cssmin      = require("gulp-cssmin"),
        uglify      = require("gulp-uglify"),
        runSequence = require("run-sequence"),
        mocha       = require("gulp-mocha"),
        debug       = require("gulp-debug"),
        Dotnet = require('gulp-dotnet'),
        browserSync = require('browser-sync').create();
    
    var paths = {
      webroot: "./wwwroot/",
      cs:"./**/*.cs",
      ts:"./app/**/*.ts"
    };
    
    paths.js = paths.webroot + "app/**/*.js";
    paths.minJs = paths.webroot + "app/**/*.min.js";
    paths.css = paths.webroot + "css/**/*.css";
    paths.minCss = paths.webroot + "css/**/*.min.css";
    paths.concatJsDest = paths.webroot + "app/site.min.js";
    paths.concatCssDest = paths.webroot + "css/site.min.css";
    
    //1. Copy over the required javascript libraries
    gulp.task('copy-lib', function () {
        gulp.src([
        'node_modules/core-js/client/shim.min.js',
        'node_modules/zone.js/dist/zone.js',
        'node_modules/reflect-metadata/Reflect.js',
        'node_modules/systemjs/dist/system.src.js',
        'app/systemjs.config.js'
        ]).pipe(gulp.dest('wwwroot/app/lib/'));
        
            gulp.src([
        'node_modules/@angular/**/*',
        'node_modules/angular2-in-memory-web-api/**/*',
        'node_modules/rxjs/**/*'
        ],{base:'./node_modules/'}).pipe(gulp.dest('wwwroot/app/lib'));
    });
    
    //Lint the typescript files and warn the potential problems
    gulp.task("ts:lint", function() {
        return gulp.src([
            "app/**/**.ts"
        ])
        .pipe(tslint({ }))
        .pipe(tslint.report("verbose"));
    });
    
    //3. Compile the typescript files, minimize and bundle them
    var tsProject = tsc.createProject("tsconfig.json");
    
    gulp.task("build:ts", function() {
        return gulp.src([
                "app/**/**.ts",
                "typings/index.d.ts"
            ])
            .pipe(tsc(tsProject))
            .js.pipe(gulp.dest("app/"));
    });
    
    gulp.task("clean:js", function (cb) {
      rimraf(paths.concatJsDest, cb);
    });
    
    gulp.task("clean:css", function (cb) {
      rimraf(paths.concatCssDest, cb);
    });
    
    gulp.task("clean", ["clean:js", "clean:css"]);
    
    gulp.task("bundle", function() {
    
        var libraryName = "angdnxapp";
        var mainTsFilePath = "app/main.js";
        var outputFolder   = "wwwroot/app/";
        var outputFileName = libraryName + ".min.js";
    
        var bundler = browserify({
            debug: true,
            standalone : libraryName
        });
    
        return bundler.add(mainTsFilePath)
            .bundle()
            .pipe(source(outputFileName))
            .pipe(buffer())
            .pipe(sourcemaps.init({ loadMaps: true }))
            .pipe(uglify())
            .pipe(debug({title:'stacktrace:'}))
            .pipe(sourcemaps.write('./'))
            .pipe(gulp.dest(outputFolder));
    });
    
    gulp.task("copy-assets", function() {
       gulp.src([
        'app/*.html',
        'app/Views/**/*',
        'app/Styles/**/*'
        ],{base:'./app/'}).pipe(gulp.dest('wwwroot/app/'));
    });
    
    //<start> Standard Javascript and Css tasks
    
    gulp.task("min:js", function () {
      return gulp.src([paths.js, "!" + paths.minJs], { base: "." })
        .pipe(concat(paths.concatJsDest))
        .pipe(uglify())
        .pipe(gulp.dest("."));
    });
    
    gulp.task("min:css", function () {
      return gulp.src([paths.css, "!" + paths.minCss])
        .pipe(concat(paths.concatCssDest))
        .pipe(cssmin())
        .pipe(gulp.dest("."));
    });
    
    gulp.task("min", ["min:js", "min:css"]);
    
    //<end> Standard Javascript and CSS tasks
    
    //<start> Dotnet related tasks
    gulp.task("build:csharp", function(cb) {
        var options ={
        // current working directory
        cwd: './',
        // how noisy?
        // options: 'debug', 'info', 'error', 'silent'
        logLevel: 'DEBUG',
        // notify on errors
        notify: true
            };
      Dotnet.build(options, cb);
    });
    
    var server;
    
    gulp.task('start:api', function(cb) {
      if(!server)
      {
          server = new Dotnet({ cwd: paths.api  });
          server.start('run', cb);
      }
    });
    
    function changed(event) {
      gutil.log('File ${event.path} was ${event.type}, running tasks...');
    };
    
    //Run this task using "gulp watch-server" to automatically rebuild and load CSharp(C#) code
    //as soon as any changes in '.cs' files are detected
    gulp.task('watch-server', ['build:csharp'], function() {
      gulp.watch(paths.cs, {interval: 500}, function(){
        runSequence('build:csharp', 'start:api');
      }).on('change', changed);
    });
    
    //<end> Dotnet related tasks
    
    //Run this task using "gulp watch-ui" to automatically rebuild UI upon any ts file changes
    gulp.task("watch-ui", ["default"], function () {
    
        browserSync.init({
            server: "."
        });
    
        gulp.watch([paths.ts], ["default"]);
        gulp.watch(paths.js).on('change', browserSync.reload);
    });
    
    gulp.task('default', ['ts:lint', 'build:ts', 'clean','copy-lib','bundle','copy-assets']);

Now we should be able to run gulp using following command:-

gulp

This should copy our libraries, build our application into minimized file \app\wwwroot\app\angdnxapp.min.js

If we see the error that gulp is not installed - we must install the same using "npm install -g gulp". If NPM is still not able to find gulp - we may need to link gulp for our project using command "npm link gulp" and then run gulp command above.

3.2.7 More npm packages for gulp (remember the –save-dev option)

 

If we would need to install more packages for gulp tasks. Remember the –save-dev option here, this creates an entry for our "DevDependency" in our package.json so that next time we call "npm install" the Dev dependencies are pulled correctly in the target machine. For Example:-

npm install --save-dev gulp-util

History

  • 09-June-2016: Publish first version of article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)