CRUDing with Angular 6 and WebAPI2

Prerequisites

A Quick Recap of Our WebAPI Back-End

USE MASTER
IF EXISTS(SELECT * FROM SYS.DATABASES WHERE NAME='UNIVERSITY_TEST')
DROP DATABASE UNIVERSITY_TEST;
CREATE DATABASE UNIVERSITY_TEST;USE UNIVERSITY_TEST;DROP TABLE IF EXISTS STUDENTS;CREATE TABLE STUDENTS (
ID INT IDENTITY(1,1) PRIMARY KEY,
FIRST_NAME VARCHAR(50) NOT NULL,
LAST_NAME VARCHAR(50) NOT NULL,
)
INSERT INTO STUDENTS (FIRST_NAME, LAST_NAME) VALUES
('DOMINIC','ROMANELLI'),
('RANA','HAUS'),
('KENDRA','DEMARS'),
('MARCELENE','LEFFLER'),
('SHWANTA','BURGHART');
[EnableCors(origins: "http://localhost:4200", headers: "Accept, Origin, Content-Type", methods: "PUT,OPTIONS,GET,POST,DELETE")]

Setting Up the Angular Environment

npm install -g @angular/cli
ng new angular-university
ng serve --open

Configuring Our Angular Front-End

<h1>{{title}}</h1>
<app-students></app-students>
ng generate component students
ng generate service uni-crud.service.js
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { Student } from './student';
import { catchError, map } from 'rxjs/operators';
import { HttpHeaders } from '@angular/common/http';
const baseUrl = 'http://localhost:63718/';const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
@Injectable({
providedIn: 'root'
})
export class UniCrudService {studentList = null;constructor(private http: HttpClient) { }getStudents(): Observable<Student[]> {
return this.http.get<Student[]>(baseUrl + 'api/student', httpOptions)
.pipe(
catchError(this.handleError('getStudents', []))
);
}
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
// TODO: send the error to remote logging infrastructure
console.error(error); // log to console instead
// Let the app keep running by returning an empty result.
return of(result as T);
};
}
}
export class Student {
id: number = -1;
first_name: string = '';
last_name: string = '';
// Have not defined a structure for subject registrations.
}
<table>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
</tr>
<tr *ngFor="let student of studentList">
<td>{{student.id}}</td>
<td>{{student.first_name}}</td>
<td>{{student.last_name}}</td>
</tr>
</table>
Students being displayed on our front end

Routing, Navigation and Some Styling

ng generate module app-routing --flat --module=app
import { RouterModule, Routes } from '@angular/router';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { StudentManagementComponent } from './student-management/student-management.component';
import { StudentsComponent } from './students/students.component';
const routes: Routes = [
{ path: 'student/:id', component: StudentManagementComponent },
{ path: 'student/create', component: StudentManagementComponent },
{ path: 'student', component: StudentsComponent },
{ path: '', redirectTo: '/student', pathMatch: 'full' }
];
@NgModule({
exports: [RouterModule],
imports: [RouterModule.forRoot(routes)]
})
export class AppRoutingModule { }
<table>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
<th></th>
</tr>
<tr *ngFor="let student of studentList">
<td>{{student.id}}</td>
<td>{{student.first_name}}</td>
<td>{{student.last_name}}</td>
<td>
<a routerLink="/student/{{student.id}}">Edit</a>
</td>
</tr>
</table>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="student">{{title}}</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="student">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="student">View Students</a>
</li>
</ul>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<router-outlet></router-outlet>
</div>
</div>
</div>
<a class="nav-link" href="student/create">Create New Student</a>
Our new navigation and style in action.

Editing and Updating Students

The Setup

