Create a Simple CRUD App Using ASP .NET Core and Angular 2 – Part 2

Arif Farwanto | June 15th, 2017

In Create a Simple CRUD App Using ASP .NET Core and Angular 2 – Part 1 we discussed the back-end service. In this post, we will review the front-end development for the Simple CRUD Application. We will use Angular 2 as a front-end framework. We will use Typescript to write the front-end code, and we also include Webpack so we can easily bundle our files. To include the required libraries, we add package.json file in our project and list those libraries.

package.json


{
  "version": "1.0.0",
  "name": "asp.net",
  "private": true,
  "scripts": {
    "build": "webpack",
    "start": "static ."
  },
  "dependencies": {
    "@angular/core": "2.4.0",
    "@angular/common": "2.4.0",
    "@angular/compiler": "2.4.0",
    "@angular/platform-browser": "2.4.0",
    "@angular/platform-browser-dynamic": "2.4.0",
    "core-js": "^2.4.1",
    "zone.js": "^0.8.4",
    "rxjs": "^5.2.0",
    "reflect-metadata": "^0.1.10"
  },
  "devDependencies": {
    "gulp": "^3.9.1",
    "gulp-exec": "^2.1.3",
    "awesome-typescript-loader": "^3.1.2",
    "typings": "^2.1.0",
    "webpack": "^2.2.1"
  }
}

File and folder structure

CRUD App folder structure

We create an ‘App’ folder for our Angular application and put the model classes into a ‘Models’ folder. We also need to make sure the field name on each class model matches our model in the back-end service.

Angular Application – Module, Component, Template and Service

In Angular 2 we need to create a module that contains declarations of components, services or other sub-components. The module acts like a container that wraps all the components that work in the same scope. After that, we create a component and a template that we named app.component and app.html. In the template we add html markup for the table and form used for inputting data. Angular 2 provides two models for form handling: Template-Driven-Form and Model-Driven-Form. In this application we use Model Driven Form which gives more control and can be tested later through unit testing.

app.module.ts


import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from "@angular/forms";
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { AppService } from "./app.service";

@NgModule({
    imports: [
        BrowserModule, HttpModule, ReactiveFormsModule
    ],
    declarations: [AppComponent],
    bootstrap: [AppComponent],
    providers: [AppService]
})

export class AppModule { }

app.component.ts


import { Component, OnInit, OnDestroy } from "@angular/core";
import { IProduct } from "./Models/product.interface";
import { ICategory } from "./Models/category.interface";
import { ISupplier } from "./Models/supplier.interface";
import { AppService } from "./app.service";
import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms';

@Component({
    selector: 'my-app',
    template: require('./app.html')
})

export class AppComponent {
    products: IProduct[] = [];
    categories: ICategory[] = [];
    suppliers: ISupplier[] = [];
    formLabel: string;
    isEditMode = false;
    form: FormGroup;
    product: IProduct = {};

    constructor(private appService: AppService, private formBuilder: FormBuilder) {
        this.form = formBuilder.group({
            "name": ["", Validators.required],
            "quantity": ["", [Validators.required, Validators.pattern("^[0-9]*$")]],
            "price": ["", [Validators.required, Validators.pattern("^[0-9]*$")]],
            "category": ["", Validators.required],
            "supplier": ["", Validators.required]
        });

        this.formLabel = "Add Product";
    }

    ngOnInit() {
        this.getProducts();

        this.appService.getCategories().subscribe(categories => {
            this.categories = categories;
        });

        this.appService.getSuppliers().subscribe(suppliers => {
            this.suppliers = suppliers;
        });
    }

    onSubmit() {
        this.product.name = this.form.controls['name'].value;
        this.product.quantity = this.form.controls['quantity'].value;
        this.product.price = this.form.controls['price'].value;
        this.product.supplierId = this.form.controls['supplier'].value;
        this.product.categoryId = this.form.controls['category'].value;
        if (this.isEditMode) {
            this.appService.editProduct(this.product).subscribe(response => {
                this.getProducts();
                this.form.reset();
            });
        } else {
            this.appService.addProduct(this.product).subscribe(response => {
                this.getProducts();
                this.form.reset();
            });
        }   
    }

    cancel() {
        this.formLabel = "Add Product";
        this.isEditMode = false;
        this.product = {};
        this.form.get("name").setValue('');
        this.form.get("quantity").setValue('');
        this.form.get('price').setValue('');
        this.form.get('supplier').setValue(0);
        this.form.get('category').setValue(0); 
    }

    edit(product: IProduct) {
        this.formLabel = "Edit Product";
        this.isEditMode = true;
        this.product = product;
        this.form.get("name").setValue(product.name);
        this.form.get("quantity").setValue(product.quantity); 
        this.form.get('price').setValue(product.price); 
        this.form.get('supplier').setValue(product.supplierId); 
        this.form.get('category').setValue(product.categoryId); 
    }

    delete(product: IProduct) {
        if (confirm("Are you sure want to delete this?")) {
            this.appService.deleteProduct(product.productId).subscribe(response => {
                this.getProducts();
                this.form.reset();
            });
        }
    }

    private getProducts() {
        this.appService.getProducts().subscribe(products => {
            this.products = products;
        });
    }
}

app.html


