Saturday 11 August 2018

ASP.NET Core - CRUD Using Angular 5 and Entity Framework Core, Part 2

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

Welcome back! If you missed part one where we build out our backend, check it out here!

Create the Angular Service

We will create an Angular service which will convert the Web API response to JSON and pass it to our component. Right click on ClientApp/app folder and then Add >> New Folder and name the folder Services.
Right click on Services folder and select Add >> New Item. An "Add New Item" dialog box will open. Select Scripts from the left panel, then select "TypeScript File" from the templates panel, and set the name as empservice.service.ts. Press OK
Open the empservice.service.ts file and put the following code into it.
import { Injectable, Inject } from '@angular/core';  
import { Http, Response } from '@angular/http';  
import { Observable } from 'rxjs/Observable';  
import { Router } from '@angular/router';  
import 'rxjs/add/operator/map';  
import 'rxjs/add/operator/catch';  
import 'rxjs/add/observable/throw';  
@Injectable()  
export class EmployeeService {  
    myAppUrl: string = "";  
    constructor(private _http: Http, @Inject('BASE_URL') baseUrl: string) {  
        this.myAppUrl = baseUrl;  
    }  
    getCityList() {  
        return this._http.get(this.myAppUrl + 'api/Employee/GetCityList')  
            .map(res => res.json())  
            .catch(this.errorHandler);  
    }  
    getEmployees() {  
        return this._http.get(this.myAppUrl + 'api/Employee/Index')  
            .map((response: Response) => response.json())  
            .catch(this.errorHandler);  
    }  
    getEmployeeById(id: number) {  
        return this._http.get(this.myAppUrl + "api/Employee/Details/" + id)  
            .map((response: Response) => response.json())  
            .catch(this.errorHandler)  
    }  
    saveEmployee(employee) {  
        return this._http.post(this.myAppUrl + 'api/Employee/Create', employee)  
            .map((response: Response) => response.json())  
            .catch(this.errorHandler)  
    }  
    updateEmployee(employee) {  
        return this._http.put(this.myAppUrl + 'api/Employee/Edit', employee)  
            .map((response: Response) => response.json())  
            .catch(this.errorHandler);  
    }  
    deleteEmployee(id) {  
        return this._http.delete(this.myAppUrl + "api/Employee/Delete/" + id)  
            .map((response: Response) => response.json())  
            .catch(this.errorHandler);  
    }  
    errorHandler(error: Response) {  
        console.log(error);  
        return Observable.throw(error);  
    }  
}
At this point in time, you might get the error. "Parameter 'employee' implicitly has an 'any' type", in the empservice.service.ts file.
If you encounter this issue, then add the following line inside tsconfig.json file.
"noImplicitAny": false 
Now, we will proceed to create our components.

Creating Angular Components

We will be adding two Angular components to our application:
  1. The fetchemployee component - will display all employee data and delete an existing employee's data.
  2. The addemployee component - will add a new employee's data or edit an existing employee's data.
Right click on ClientApp/app/components folder and select Add >> New Folder and name the folder, addemployee.
Right click on addemployee folder and select Add >> New Item. An "Add New Item" dialog box will open. Select Scripts from the left panel, then select " TypeScript File" from templates panel, and put the name as addemployee.component.ts. Press OK. This will add a typescript file inside addemployee folder.
Right click on addemployee folder and select Add >> New Item. An "Add New Item" dialog box will open. Select ASP.NET Core from the left panel, then select "HTML Page" from templates panel, and put the name as addemployee.component.html. Press OK. This will add an HTML file inside the addemployee folder.
Similarly, create a fetchemployee folder inside the ClientApp/app/components folder and add fetchemployee.component.ts and fetchemployee.component.html file to it.
Now our ClientApp/app/components will look like the image below:
Open the fetchemployee.component.ts file and put the following code in it:
import { Component, Inject } from '@angular/core';  
import { Http, Headers } from '@angular/http';  
import { Router, ActivatedRoute } from '@angular/router';  
import { EmployeeService } from '../../services/empservice.service'  
@Component({  
    templateUrl: './fetchemployee.component.html'  
})  
export class FetchEmployeeComponent {  
    public empList: EmployeeData[];  
    constructor(public http: Http, private _router: Router, private _employeeService: EmployeeService) {  
        this.getEmployees();  
    }  
    getEmployees() {  
        this._employeeService.getEmployees().subscribe(  
            data => this.empList = data  
        )  
    }  
    delete(employeeID) {  
        var ans = confirm("Do you want to delete customer with Id: " + employeeID);  
        if (ans) {  
            this._employeeService.deleteEmployee(employeeID).subscribe((data) => {  
                this.getEmployees();  
            }, error => console.error(error))  
        }  
    }  
}  
interface EmployeeData {  
    employeeId: number;  
    name: string;  
    gender: string;  
    city: string;  
    department: string;  
}
Let's understand this code. At the very top, we have imported Angular modules and EmployeeService references. After this, we have the @Component decorator to define the template URL for our component.
Inside the FetchEmployeeComponent class, we have declared an array variable empList of type EmployeeData where EmployeeData is an interface having the properties same as our TblEmployeeModel class. Inside the getEmployees method, we are calling the getEmployees method of our service EmployeeService, which will return an array of employees to be stored in the empList variable. The getEmployees method is called inside the constructor so that the employee data will be displayed as the page loads.
Next, we have a delete method which accepts employeeID as a parameter. This will prompt the user with a confirmation box and if the user selects yes then it will delete the employee with this employeeID.
Open fetchemployee.component.html file and put the following code into it.
<h1>Employee Data</h1>  
<p>This component demonstrates fetching Employee data from the server.</p>  
<p *ngIf="!empList"><em>Loading...</em></p>  
<p>  
    <a [routerLink]="['/register-employee']">Create New</a>  
