Earlier I posted about Cascading DropDown List using Angular 2 and in this post, we take it to next level and implement it with ASP.NET Core Web API and Angular 2. But starting/configuring AngularJS 2 with ASP.NET Core is not straightforward and it requires some manual configurations to be done. If you are not sure how to do it, I recommend you to read this helpful post to find out post how to start with Angular 2 in ASP.NET Core with TypeScript using Visual Studio 2015.
Cascading DropDown List with ASP.NET Core WEB API and Angular 2
I believe now you have an idea about starting with Angular 2. I also followed same steps to configure this demo project as mentioned here. So now let’s move to next part. Please note, I selected the “Web application template” while creating the project but removed all the unnecessary code.
Add Models classes in Models folder
First, add Country and State model classes in Models folder. Following is the code for Country.cs. This class has 2 properties and a method which returns a list of countries. For demo, I used static data but you can also get the same list from database.
public class Country { public int id { get; set; } public string name { get; set; } public Country(int CountryID, string CountryName) { id = CountryID; name = CountryName; } public static List<Country> GetAllCountries() { List<Country> lstCountries = new List<Country>(); lstCountries.Add(new Country(1, "USA")); lstCountries.Add(new Country(2, "India")); lstCountries.Add(new Country(3, "Australia")); lstCountries.Add(new Country(4, "Canada")); return lstCountries ; } }
And following is the code for State.cs. This class has 3 properties and a method which returns a list of states. Nothing fancy, self-explanatory code.
public class State { public int id { get; set; } public int countryid { get; set; } public string name { get; set; } public State(int StateID, int CountryID, string StateName) { id = StateID; countryid = CountryID; name = StateName; } public static List<State> GetAllStates() { List<State> lstState = new List<State>(); lstState.Add(new State(1, 1, "Arizona")); lstState.Add(new State(2, 1, "Alaska")); lstState.Add(new State(3, 1, "Florida")); lstState.Add(new State(4, 1, "Hawaii")); lstState.Add(new State(5, 2, "Gujarat")); lstState.Add(new State(6, 2, "Goa")); lstState.Add(new State(7, 2, "Punjab")); lstState.Add(new State(8, 3, "Queensland")); lstState.Add(new State(9, 3, "South Australia")); lstState.Add(new State(10, 3, "Tasmania")); lstState.Add(new State(11, 4, "Alberta")); lstState.Add(new State(12, 4, "Ontario")); lstState.Add(new State(13, 4, "Quebec")); lstState.Add(new State(14, 4, "Saskatchewan")); return lstState; } }
Add action methods in controller
Next step is to make our WEB API ready. For your information, ASP.NET Core comes with Unified Programming Model for MVC and Web API. So open HomeController.cs
and add following 2 action methods.
[HttpGet] [Route("api/Home/GetCountries")] public IEnumerable<Country> GetCountries() { return Country.GetAllCountries(); } [HttpGet] [Route("api/Home/GetState/{countryid?}")] public IEnumerable<State> GetState(int countryid = 1) { List<State> lstState = State.GetAllStates(); return lstState.Where(item => item.countryid == countryid); }
GetCountries()
method makes call to Country.GetAllCountries();
and returns the list. Where the second method GetState()
accepts countryid
as parameters and based on the its value, filters the state list and return the same.
Note countryid
parameter is optional for GetState()
action method. And also a default value is supplied to it. Why? Well, this actually allows you to pass countryid
in querystring. And while calling REST APIs via AngularJS, the arguments/parameters are appended to URL as part of querystring. And that’s why we must allow our API to support querystring. So following URL,
http://localhost:53483/api/Home/GetState/1
is similar to
http://localhost:53483/api/Home/GetState?countryid=1
Angular 2 application structure
Create a folder named “app” in wwwroot folder. If you have already created while configuring Angular 2 then ignore this step. Now within this folder, following files needs to added.
I already explained about this application structure and about code here. But let me just summarize here again.
- country.ts: This file has a simple class called
Country
with 2 properties id and name. - state.ts: This file has a simple class called
States
with 3 properties id, countryid and name. - countrylistcomponent.ts: This file contains code for defining a component and template used to render HTML. The Component adds the metadata to the class. And it also makes call to Angular 2 Service, which in turn calls WEB API.
- CountryTemplate.html: This file has HTML code to render select dropdown list.
- dataservice.ts: This is an Angular 2 service which makes WEB API calls to get list of countries and states.
- main.ts: This file contains code to bootstrap angular 2 in our application.
country.cs
export class Country { constructor(public id: number, public name: string) { } }
state.cs
export class State { constructor(public id: number, public countryid: number, public name: string) { } }
dataservice.ts
import { Injectable } from 'angular2/core'; import {Http, URLSearchParams} from "angular2/http"; import 'rxjs/Rx'; import { Country } from './country'; import { State } from './state'; @Injectable() export class DataService { constructor(public http: Http) { this.http = http; } getCountries() { return this.http.get("/api/Home/GetCountries") .map((responseData) => responseData.json()); } getStates(countryid: string) { var params = new URLSearchParams(); params.set('countryid', countryid); return this.http.get("/api/Home/GetState", { search: params }) .map((responseData) => responseData.json()); } }
As you know Angular 2 is modular. And as here in this service, we need to make use of HTTP service. So we need to import it. Similarly URLSearchParams
, which is used to pass arguments to WEB API method. Therefore,import {Http, URLSearchParams} from "angular2/http";
And the other import statement import 'rxjs/Rx';
, imports ReactiveX library. The Http service in Angular 1 returns a promise where Angular 2 returns an Observable object. And The Observable classes in Angular 2 are provided by the ReactiveX library.
Here, first we inject $http service in class constructor. And we use http.get()
to run our HTTP request. And this returns an Observable object. Since it’s an observable object, we can use map()
to convert the response into JSON response.
countrylistcomponent.ts
import { Component } from 'angular2/core'; import { DataService } from './dataservice'; import { Country } from './country'; import { State } from './state'; @Component({ selector: 'my-country-list', templateUrl: 'app/CountryTemplate.html', providers: [DataService] }) export class CountryListComponent { selectedCountry: Country = new Country(0, 'India'); countries: Country[]; states: State[]; constructor(private _dataService: DataService) { this._dataService.getCountries().subscribe(data => { this.countries = data }); } onSelect(countryid) { if (countryid == 0) this.states = null; else this._dataService.getStates(countryid).subscribe(data => { this.states = data }); } }
This file is modified to use the service to get countries and states list.
- First, import the Service and then within
@Component
directive, setproviders: [DataService]
. - And inject the service in
CountryListComponent
constructor. And in constructor, callgetCountries()
method to get list of countries from the service. Since the service returns Observable object, we can use.subscribe()
method to get the output in variable. - Also defined
onSelect
method which will be called onChange event of country dropdown list. This method calls service to get the list of states based on selectedcountryid
and then usingsubscribe
get the list in states variable.
main.ts
import { bootstrap } from 'angular2/platform/browser'; import {HTTP_PROVIDERS} from "angular2/http"; import { CountryListComponent } from './countrylistcomponent'; bootstrap(CountryListComponent, [HTTP_PROVIDERS]);
This file has code to bootstrap Angular 2 in our application. Along with that, it also bootstrap application root/parent component. Angular’s http module exposes HTTP_PROVIDERS, which has all the providers that we need, to code http action in our service. Therefore add the http providers to the bootstrap, by importing the HTTP_PROVIDERS from angular2/http
.
CountryTemplate.html
[js]
<div class="row">
<div class="col-md-5" style="text-align:right;">
<label>Country:</label>
&lt;/div&gt;
&lt;div class="col-md-7" style="text-align:left;"&gt;
&lt;select [(ngModel)]="selectedCountry.id" (change)="onSelect($event.target.value)" style="width:150px;"&gt;
&lt;option value="0"&gt;--Select--&lt;/option&gt;
&lt;option *ngFor="let country of countries" value={{country.id}}&gt;{{country.name}}&lt;/option&gt;
&lt;/select&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div class="row"&gt;
&lt;div class="col-md-5" style="text-align:right;"&gt;
&lt;label&gt;State:&lt;/label&gt;
&lt;/div&gt;
&lt;div class="col-md-7" style="text-align:left;"&gt;
&lt;select style="width:150px;"&gt;
&lt;option value="0"&gt;--Select--&lt;/option&gt;
&lt;option *ngFor="let state of states " value={{state.id}}&gt;{{state.name}}&lt;/option&gt;
&lt;/select&gt;
&lt;/div&gt;
&lt;/div&gt;
[/js]
And finally the HTML template. This file name is used while specifying metadata for CountryListComponent
class.
There are 2 dropdowns list one for country and other for state and also change event defined on country dropdown list. Angular 2 has different way of defining event. Take the HTML event and wrap it with parentheses. $event.target.value
will give the selected country id.
I also want to bring in your attention a change made in Angular 2 version “2.0.0-beta.17” with respect to *ngFor
. Previously we were using,
*ngFor="#state of states"
but in this version, it is changed a bit. Instead of “#”, use “let”
*ngFor="let state of states"
And finally since we are using Angular 2 Http service in our application, we need to include the script in the layout. So, open the _Layout.cshtml
file located at Views/Shared/ and add the following code right after the script element that loads Angular 2 core.
<environment names="Development"> <script src="~/lib/npmlibs/angular2/http.dev.js"></script> </environment> <environment names="Staging,Production"> <script src="~/lib/npmlibs/angular2/http.min.js"></script> </environment>
Wait!!! We still need to show what we have done till now on UI. So open Views/Home/Index.cshtml
and add angular component tag <my-country-list></my-country-list>
to HTML file. And we also need to load the application and all its modules. So place following script section to the end of the file.
@section Scripts { &amp;lt;script&amp;gt; System.config({ packages: { 'app': { defaultExtension: 'js' }, 'lib': { defaultExtension: 'js' }, }, }); System.import('app/main') .then(null, console.error.bind(console)); &amp;lt;/script&amp;gt; }
That’s it. Now when you run this solution, you will see following output.
You can download the solution from here.
Configuration of Angular 2 with ASP.NET Core is bit of pain right now. But using Angular 2, once configured was fun and challenging. As both are in beta stage, let’s hope things may change.
Thank you for reading and I hope it helped you. Keep visiting this blog and share this in your network. Please put your thoughts and feedback in comments section.
Hi, i am getting error like blank dropdown data……..unable to see data in dropdown, showing white
Works! My primary mistake was I accidentally misconfigured package.json, and put everything under “devDependencies” – and had no “dependencies” section.
I also had to add in “zone.js” under “dependencies”, which isn’t mentioned on http://blog.nbellocam.me/2016/03/14/asp-net-core-and-angular-2/ but I think that’s just the version I’m using
Thank you for your articles and your help!
I am doing this in debian, visual studio code and dotnet 1.0.0-rc2-final – but I will spin up my Windows VM tomorrow and give it a try. I have verified CountryTemplate.html pathing is correct (I can’t paste it here, it seems to freak out the reply mechanism)
Hi, I tried to get this to work and it seems close – however the contents of CountryTemplate.html never render. The logging shows that countrylistcomponent.ts is trying to reach out to it, I see a Request code 200 after requesting path ‘/app/CountryTemplate.html’. Any clues? Thank you
Did you download my solution and gave it a try? Also ensure that path of CountryTemplate.html is correct in CountryListComponent.ts file.
Here are some of my own experiences adapting your guidance to a slightly different environment: http://malachib.blogspot.com/2016/05/debian-aspnet-core-10-rc2-angularjs-2.html
Thanks for everything!