<section>
    <div class="row">
        <div class="col-xs-12 col-sm-8 col-md-10 table-responsive">
            <h1>Product</h1>
            <table class="table">
                <thead>
                    <tr>
                        <td>Product ID</td>
                        <td>Name</td>
                        <td>Quantity</td>
                        <td>Price</td>
                        <td>Category</td>
                        <td>Supplier</td>
                        <td> </td>
                    </tr>
                </thead>
                <tbody>
                    <tr *ngFor="let product of products">
                        <td>{{ product.productId }}</td>
                        <td>{{ product.name }}</td>
                        <td>{{ product.quantity }}</td>
                        <td>{{ product.price }}</td>
                        <td>{{ product.categoryName }}</td>
                        <td>{{ product.supplierName }}</td>
                        <td><button (click)="edit(product)">Edit</button> | <button (click)="delete(product)">Delete</button></td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</section>
<section>
    <div class="row">
        <div class="col-xs-12 col-sm-8 col-md-10">
            <h2>{{ formLabel }}</h2>
            <form [formGroup]="form" (ngSubmit)="onSubmit()">
                <div class="form-horizontal">
                    <div class="form-group">
                        <label class="control-label col-xs-12 col-sm-1">Name:</label>
                        <div class="col-xs-12 col-sm-6">
                            <input class="form-control" type="text" formControlName="name">
                        </div>
                    </div>
                    <div class="form-group">
                        <label class="control-label col-xs-12 col-sm-1">Quantity:</label>
                        <div class="col-xs-12 col-sm-6">
                            <input class="form-control" type="text" formControlName="quantity">
                        </div>
                    </div>
                    <div class="form-group">
                        <label class="control-label col-xs-12 col-sm-1">Price:</label>
                        <div class="col-xs-12 col-sm-6">
                            <input class="form-control" type="text" formControlName="price">
                        </div>
                    </div>
                    <div class="form-group">
                        <label class="control-label col-xs-12 col-sm-1">Category:</label>
                        <div class="col-xs-12 col-sm-6">
                            <select class="form-control" formControlName="category">
                                <option *ngFor="let category of categories" value="{{ category.categoryId }}">{{ category.categoryName }}</option>
                            </select>
                        </div>
                    </div>
                    <div class="form-group">
                        <label class="control-label col-xs-12 col-sm-1">Supplier:</label>
                        <div class="col-xs-12 col-sm-6">
                            <select class="form-control" formControlName="supplier">
                                <option *ngFor="let supplier of suppliers" value="{{ supplier.supplierId }}">{{ supplier.name }} - {{ supplier.contactName }}</option>
                            </select>
                        </div>
                    </div>
                    <div class="form-footer col-xs-12 col-sm-3">
                        <button type="submit" [disabled]="!form.valid">Submit</button>
                        <button type="button" (click)="cancel()">Cancel</button>
                    </div>
                </div>
            </form>
        </div>
    </div>
</section>

Because we need a class that has functionality to get the data from the backend service, we create a Service. Service is typically a class with a well-defined purpose and specific feature. In the Service class, you can manage the data flow from the backend service by using the Observable variable from the RxJs library, along with the help of an Http module from Angular 2 to communicate with the server.

app.service.ts


import { Injectable } from "@angular/core";
import { Http, Response } from "@angular/http";
import { Observable } from "rxjs/Observable";
import 'rxjs/add/operator/map';
import { IProduct } from "./Models/product.interface";
import { ICategory } from "./Models/category.interface";
import { ISupplier } from "./Models/supplier.interface";

@Injectable()
export class AppService {
    constructor(private http: Http) { }

    getProducts() {
        return this.http.get("/api/product").map(data => <IProduct[]>data.json());
    }

    addProduct(product: IProduct) {
        return this.http.post("/api/product", product);
    }

    editProduct(product: IProduct) {
        return this.http.put(`/api/product/${product.productId}`, product);
    }

    deleteProduct(productId: number) {
        return this.http.delete(`/api/product/${productId}`);
    }

    getCategories() {
        return this.http.get("/api/category").map(data => <ICategory[]>data.json());
    }

    getSuppliers() {
        return this.http.get("/api/supplier").map(data => <ISupplier[]>data.json());
    }
}

Final Step

The next step is to add a function call bootstrapModule() as a starting point in running the application.

main.ts


import 'core-js';
import 'zone.js/dist/zone';

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './App/app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

To be able to run the application we still need to set the webpack’s config. The important thing to note is that entry should be directed to main.ts.

webpack.dev.js


module.exports = () => {
    return {
        entry: {
            main: './Scripts/main.ts'
        },
        output: {
            path: './wwwroot',
            filename: 'bundle.js'
        },
        resolve: {
            extensions: ['.js', '.ts', '.html']
        },
        module: {
            rules: [
                {
                    test: /\.ts$/,
                    loaders: ['awesome-typescript-loader'],
                    exclude: [/\.(spec|e2e)\.ts$/]
                },
                {
                    test: /\.html$/,
                    loader: 'raw-loader'
                }
            ]
        },
        devtool: 'inline-source-map'
    };
};

The final step is to add the output file from Webpack to index.cshtml and add our angular app markup.

index.cshtml


@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<my-app></my-app>
<script src="~/bundle.js"></script>
bootstrap module webpack angular app markup

 
The complete code can be found at this link.

Subscribe

* indicates required
Arif Farwanto

Arif is a developer at Palador. He is a Microsoft Certified Professional in Implementing Microsoft Azure Infrastructure Solutions.