</p>  
<table class='table' *ngIf="empList">  
    <thead>  
        <tr>  
            <th>EmployeeId</th>  
            <th>Name</th>  
            <th>Gender</th>  
            <th>Department</th>  
            <th>City</th>  
        </tr>  
    </thead>  
    <tbody>  
        <tr *ngFor="let emp of empList">  
            <td>{{ emp.employeeId }}</td>  
            <td>{{ emp.name }}</td>  
            <td>{{ emp.gender }}</td>  
            <td>{{ emp.department }}</td>  
            <td>{{ emp.city }}</td>  
            <td>  
            <td>  
                <a [routerLink]="['/employee/edit/', emp.employeeId]">Edit</a> |  
                <a [routerLink]="" (click)="delete(emp.employeeId)">Delete</a>  
            </td>  
        </tr>  
    </tbody>  
</table>
The code for this HTML file is pretty simple. At the top, it has a link to create new employee record and after that, it will have a table to display employee data and two links for editing and deleting each employee record.
We are finished with our fetchemployee component.
Now open addemployee.component.ts file and put the following code into it:
import { Component, OnInit } from '@angular/core';  
import { Http, Headers } from '@angular/http';  
import { NgForm, FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';  
import { Router, ActivatedRoute } from '@angular/router';  
import { FetchEmployeeComponent } from '../fetchemployee/fetchemployee.component';  
import { EmployeeService } from '../../services/empservice.service';  
@Component({  
    templateUrl: './AddEmployee.component.html'  
})  
export class createemployee implements OnInit {  
    employeeForm: FormGroup;  
    title: string = "Create";  
    employeeId: number;  
    errorMessage: any;  
    cityList: Array<any> = [];  
    constructor(private _fb: FormBuilder, private _avRoute: ActivatedRoute,  
        private _employeeService: EmployeeService, private _router: Router) {  
        if (this._avRoute.snapshot.params["id"]) {  
            this.employeeId = this._avRoute.snapshot.params["id"];  
        }  
        this.employeeForm = this._fb.group({  
            employeeId: 0,  
            name: ['', [Validators.required]],  
            gender: ['', [Validators.required]],  
            department: ['', [Validators.required]],  
            city: ['', [Validators.required]]  
        })  
    }  
    ngOnInit() {  
        this._employeeService.getCityList().subscribe(  
            data => this.cityList = data  
        )  
        if (this.employeeId > 0) {  
            this.title = "Edit";  
            this._employeeService.getEmployeeById(this.employeeId)  
                .subscribe(resp => this.employeeForm.setValue(resp)  
                , error => this.errorMessage = error);  
        }  
    }  
    save() {  
        if (!this.employeeForm.valid) {  
            return;  
        }  
        if (this.title == "Create") {  
            this._employeeService.saveEmployee(this.employeeForm.value)  
                .subscribe((data) => {  
                    this._router.navigate(['/fetch-employee']);  
                }, error => this.errorMessage = error)  
        }  
        else if (this.title == "Edit") {  
            this._employeeService.updateEmployee(this.employeeForm.value)  
                .subscribe((data) => {  
                    this._router.navigate(['/fetch-employee']);  
                }, error => this.errorMessage = error)  
        }  
    }  
    cancel() {  
        this._router.navigate(['/fetch-employee']);  
    }  
    get name() { return this.employeeForm.get('name'); }  
    get gender() { return this.employeeForm.get('gender'); }  
    get department() { return this.employeeForm.get('department'); }  
    get city() { return this.employeeForm.get('city'); }  
}
This component will be used for both adding and editing employee data. Since we are using a form model along with client-side validation to Add and Edit customer data we have imported classes from @angular/forms. The code to create the form has been put in the constructor so that the form will be displayed as the page loads.
This component will handle both Add and Edit requests. So how will the system differentiate between both requests? The answer is routing. We need to define two different route parameters, one for adding employee records and another for editing employee record, which will be defined in the app.shared.module.ts file shortly.
We have declared the variable title to show on the top of the page and the variable id to store the employee id passed as the parameter in the case of an edit request. To read the employee id from the URL, we will use ActivatedRoute.snapshot inside the constructor and set the value of the variable id.
Inside ngOnInitwe are performing two operations:
  1. We are fetching the list of cities by calling the  getCityList method from our service. We will bind the list of cities to a dropdown list in our HTML page. Since we are calling the getCityList method in ngOnInit, the dropdown list will be populated as the page loads.
  2. We will check if the id is set, then we will change the title to "Edit," get the data for that id from our service, and populate the fields in our form. The value read from the database will be returned as JSON and have all the same properties as we declared in our FormBuilder. Hence, we use the setValue method to populate our form.
The save method will be called by clicking on the "Save" button of our form. Based on whether it is an Add operation or an Edit operation it will call the corresponding method from our service and then, upon successful redirection, back to the fetch-employee component.
In the last one, we have also defined getter functions for the control names of our form to enable client-side validation.
Open the addemployee.component.html file and put the following code into it:
<!DOCTYPE html>  
<html>  
<head>  
    <meta charset="utf-8" />  
    <title></title>  
</head>  
<body>  
    <h1>{{title}}</h1>  
    <h3>Employee</h3>  
    <hr />  
    <form [formGroup]="employeeForm" (ngSubmit)="save()" #formDir="ngForm" novalidate>  
        <div class="form-group row">  
            <label class=" control-label col-md-12" for="Name">Name</label>  
            <div class="col-md-4">  
                <input class="form-control" type="text" formControlName="name">  
            </div>  
            <span class="text-danger" *ngIf="employeeForm.hasError('required', 'name') && formDir.submitted">  
                Name is required.  
            </span>  
        </div>  
        <div class="form-group row">  
            <label class="control-label col-md-12" for="Gender">Gender</label>  
            <div class="col-md-4">  
                <select class="form-control" data-val="true" formControlName="gender">  
                    <option value="">-- Select Gender --</option>  
                    <option value="Male">Male</option>  
                    <option value="Female">Female</option>  
                </select>  
            </div>  
            <span class="text-danger" *ngIf="employeeForm.hasError('required', 'gender') && formDir.submitted">  
                Gender is required  
            </span>  
        </div>  
        <div class="form-group row">  
            <label class="control-label col-md-12" for="Department">Department</label>  
            <div class="col-md-4">  
                <input class="form-control" type="text" formControlName="department">  
            </div>  
            <span class="text-danger" *ngIf="employeeForm.hasError('required', 'department') && formDir.submitted">  
                Department is required  
            </span>  
        </div>  
        <div class="form-group row">  
            <label class="control-label col-md-12" for="City">City</label>  
            <div class="col-md-4">  
                <select class="form-control" data-val="true" formControlName="city">  
                    <option value="">--Select City--</option>  
                    <option *ngFor="let city of cityList"  
                            value={{city.cityName}}>  
                        {{city.cityName}}  
                    </option>  
                </select>  
            </div>  
            <span class="text-danger" *ngIf="employeeForm.hasError('required', 'city') && formDir.submitted">  
                City is required  
            </span>  
        </div>  
        <div class="form-group">  
            <button type="submit" class="btn btn-default">Save</button>  
            <button class="btn" (click)="cancel()">Cancel</button>  
        </div>  
    </form>  
</body>  
</html>
Here you can observe that we have the attribute [formGroup]="employeeForm", which is our defined form group name in the addemployee.component.ts file.  "(ngSubmit)="save()" will invoke our save method upon form submission.
Also, every input control has the attribute formControlName="xyz", this is used to bind FormControl to our HTML. We have also defined error messages for the client-side validation check and they will be invoked upon form submission only.
For binding the dropdown list, we are using the cityList property that we have populated from the tblCities table by calling the getCityList method from our service inside the ngOnInit method of the addemployee.component.ts file.

Defining Route and Navigation Menu for Our Application

Open /app/app.shared.module.ts file and put the following code into it.
import { NgModule } from '@angular/core';  
import { EmployeeService } from './services/empservice.service'  
import { CommonModule } from '@angular/common';  
import { FormsModule, ReactiveFormsModule } from '@angular/forms';  
import { HttpModule } from '@angular/http';  
import { RouterModule } from '@angular/router';  
import { AppComponent } from './components/app/app.component';  
import { NavMenuComponent } from './components/navmenu/navmenu.component';  
import { HomeComponent } from './components/home/home.component';  
import { FetchEmployeeComponent } from './components/fetchemployee/fetchemployee.component'  
import { createemployee } from './components/addemployee/AddEmployee.component'  
@NgModule({  
    declarations: [  
        AppComponent,  
        NavMenuComponent,  
        HomeComponent,  
        FetchEmployeeComponent,  
        createemployee,  
    ],  
    imports: [  
        CommonModule,  
        HttpModule,  
        FormsModule,  
        ReactiveFormsModule,  
        RouterModule.forRoot([  
            { path: '', redirectTo: 'home', pathMatch: 'full' },  
            { path: 'home', component: HomeComponent },  
            { path: 'fetch-employee', component: FetchEmployeeComponent },  
            { path: 'register-employee', component: createemployee },  
            { path: 'employee/edit/:id', component: createemployee },  
            { path: '**', redirectTo: 'home' }  
        ])  
    ],  
    providers: [EmployeeService]  
})  
export class AppModuleShared {  
}
Here we have also imported all our components and defined the route for our application as below:
  • home - will redirect to the home component.
  • fetch-employee - to display all employee data using the fetchemployee component.
  • register-employee - to add a new employee record using the createemployee component.
  • employee/edit/:id - to edit an existing employee record using the createemployee component.
One last thing is to define the navigation menu for our application. Open the /app/components/navmenu/navmenu.component.html file and put the following code into it:
<div class='main-nav'>  
    <div class='navbar navbar-inverse'>  
        <div class='navbar-header'>  
            <button type='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>  
            <a class='navbar-brand' [routerLink]="['/home']">ASPCoreWithAngular</a>  
        </div>  
        <div class='clearfix'></div>  
        <div class='navbar-collapse collapse'>  
            <ul class='nav navbar-nav'>  
                <li [routerLinkActive]="['link-active']">  
                    <a [routerLink]="['/home']">  
                        <span class='glyphicon glyphicon-home'></span> Home  
                    </a>  
                </li>  
                <li [routerLinkActive]="['link-active']">  
                    <a [routerLink]="['/fetch-employee']">  
                        <span class='glyphicon glyphicon-th-list'></span> Fetch employee  
                    </a>  
                </li>  
            </ul>  
        </div>  
    </div>  
</div>
And that's it. We have created our first ASP.NET Core application using Angular 5 and Entity Framework Core's database first approach.

Execution Demo

Press F5 to launch the application.
A web page will open as shown in the image below. Notice the URL showing the route for our home component. And a navigation menu on the left showing the navigation link for the Home and Fetch Employee pages.
Click on Fetch Employee in the navigation menu. It will redirect you to the fetch employee component and display all the employee data on the page.
Since we have not added any data, it is empty.
Click on CreateNew to navigate to the /register-employee page. Add a new Employee record as shown in the image below. You can see that the City field is a dropdown list, containing all the city names that we have inserted into the tblCities table.
If we miss the data in any field while creating an employee record, we will get a required field validation error message.
After inserting the data in all the fields, click on the "Save" button. The new employee record will be created and you will be redirected to the /fetch-employee page, displaying records of all the employees. Here, we can also see the action methods, Edit and Delete.
If we want to edit an existing employee record, click the Edit action link. It will open the Edit page as below where we can change the employee data. Notice that we have passed an employee id in the URL parameter.
Here we have changed the city of the employee Rahul from Hyderabad to Chennai. Click on "Save" to return to the fetch-employee page to see the updated changes as highlighted in the image below:
If we miss any fields while editing employee records, then the Edit view will also throw a required field validation error message as shown in the image below:
Now, we will perform a Delete operation on an employee named Swati whose Employee ID is 2. Click on the Delete action link which will open a JavaScript confirmation box asking for a confirmation to delete.
Once we click on the Delete button the employee with the name Swati will be removed from our record, and you can see the updated list of employees as shown below.

Conclusion

We have successfully created an ASP.NET Core application using Angular 5 and Entity Framework Core's database first approach with the help of Visual Studio 2017 and SQL Server 2012. We have used Angular forms to get data from the user and also bind the dropdown list to a database table using the Entity Framework.

No comments:

Post a Comment

Angular Tutorial (Update to Angular 7)

As Angular 7 has just been released a few days ago. This tutorial is updated to show you how to create an Angular 7 project and the new fe...