Introduction
Angular 2 library contains eight TypeScript custom controls. Each control has its label and its validations. Grid and dropdown get the data dynamically using the API name. All the eight controls are listed below.
- textBox
- textbox-multiline
- date-picker
- dropdown
- grid
- checkbox
- radio.
- radio list
Also, the library contains base classes and common HTTP services.
- base-control-component
- base-form-component
- http-common-service
Prerequisites
- Visual Studio Community 2015 with Update 3 – Free
- .NET Core 1.1 SDK - Installer (dotnet-dev-win-x64.1.0.0-preview2-1-003177.exe)
- Typescript for visual studio 2015 (TypeScript_Dev14Full_2.1.4.0.exe)
- Node js 6.9
- Install node_modules in client folder in the project.
Open cmd.exe and then open client folder.
D:\> cd D:\ Angular2WithAspNetCoreWebAPI\src\Angular2WithAspNetCoreWebAPI\client
D:\ Angular2WithAspNetCoreWebAPI\src\Angular2WithAspNetCoreWebAPI\client> npm install - Start page
http://localhost:56245/client/index.html
Using the code
Examples for using the custom form controls.
Create a new component for student form client\app\components\student\student-form.component.ts that inherits from baseFormComponent to use, save, and load methods from it.
- import { Component, OnDestroy,OnInit } from '@angular/core';
- import { FlashMessagesService } from 'angular2-flash-messages';
- import { HttpCommonService } from '../../shared/services/http-common.service';
- import { BaseFormComponent } from '../../shared/controls/base/base-form.component';
- import { ActivatedRoute } from '@angular/router';
- @Component({
- moduleId: module.id,
- selector: 'student-form',
- templateUrl: 'student-form.template.html',
- providers: [HttpCommonService]
- })
- export class StudentFormComponent extends BaseFormComponent{
- //public strDate:string = "2010-10-25";
- constructor( _httpCommonService: HttpCommonService,
- flashMessagesService: FlashMessagesService,
- route: ActivatedRoute) {
- super(_httpCommonService, flashMessagesService, route, "Students"
- <div class="container">
- <div>
- <!--[hidden]="submitted"-->
- <h1>Student</h1>
- <form (ngSubmit)="save()" #from="ngForm">
- <textbox id="txtFirstMidName" name="txtFirstMidName" label="First-Mid Name" [(ngModel)]="model.firstMidName" required="true"></textbox>
- <textbox id="txtLastName" name="txtLastName" label="Last Name" [(ngModel)]="model.lastName" required="true"></textbox>
- <date-picker name="dpEnrollmentDate" id="dpEnrollmentDate" label="EnrollmentDate" [(ngModel)]="model.enrollmentDate" required="true"></date-picker>
- <dropdown name="ddlCourse" id="ddlCourse" label="Course" [(ngModel)]="model.course1ID" apiName="Courses" valueFieldName="courseID" textFieldName="title" required="true"></dropdown>
- <textbox-multiline id="txtStudentDescription" name="txtStudentDescription" label="Description" [(ngModel)]="model.description" required="true"></textbox-multiline>
- <radio-list name="elStudentType" id="studentType" [(ngModel)]="model.course2ID" valueFieldName="courseID" textFieldName="title" apiName="Courses" required="true"></radio-list>
- <radio id="rbMale" label="Male" name="rbgender" [(ngModel)]="model.gender" checked="true" val="1"></radio>
- <radio id="rbFemale" label="Female" name="rbgender" [(ngModel)]="model.gender" val="0"></radio>
- <checkbox id="chkHasCar" label="HasCar" name="chkHasCar" [(ngModel)]="model.hasCar"></checkbox>
- <button type="submit" class="btn btn-default" [disabled]="!from.form.valid">Save</button>
- <button type="button" class="btn btn-default" [disabled]="model.id>0" (click)="from.reset()">New</button>
- </form>
- </div>
- </div>
- <button type="submit" class="btn btn-default" [disabled]="!from.form.valid">Save</button>
Note - textType property in text box could be number, email, URL, tel.

When the controls are not empty, the "Save" button will be enabled and a green sign will appear in the control.

All these controls have common properties which are included in client\app\shared\controls\base\base-control.component.ts.
- label
- name
- id
- required
- hidden
- textType
- minLength
- maxLength
Example for using custom grid
Create a new component for students list client\app\components\student\student-list.component.ts.
- import { Component, Input } from '@angular/core';
- import { GridColumnModel } from '../../shared/controls/grid/grid-column.model';
- @Component({
- moduleId: module.id,
- selector: 'student-list',
- templateUrl: 'student-list.template.html'
- })
- export class StudentListComponent {
- @Input() columns: Array<GridColumnModel>;
- constructor() {
- this.columns = [
- new GridColumnModel({ name: 'LastName', modelName: 'lastName', label: 'Last Name' }),
- new GridColumnModel({ name: 'FirstMidName', modelName: 'firstMidName', label: 'FirstMid Name' }),
- new GridColumnModel({ name: 'EnrollmentDate', modelName: 'enrollmentDate', label: 'Enrollment Date' }),
- ]
- }
- }
- <grid [columns]="columns" apiName="Students" label="Student" name="student"></grid>

Controls Source code
BaseControlComponent
All custom controls inherit from BaseControlComponent to get common properties such as label, name, id, required, hidden, texttype, which are used for fixing nested controls' ngmodel binding issue in validation using steps in http://blog.rangle.io/angular-2-ngmodel-and-custom-form-components/
It also contains pattern objects for regular expression validations for email and url or any extra validations by adding regular expression for each textType (emial, URL, tel) which will be used in child controls html template.
- <input type="{{textType}}" pattern="{{patterns[textType]}}">
- import { Component, Optional,Inject,OnInit, Output, Input, AfterViewInit, AfterViewChecked, EventEmitter } from '@angular/core';
- import { NgModel } from '@angular/forms';
- import { Observable } from 'rxjs/Observable';
- import { ValueAccessorBase } from './value-accessor';
- import {
- AsyncValidatorArray,
- ValidatorArray,
- ValidationResult,
- message,
- validate,
- } from './validate';
- @Component({
- })
- export abstract class BaseControlComponent<T> extends ValueAccessorBase<T> implements OnInit{
- protected abstract model: NgModel;
- @Input() label: string;
- @Input() name: string;
- @Input() id: string;
- @Input() required: boolean = false;
- @Input() hidden: boolean = false;
- @Input() textType: string;
- @Input() minLength: string;
- @Input() maxLength: string;
- public patterns = {
- email: "([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+",
- url: "(https?)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"
- };
All input forms should be inherited from BaseFormComponent to get all CRUD operations, such as save [create or update], delete, reset form data to be in new mode and load model in edit mode if router has id param.
- import { Component, OnDestroy, OnInit ,Input} from '@angular/core';
- import { ActivatedRoute } from '@angular/router';
- import { HttpCommonService } from '../../services/http-common.service';
- import { FlashMessagesService } from 'angular2-flash-messages';
- @Component({
- moduleId: module.id,
- providers: [HttpCommonService]
- })
- export class BaseFormComponent {
- public apiName:string
- protected model = {};
- protected submitted = false;
- private sub: any;
- id: number;
- // onSubmit() { this.submitted = true; }
- // TODO: Remove this when we're done
- // get diagnostic() { return JSON.stringify(this.model); }
- constructor(private _httpCommonService: HttpCommonService,
- private flashMessagesService: FlashMessagesService,
- private route: ActivatedRoute, _apiName: string) {
- this.apiName = _apiName;
- this.sub = this.route.params.subscribe(params => {
- this.id = +params['id']; // (+) converts string 'id' to a number
- if (this.id > 0) {
- this._httpCommonService.getItem(this.apiName, this.id).subscribe(data => {
- this.model = data
- this.model["enrollmentDate"] = this.model["enrollmentDate"].substring(0, 10);
- });
- }
- });
- }
- ngOnInit() {
- //this.sub = this.route.params.subscribe(params => {
- // this.id = +params['id']; // (+) converts string 'id' to a number
- //this._httpCommonService.getItem("Students", this.id).subscribe(data => {
- // this.model = data
- //});
- // });
- }
- ngOnDestroy() {
- this.sub.unsubscribe();
- }
- reset() {
- this.id = 0;
- this.model = {};
- }
- save() {
- alert(JSON.stringify(this.model));
- if (this.id > 0) {
- this._httpCommonService.update(this.apiName, this.model).subscribe();
- }
- else {
- this._httpCommonService.create(this.apiName, this.model).subscribe();
- }
- this.flashMessagesService.show('success', { cssClass: 'alert-success' });//{ cssClass: 'alert-success', timeout: 1000 }
- //this.flashMessagesService.grayOut(true);
- this.submitted = true;
- }
- delete () {
- this._httpCommonService.delete(this.apiName, this.model["id"]);
- }
- }
It is used to centralize all http methods and to be an entry point for any request. It contains create, update, delete, getlist, and getItem methods. We have to set apiBaseUrl property to use it for all these methods.
- import { Injectable } from "@angular/core";
- import { Http, Response, ResponseContentType, Headers, RequestOptions, RequestOptionsArgs, Request, RequestMethod, URLSearchParams } from "@angular/http";
- //import { Observable } from 'rxjs/Observable';
- //import { Observable } from "rxjs/Rx";
- import { Observable } from 'rxjs/Rx'
- @Injectable()
- export class HttpCommonService {
- public apiBaseUrl: string;
- constructor(public http: Http) {
- this.http = http;
- this.apiBaseUrl = "/api/";
- }
- PostRequest(apiName: string, model: any) {
- let headers = new Headers();
- headers.append("Content-Type", 'application/json');
- let requestOptions = new RequestOptions({
- method: RequestMethod.Post,
- url: this.apiBaseUrl + apiName,
- headers: headers,
- body: JSON.stringify(model)
- })
- return this.http.request(new Request( requestOptions))
- .map((res: Response) => {
- if (res) {
- return [{ status: res.status, json: res.json() }]
- }
- });
- }
- requestOptions()
- {
- let contentType = 'application/json';//"x-www-form-urlencoded";
- let headers = new Headers({ 'Content-Type': contentType});
- let options = new RequestOptions({
- headers: headers,
- //body: body,
- // url: this.apiBaseUrl + apiName,
- // method: requestMethod,
- //responseType: ResponseContentType.Json
- });
- return options;
- }
- stringifyModel(model: any)
- {
- return JSON.stringify(model);
- }
- create(apiName: string, model: any) {
- // let headers = new Headers({ 'Content-Type': 'application/json' });
- // let options = new RequestOptions({ headers: headers });
- // let body = JSON.stringify(model);
- return this.http.post(this.apiBaseUrl + apiName,
- this.stringifyModel(model),
- this.requestOptions())
- .map(this.extractData) //.map((res: Response) => res.json())
- .catch(this.handleError)
- // .subscribe()
- ;
- }
- update(apiName:string,model: any) {
- let headers = new Headers({ 'Content-Type': 'application/json' });
- let options = new RequestOptions({ headers: headers });
- let body = JSON.stringify(model);
- return this.http.put(this.apiBaseUrl + apiName + '/' + model.id, body, options).map((res: Response) => res.json());//.subscribe();
- }
- delete(apiName:string,id:any) {
- return this.http.delete(this.apiBaseUrl + apiName + '/' + id);//.subscribe();;
- }
- getList(apiName: string) {
- return this.http.get(this.apiBaseUrl + apiName, { search: null })
- .map((responseData) => responseData.json());
- }
- getItem(apiName: string,id:number) {
- return this.http.get(this.apiBaseUrl + apiName + "/" + id, { search: null })
- .map((responseData) => responseData.json());
- }
- getLookup(lookupName: string, parentId: number, parentName: string) {
- var params = null;
- if (parentId != null) {
- params = new URLSearchParams();
- params.set(parentName, parentId.toString());
- }
- return this.http.get(this.apiBaseUrl +"lookup/" + lookupName, { search: params })
- .map((responseData) => responseData.json());
- }
- private extractData(res: Response) {
- let body = res.json();
- return body || {};
- }
- private handleError(error: Response | any) {
- // In a real world app, we might use a remote logging infrastructure
- let errMsg: string;
- if (error instanceof Response) {
- const body = error.json() || '';
- const err = body.error || JSON.stringify(body);
- errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
- } else {
- errMsg = error.message ? error.message : error.toString();
- }
- //console.error(errMsg);
- return Observable.throw(errMsg);
- }
- }
The page routes should be added in app.routes for add, edit and list. In edit, we path the id in the url client\app\app.routes.ts
- import { ModuleWithProviders } from '@angular/core';
- import { Routes, RouterModule } from '@angular/router';
- import { StudentFormComponent } from './components/student/student-form.component';
- import { StudentListComponent } from './components/student/student-list.component';
- // Route Configuration
- export const routes: Routes = [
- { path: 'student', component: StudentFormComponent },
- { path: 'student/:id', component: StudentFormComponent },
- { path: 'students', component: StudentListComponent},
- ];
- export const routing: ModuleWithProviders = RouterModule.forRoot(routes);
Add new pages links to app component client\app\app.component.template.html using [routerLink].
- <div id="wrapper">
- <!-- Sidebar -->
- <div id="sidebar-wrapper">
- <nav class="mdl-navigation">
- <ul class="sidebar-nav">
- <li class="sidebar-brand">
- <!--<a href="#">-->
- Student System
- <!--</a>-->
- </li>
- <li>
- <a class="mdl-navigation__link" [routerLink]="['/']">Dashboard</a>
- </li>
- <li>
- <a class="mdl-navigation__link" [routerLink]="['/student']">Add Student</a>
- </li>
- <li>
- <a class="mdl-navigation__link" [routerLink]="['/students']">List Students</a>
- </li>
- </ul>
- </nav>
- </div>
- <!-- /#sidebar-wrapper -->
- <!-- Page Content -->
- <div id="page-content-wrapper">
- <div class="container-fluid">
- <div class="row">
- <div class="col-lg-12">
- <router-outlet></router-outlet>
- </div>
- </div>
- </div>
- </div>
- <!-- /#page-content-wrapper -->
- </div>
Edit link will be in list form in the grid control.
- <td><a class="mdl-navigation__link" [routerLink]="['/'+name+'',item.id]">Edit</a></td>
- <td><a class="mdl-navigation__link" [routerLink]="['/'+name+'Details',item.id]">Details</a></td>
- import { NgModule } from '@angular/core';
- import { BrowserModule } from '@angular/platform-browser';
- import { FormsModule, ReactiveFormsModule, NG_VALIDATORS, NG_ASYNC_VALIDATORS, FormControl } from '@angular/forms';
- import { HttpModule, JsonpModule } from '@angular/http';
- import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
- import { requestOptionsProvider } from './shared/services/default-request-options.service';
- import { FlashMessagesModule } from 'angular2-flash-messages';
- import { DataTableModule } from "angular2-datatable";
- import { routing } from './app.routes';
- //validation-test
- import { DropdownComponent } from './shared/controls/dropdown/dropdown.component';
- import { RadioListComponent } from './shared/controls/radio/radio-list.component';
- import { TextboxComponent } from './shared/controls/textbox/textbox.component';
- import { TextboxMultilineComponent } from './shared/controls/textbox/textbox-multiline.component';
- import { DatePickerComponent } from './shared/controls/date/date-picker.component';
- import { CheckboxComponent } from './shared/controls/checkbox/checkbox.component';
- import { RadioComponent } from './shared/controls/radio/radio.component';
- import { GridComponent } from './shared/controls/grid/grid.component';
- import { ValidationComponent } from './shared/controls/validators/validation';
- import { StudentFormComponent } from './components/student/student-form.component';
- import { StudentListComponent } from './components/student/student-list.component';
- import { AppComponent } from './app.component';
- @NgModule({
- imports: [
- BrowserModule,
- FormsModule,
- ReactiveFormsModule,
- HttpModule,
- JsonpModule,
- routing,
- FlashMessagesModule,
- DataTableModule,
- ],
- declarations: [
- AppComponent,
- TextboxComponent,
- TextboxMultilineComponent,
- DatePickerComponent,
- CheckboxComponent,
- DropdownComponent,
- RadioListComponent,
- RadioComponent,
- GridComponent,
- ValidationComponent,
- StudentFormComponent,
- StudentListComponent,
- ],
- providers: [requestOptionsProvider],
- bootstrap: [AppComponent]
- })
- export class AppModule { }
TextboxComponent overrides NgModel to pass the validation from custom control to the original input.
- import { Component, ViewChild, Optional, Inject} from '@angular/core';
- import { BaseControlComponent } from '../base/base-control.component'
- import { NG_VALUE_ACCESSOR, NgModel, NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '@angular/forms';
- import { animations } from '../validators/animations';
- @Component({
- moduleId: module.id,
- selector: 'textbox',
- templateUrl: 'textbox.template.html'
- , animations
- , providers: [
- { provide: NG_VALUE_ACCESSOR, useExisting: TextboxComponent, multi: true}
- ]
- })
- export class TextboxComponent extends BaseControlComponent<string> {
- @ViewChild(NgModel) model: NgModel;
- constructor(
- @Optional() @Inject(NG_VALIDATORS) validators: Array<any>,
- @Optional() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<any>,
- ) {
- super(validators, asyncValidators);
- }
- }
- <div class="form-group">
- <label for="{{name}}">{{label}}</label>
- <input type="{{textType}}" class="form-control" id="{{name}}"
- required="{{required}}"
- [(ngModel)]="value" name="{{name}}"
- #txt="ngModel"
- pattern="{{patterns[textType]}}"
- maxlength="{{maxLength}}"
- minlength="{{minLength}}"
- hidden="{{hidden}}">
- <div *ngIf="txt.errors && (txt.dirty || txt.touched)"
- class="alert alert-danger">
- <div [hidden]="(!txt.errors.required)">
- {{label}} is required
- </div>
- <div [hidden]="!txt.errors.minlength">
- {{label}} must be at least 4 characters long.
- </div>
- <div [hidden]="!txt.errors.maxlength">
- {{label}} cannot be more than 24 characters long.
- </div>
- </div>
- <validation [@flyInOut]="'in,out'"
- *ngIf="invalid | async"
- [messages]="failures | async">
- </validation>
- </div>
TextboxMultilineComponent overrides NgModel to pass the validation from custom control to the original input.
- import { Component, ViewChild, Optional, Inject} from '@angular/core';
- import { NgModel, NG_VALUE_ACCESSOR, NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '@angular/forms';
- import { BaseControlComponent } from '../base/base-control.component'
- @Component({
- moduleId: module.id,
- selector: 'textbox-multiline',
- templateUrl: 'textbox-multiline.template.html', providers: [
- { provide: NG_VALUE_ACCESSOR, useExisting: TextboxMultilineComponent, multi: true }
- ]
- })
- export class TextboxMultilineComponent extends BaseControlComponent<string> {
- @ViewChild(NgModel) model: NgModel;
- constructor(
- @Optional() @Inject(NG_VALIDATORS) validators: Array<any>,
- @Optional() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<any>,
- ) {
- super(validators, asyncValidators);
- }
- }
- div class="form-group"><!--#txt="ngModel" (blur)="setValid(txt)"-->
- <label for="{{name}}">{{label}}</label>
- <textarea class="form-control" id="{{name}}"
- required="{{required}}"
- [(ngModel)]="value" name="{{name}}"
- pattern="{{patterns[textType]}}"
- #txt="ngModel"
- maxlength="{{maxLength}}"
- minlength="{{minLength}}"
- ></textarea>
- <div *ngIf="txt.errors && (txt.dirty || txt.touched)"
- class="alert alert-danger">
- <div [hidden]="(!txt.errors.required)">
- {{label}} is required
- </div>
- <div [hidden]="!txt.errors.minlength">
- {{label}} must be at least {{minlength}} characters long.
- </div>
- <div [hidden]="!txt.errors.maxlength">
- {{label}} cannot be more than {{maxlength}} characters long.
- </div>
- </div>
- </div>
DropdownComponent is used to override NgModel to pass the validation from custom control to the original input. It has apiName for the WebAPI service which will be used to load the select options, field name for option value, and field name for option text. On component init, it calls http common service and fills items arrays which is used in the template to load the select options.
- import { Component, OnInit, Inject, Output, Optional,Input, AfterViewInit, AfterViewChecked, EventEmitter, ViewChild} from '@angular/core';
- import { HttpCommonService } from '../../services/http-common.service';
- import { BaseControlComponent } from '../base/base-control.component'
- import { DropdownModel } from './dropdown.model';
- import { NgModel, NG_VALUE_ACCESSOR, NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '@angular/forms';
- import { animations } from '../validators/animations';
- @Component({
- moduleId: module.id,
- selector: 'dropdown',
- templateUrl: 'dropdown.template.html', animations,
- providers: [ {
- provide: NG_VALUE_ACCESSOR,
- useExisting: DropdownComponent,
- multi: true
- },HttpCommonService]
- })
- export class DropdownComponent extends BaseControlComponent<string> {
- @ViewChild(NgModel) model: NgModel;
- items: DropdownModel[];
- <div class="form-group">
- <label for="{{name}}">{{label}} </label>
- <select class="form-control" id="{{ name}}"
- name="{{ name}}"
- [(ngModel)]="value"
- hidden="{{hidden}}"
- #ddl="ngModel"
- required="{{required}}">
- <option value="">--Select--</option>
- <ng-content></ng-content>
- <option *ngFor="let item of items" [value]="item[valueFieldName]">{{item[textFieldName]}}</option>
- </select>
- <div [hidden]="(ddl.valid || ddl.pristine)" class="alert alert-danger">
- {{name}} is required
- </div>
- <validation [@flyInOut]="'in,out'"
- *ngIf="invalid | async"
- [messages]="failures | async">
- </validation>
- </div>
RadioListComponent is used to override NgModel to pass the validation from custom control to the original input. It has apiName for the WebAPI service which will used to load the radio list, field name for its value, and field name for its text. On component init, it calls http common service and fills items arrays which is used in the template to load the radio list.
- import { Component, OnInit, Optional, Input, ViewChild, Inject} from '@angular/core';
- import { HttpCommonService } from '../../services/http-common.service';
- import { RadioListModel } from './radio-list.model';
- import { BaseControlComponent } from '../base/base-control.component'
- import { NgModel, NG_VALUE_ACCESSOR, NG_VALIDATORS, NG_ASYNC_VALIDATORS } from '@angular/forms';
- @Component({
- moduleId :module.id ,
- selector: 'radio-list',
- templateUrl: 'radio-list.template.html',
- providers: [{
- provide: NG_VALUE_ACCESSOR,
- useExisting: RadioListComponent,
- multi: true
- },HttpCommonService]
- })
- export class RadioListComponent extends BaseControlComponent<string>{
- @ViewChild(NgModel) model: NgModel;
- items: RadioListModel[];
- @Input() apiName: string;
- @Input() valueFieldName: string;
- @Input() textFieldName: string;
- constructor(private _httpCommonService: HttpCommonService,
- @Optional() @Inject(NG_VALIDATORS) validators: Array<any>,
- @Optional() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<any>,
- ) {
- super(validators, asyncValidators);
- }
- ngOnInit() {
- super.ngOnInit();
- if (this.apiName != null) {
- this._httpCommonService.getList(this.apiName).subscribe(data => {
- this.items = data
- });
- }
- }
- }
- export class RadioListModel {
- constructor(public id: number, public name: string,public checked:boolean) { }
- }
- <div class="form-group"> <!--#rbl="ngModel"-->
- <label for="{{name}}">{{label}} </label>
- <div *ngFor="let item of items">
- <label>
- <input type="radio" id="{{name}}"
- name="{{name}}"
- [value]="item[valueFieldName]"
- [(ngModel)]="value"
- required="{{required}}"
- [checked]="item[valueFieldName] === value"
- #rbl="ngModel"
- >
- <span>{{ item[textFieldName] }}</span>
- </label>
- <div [hidden]="rbl.valid || rbl.pristine" class="alert alert-danger">
- {{name}} is required
- </div>
- </div>
- </div>
GridComponent has apiName for the WebAPI service which issued to load the data in the grid and array of grid columns and each column has name, label, model name properties. On component init, it calls http common service and fills data in the grid. Also, it handles the filtering feature using query property and getdata method.
- import { Component, OnInit, Output, Input, AfterViewInit, AfterViewChecked, EventEmitter } from '@angular/core';
- import { HttpCommonService } from '../../services/http-common.service';
- import { GridColumnModel } from './grid-column.model';
- @Component({
- moduleId: module.id,
- selector: 'grid',
- templateUrl: 'grid.template.html',
- providers: [HttpCommonService]
- })
- export class GridComponent implements OnInit {
- data: any;
- @Input() name: string;
- @Input() apiName: string;
- @Input() columns: Array<GridColumnModel>;
- @Input() enableFilter = true;
- query = "";
- filteredList:any;
- constructor(private _httpCommonService: HttpCommonService) {
- }
- getData() {
- if (this.query !== "") {
- return this.filteredList;
- } else {
- return this.data;
- }
- }
- filter() {
- this.filteredList = this.data.filter(function (el:any) {
- var result = "";
- for (var key in el) {
- result += el[key];
- }
- return result.toLowerCase().indexOf(this.query.toLowerCase()) > -1;
- }.bind(this));
- }
- ngOnInit() {
- if (this.columns == null)
- {
- this.columns = [
- new GridColumnModel({ name: 'name', modelName: 'name', label: 'name' }),
- ]
- }
- this._httpCommonService.getList(this.apiName).subscribe(data => {
- this.data = data
- });
- }
- }
- export class GridColumnModel {
- // value: T;
- name: string;
- label: string;
- order: number;
- modelName: string;
- constructor(options: {
- // value?: T,
- name?: string,
- label?: string,
- order?: number,
- modelName?: string,
- } = {}) {
- //this.value = options.value;
- this.name = options.name || '';
- this.label = options.label || '';
- this.order = options.order === undefined ? 1 : options.order;
- this.modelName = options.modelName || '';
- }
- }
- <div>
- <a class="mdl-navigation__link" [routerLink]="['/'+name]">New {{name}}</a>
- </div>
- <label for="filter">Filter</label>
- <input name="filter" id="filter" type="text" class="form-control" *ngIf=enableFilter [(ngModel)]=query
- (keyup)=filter() placeholder="Filter" />
- <table class="table table-striped" [mfData]="getData()" #mf="mfDataTable" [mfRowsOnPage]="5" hidden="{{hidden}}">
- <thead>
- <tr>
- <th *ngFor="let colum of columns">
- <mfDefaultSorter by="{{colum.modelName}}">{{colum.label}}</mfDefaultSorter>
- </th>
- </tr>
- </thead>
- <tbody>
- <tr *ngFor="let item of mf.data">
- <td *ngFor="let colum of columns">
- {{item[colum.modelName] ? (item[colum.modelName].name? item[colum.modelName].name : item[colum.modelName]): 'N/A'}}
- </td>
- <td><a class="mdl-navigation__link" [routerLink]="['/'+name+'',item.id]">Edit</a></td>
- </tr>
- </tbody>
- <tfoot>
- <tr>
- <td colspan="4">
- <mfBootstrapPaginator [rowsOnPageSet]="[5,10,25]"></mfBootstrapPaginator>
- </td>
- </tr>
- </tfoot>
- </table>
DatePickerComponent overrides NgModel to pass the validation from custom control to the original input.
- import { Component, Optional, Inject, OnInit, ViewChild} from '@angular/core';
- import { BaseControlComponent } from '../base/base-control.component'
- import { NgModel, NG_VALIDATORS, NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms';
- @Component({
- moduleId: module.id,
- selector: 'date-picker',
- templateUrl: 'date-picker.template.html' ,
- providers: [
- { provide: NG_VALUE_ACCESSOR, useExisting: DatePickerComponent, multi: true }
- ]
- })
- export class DatePickerComponent extends BaseControlComponent<string> {
- @ViewChild(NgModel) model: NgModel;
- constructor(
- @Optional() @Inject(NG_VALIDATORS) validators: Array<any>,
- @Optional() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<any>,
- ) {
- super(validators, asyncValidators);
- }
- }
- <div class="form-group" >
- <label for="name">{{label}}</label>
- <input type="date" class="form-control" id="{{name}}"
- required="{{required}}"
- [(ngModel)]="value" name="{{name}}"
- >
- </div>
RadioComponent overrides NgModel to pass the validation from custom control to the original input and it contains checked and val properties.
- import { Component, Optional,Inject,ViewChild, OnInit, Output, Input, AfterViewInit, AfterViewChecked, EventEmitter } from '@angular/core';
- import { BaseControlComponent } from '../base/base-control.component'
- import { NgModel, NG_VALIDATORS, NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR} from '@angular/forms'
- @Component({
- moduleId: module.id,
- selector: 'radio',
- templateUrl: 'radio.template.html',
- providers: [
- { provide: NG_VALUE_ACCESSOR, useExisting: RadioComponent, multi: true }
- ]
- })
- export class RadioComponent extends BaseControlComponent<string>{
- @ViewChild(NgModel) model: NgModel;
- @Input() checked: boolean = false;
- @Input() val:string
- constructor(
- @Optional() @Inject(NG_VALIDATORS) validators: Array<any>,
- @Optional() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<any>,
- ) {
- super(validators, asyncValidators);
- }
- }
Note
When I set the val property name to be value, it returns on, instead of the right value, so I changed its name to be val.
- <div class="form-group">
- <label for="{{name}}">{{label}}</label>
- <input #rb
- id="{{id}}"
- name="{{name}}"
- [value]="val"
- type="radio"
- [checked]="value == rb.value"
- (click)="value = rb.value"
- >
- </div>
CheckboxComponent overrides NgModel to pass the validation from custom control to the original input and it contains checked property.
- import { Component, OnInit, Inject,Optional,Output, ViewChild, Input, AfterViewInit, AfterViewChecked, EventEmitter } from '@angular/core';
- import { BaseControlComponent } from '../base/base-control.component'
- import { NgModel, NG_VALIDATORS, NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR} from '@angular/forms'
- @Component({
- moduleId: module.id,
- selector: 'checkbox',
- templateUrl: 'checkbox.template.html',
- providers: [
- { provide: NG_VALUE_ACCESSOR, useExisting: CheckboxComponent, multi: true }
- ]
- })
- export class CheckboxComponent extends BaseControlComponent<string>{
- @ViewChild(NgModel) model: NgModel;
- constructor(
- @Optional() @Inject(NG_VALIDATORS) validators: Array<any>,
- @Optional() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<any>,
- ) {
- super(validators, asyncValidators);
- }
- }
- <div class="form-group">
- <label for="{{name}}">{{label}}</label>
- <input type="checkbox"
- id="{{name}}"
- [(ngModel)]="value" name="{{name}}"
- #chk="ngModel"
- hidden="{{hidden}}"
- >
- <div [hidden]="chk.valid || chk.pristine"
- class="alert alert-danger">
- {{name}} is required
- </div>
- </div>
The ASP.NET Core Web API is used by following the steps in https://docs.microsoft.com/en-us/aspnet/core/data/ef-mvc/intro and changing MVC Controllers to Web API Controllers and add the below configurations to Startup.cs.
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddDbContext<SchoolContext>(options =>
- options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
- public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, SchoolContext context)
- {
- app.UseStaticFiles(new StaticFileOptions()
- {
- FileProvider = new PhysicalFileProvider(
- Path.Combine(Directory.GetCurrentDirectory(), @"client")),
- RequestPath = new PathString("/client")
- });
- DbInitializer.Initialize(context);
"ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database= Angular2WithAspNetCoreWebAPI;Trusted_Connection=True;MultipleActiveResultSets=true" }
DbInitializer.Initialize used add dummy data in database to test the grid
The source code for the project is attached on top .
Points of Interest
Using TypeScript and Angular 2 to build custom controls library that will be easier than writing label and validation message for each control on the screen. Also, use the inheritance concept by adding base control and base form. In addition, fixing binding issue in base control, and adding crud operations once in base form, fill grid by setting its API name and its columns list, as well as fill dropdown list by setting its API name, textFieldName, and valueFieldName.
References
- https://angular.io/docs/ts/latest/tutorial/
- https://angular.io/docs/ts/latest/tutorial/toh-pt1.html
- http://blog.rangle.io/angular-2-ngmodel-and-custom-form-components/
- https://www.themarketingtechnologist.co/building-nested-components-in-angular-2/
- https://blog.thoughtram.io/angular/2016/10/13/two-way-data-binding-in-angular-2.html
- https://github.com/mariuszfoltak/angular2-datatable
George VighPosted May 16, 2017, 6:15 AM
I get this: TypeError: this._rootRenderer.document.querySelector is not a functionat DomRenderer.selectRootElement
Muhammad Aqib ShehzadPosted Feb 1, 2017, 1:51 AM
Nice piece of article and good sharing