ng generate component student-management
formBuilder.group({studentData: formBuilder.group(new Student())});
import { ReactiveFormsModule } from '@angular/forms';
...
imports: [
...
ReactiveFormsModule
],
import { FormGroup, FormBuilder } from '@angular/forms';
constructor(
private formBuilder: FormBuilder) {
this.studentForm = this.createFormGroupWithBuilderAndModel(formBuilder);
}
export class StudentManagementComponent implements OnInit {
studentForm: FormGroup;
...createFormGroupWithBuilderAndModel(formBuilder: FormBuilder) {
return formBuilder.group({
studentData: formBuilder.group({
id: { value: -1, disabled: true },
first_name: '',
last_name: ''
})
});
}
<form [formGroup]="studentForm" (ngSubmit)="onSubmit(studentForm)" novalidate>
<div formGroupName="studentData" novalidate>
<div class="form-group" *ngIf="studentForm.get('studentData').get('id').value !== -1" >
<label for="id">ID</label>
<input formControlName="id" class="form-control" placeholder="ID">
</div>
<div class="form-group">
<label for="first_name">First Name</label>
<input formControlName="first_name" class="form-control" placeholder="First Name eg Luke">
</div>
<div class="form-group">
<label for="last_name">Last Name</label>
<input formControlName="last_name" class="form-control" placeholder="Last Name eg. Lindner">
</div>
</div>
<button type="submit" [disabled]="studentForm.pristine" class="btn btn-primary">{{studentForm.get('studentData').get('id').value !== -1 ? 'Update' : 'Create'}}</button>
<button type="reset" (click)="revert()" [disabled]="studentForm.pristine" class="btn btn-secondary">Revert</button>
</form>
<div>Form Value: {{studentForm.value| json}}</div>
<div>Current Student: {{currentStudent | json}}</div>
import { ActivatedRoute } from '@angular/router';
...
export class StudentManagementComponent implements OnInit {
...
constructor(
...
private route: ActivatedRoute,
...
}
ngOnInit() {
this.getStudent();
}
getStudent(): void {
const id = +this.route.snapshot.paramMap.get('id');
if (id) {
this.uniCrudService.getStudent(id)
.subscribe(student => {
this.currentStudent = student;
this.studentForm.get('studentData').setValue({
id: this.currentStudent.id,
first_name: this.currentStudent.first_name,
last_name: this.currentStudent.last_name
});
});
} else if (this.currentStudent === undefined) {
this.currentStudent = new Student();
}
}
...
}
this.studentForm.get('studentData').setValue(this.currentStudent);

Persisting Students to Our Back-End

addStudent(student: Student): Observable<Student> {
return this.http.post<Student>(baseUrl + 'api/student', student, httpOptions)
.pipe(
catchError(this.handleError<Student>('addStudent'))
);
}
updateStudent(student: Student): Observable<any> {
return this.http.put<any>(baseUrl + `api/student/${student.id}`, student, httpOptions)
.pipe(
map ( response => {
console.log('updateStudent(Student): I\'m emitting this value:', response);
if (response.status !== 204) {
throw new Error('Update Student request has failed with response: ' + response.status);
}
}),
catchError(this.handleError<any>(`updateStudent id=${student.id}`))
);
}
Putstudents return object on success
[ResponseType(typeof(void))]
...
import { ActivatedRoute, Router } from '@angular/router';
...constructor (
...
private router: Router
)
...onSubmit(studentForm) {
this.currentStudent = studentForm.getRawValue().studentData;
if (this.currentStudent.id === -1) {
let submitValue;
this.uniCrudService.addStudent(this.currentStudent).subscribe(_val => submitValue = _val);
console.log(submitValue);
} else {
this.uniCrudService.updateStudent(this.currentStudent).subscribe();
}
this.router.navigate(['/student']);
}
revert() {
// Resets to blank object
this.studentForm.reset();
// Resets to provided model
this.studentForm.reset({ student: new Student(), requestType: '' });
}
this.currentStudent = <Student>this.studentForm.controls.studentData.value;
this.currentStudent.id = this.studentForm.get('studentData').get('id').value;

Deleting Students

deleteStudent(id: number): Observable<Student> {
return this.http.delete<Student>(baseUrl + `api/student/${id}`, httpOptions)
.pipe(
tap(n => console.log('deleteStudent: I\'m emitting this value:', n)),
catchError(this.handleError<Student>(`updateStudent id=${id}`))
);
}
<td>
<a routerLink="/student/{{student.id}}">Edit</a> | <a routerLink="" (click)="deleteStudent(student.id)">Delete</a>
</td>
deleteStudent(id: number) {
this.uniCrudService.deleteStudent(id)
.subscribe(res => {
console.log(`deleteStudent: ${JSON.stringify(res)}`);
this.getStudents();
});
}

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Luke Lindner

Luke Lindner

Bolognaise even your Mum would be proud of