TechNet Guru Awards February 2019 - Miscellaneous - SILVER
I won the SILVER medal for one of my article on TechNet Guru competition
Source Code
GutHub : angular-zingchart
npm i zingchart
Go to package.json file, you can see zingchart references has been added into dependencies section.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>BMI Calculator</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
<script src= "https://cdn.zingchart.com/zingchart.min.js"></script>
</body>
</html>
<div class="container">
<form>
<div class="form-row">
<div class="form-group col-md-6">
<label for="height">Height (cm)</label>
<input type="number" class="form-control" id="height" name="height"
placeholder="Height" [(ngModel)]="height" (change)="onValueChange()"/>
</div>
<div class="form-group col-md-6">
<label for="weight">Weight (kg)</label>
<input type="number" class="form-control" id="weight" name="weight"
placeholder="Weight" [(ngModel)]="weight" (change)="onValueChange()"/>
</div>
</div>
<div class="form-group">
<app-chart></app-chart>
</div>
</form>
</div>
You can see app.component typescript file as below, it initially passes weight and height values as 0. When text box values changes, call onValueChange event and calculate the BMI value and render it in the zingchart control
import { Component, OnInit } from '@angular/core';
import { ChartComponent } from './shared/app.chart';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers:[ChartComponent]
})
export class AppComponent implements OnInit{
weight : number;
height : number;
bmi : string;
constructor(private chartComponent : ChartComponent){}
ngOnInit() {
this.weight = 0;
this.height = 0;
}
onValueChange() {
var bmi = (this.weight / ((this.height / 100) * (this.height / 100)));
if (isNaN(bmi) || bmi < 10)
bmi = 10;
else if (bmi > 40)
bmi = 40;
this.bmi = bmi.toFixed(2);
this.chartComponent.ChangeChartValue(this.bmi);
}
}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import {ChartComponent} from './shared/app.chart';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent,
ChartComponent
],
imports: [
BrowserModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
<div id="{{chart.id}}"></div>
import { Component, AfterViewInit } from '@angular/core';
declare var zingchart: any;
@Component({
selector: 'app-chart',
templateUrl: './app.chart.html',
styleUrls: ['./app.chart.scss']
})
export class ChartComponent implements AfterViewInit {
chart: Chart = {
id: "chart-1",
data: {
"type": "gauge",
"scale-r": {
"aperture": 200,
"values": "10:40:1",
"center": { "size": 10, "background-color": "#000", "border-color": "none" },
"guide": { "visible": true },
"item": { "visible": true },
"tick": { "visible": true },
"ring": {
"size": 20,
"rules": [
{ "rule": "%v >= 10 && %v <= 20", "background-color": "#f6f34a" },
{ "rule": "%v >= 20 && %v <= 28", "background-color": "#7cfd45" },
{ "rule": "%v >= 28 && %v <= 32", "background-color": "#f79333" },
{ "rule": "%v >= 32 && %v <= 40", "background-color": "#f30c22" },
]
}
},
"plot": { "csize": "8%", "size": "90%", "background-color": "#000000" },
"series": [ { "values": [ 25 ] } ]
},
height: 170,
width: 170
};
ngAfterViewInit() {
zingchart.render(this.chart);
}
ChangeChartValue (value){
zingchart.exec('chart-1', 'setseriesvalues', {
plotindex : 0,
values : [Number(value)]
});
}
}
interface Chart {
id:string;
data : {};
height : number;
width : number;
}
import {PhoneValidator} from './directives/input-validators/phone-validator.directive';
import {EmailValidator} from './directives/input-validators/email-validator.directive';
import {SSNValidator} from './directives/input-validators/ssn-validator.directive';
@NgModule({
declarations: [
AppComponent,
//validate email
import { NG_VALIDATORS, FormControl, ValidatorFn, Validator } from '@angular/forms';
import { Directive } from '@angular/core';
@Directive({
selector: '[emailvalidator][ngModel]',
providers: [
{
provide: NG_VALIDATORS,
useExisting: EmailValidator,
multi: true
}
]
})
export class EmailValidator implements Validator {
validator: ValidatorFn;
constructor() {
this.validator = this.emailValidator();
}
validate(c: FormControl) {
return this.validator(c);
}
emailValidator(): ValidatorFn {
return (c: FormControl) => {
if(c.value == "") {
return null;
}
let isValid = /^[_a-z0-9]+(\.[_a-z0-9]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$/ .test(c.value); //email
if (isValid) {
return null;
} else {
return {
emailvalidator: {
valid: false
}
};
}
}
}
}
We can validate Phone no field using this regular expression, It allows to enter phone no in +11111111111 format.
//validate phone no
import { NG_VALIDATORS, FormControl, ValidatorFn, Validator } from '@angular/forms';
import { Directive } from '@angular/core';
@Directive({
selector: '[phonevalidator][ngModel]',
providers: [
{
provide: NG_VALIDATORS,
useExisting: PhoneValidator,
multi: true
}
]
})
export class PhoneValidator implements Validator {
validator: ValidatorFn;
constructor() {
this.validator = this.phoneValidator();
}
validate(c: FormControl) {
return this.validator(c);
}
phoneValidator(): ValidatorFn {
return (c: FormControl) => {
if(c.value == "") {
return null;
}
let isValid = /^\+[0-9]{11}$/.test(c.value);
if (isValid) {
return null;
} else {
return {
phonevalidator: {
valid: false
}
};
}
}
}
}
npm install --save norwegian-national-id-validator
You can find the following directive has used installed npm package to validate norwegian ssn no
//validate ssn
import { NG_VALIDATORS, FormControl, ValidatorFn, Validator } from '@angular/forms';
import { Directive } from '@angular/core';
import { validateNorwegianIdNumber } from 'norwegian-national-id-validator';
@Directive({
selector: '[ssnvalidator][ngModel]',
providers: [
{
provide: NG_VALIDATORS,
useExisting: SSNValidator,
multi: true
}
]
})
export class SSNValidator implements Validator {
validator: ValidatorFn;
constructor() {
this.validator = this.ssnValidator();
}
validate(c: FormControl) {
return this.validator(c);
}
ssnValidator(): ValidatorFn {
return (c: FormControl) => {
if(c.value == "") {
return null;
}
let isValid = /^[0-9]{6}( )[0-9]{5}$/g.test(c.value);
if (isValid) {
var format = validateNorwegianIdNumber(c.value.replace(/ /g,''));
if(format){
return null;
}
else{
return {
ssnvalidator: {
valid: false
}
};
}
} else {
return {
ssnvalidator: {
valid: false
}
};
}
}
}
}
.invalid-feedback-select {
width: 100%;
font-size: 80%;
color: #dc3545;
}
"styles": [
"./node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.css",
"src/assets/css/common.scss"
],
ng new <projectname>
ng build
ng serve
import { NgSelectModule } from '@ng-select/ng-select';
export class AppComponent implements OnInit {
import { Component, OnInit } from '@angular/core';
ngOnInit(){
}
namespace input_restrictions_dal.Dto
{
public class MasterData
{
public IEnumerable Gender { get; set; }
public IEnumerable PostalCode { get; set; }
}
public class DropDownField
{
public int Id { get; set; }
public string Value { get; set; }
}
public class PostalCodefield
{
public int Id { get; set; }
public string Code { get; set; }
public string City { get; set; }
}
}
namespace input_restrictions_dal.Enum
{
public enum Gender
{
Male = 1,
Female = 2
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(o =>
{
o.AddPolicy("AllowAnyOrigin", builderCor =>
{
builderCor.AllowAnyOrigin().AllowCredentials().AllowAnyHeader().AllowAnyMethod();
});
o.DefaultPolicyName = "AllowAnyOrigin";
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseHttpsRedirection();
app.UseCors("AllowAnyOrigin");
app.UseMvc();
}
using input_restrictions_dal.Dto;
namespace input_restrictions_api.Service
{
public interface IStudentService
{
MasterData GetMasterData();
}
}
using System;
using System.Collections.Generic;
using input_restrictions_dal.Dto;
using input_restrictions_dal.Enum;
using System.Linq;
namespace input_restrictions_api.Service
{
public class StudentService : IStudentService
{
public MasterData GetMasterData()
{
MasterData masterData = new MasterData
{
Gender = (from Gender g in Enum.GetValues(typeof(Gender))
select new
{
ID = Convert.ToInt32(g),
Name = g.ToString()
}).Select(s => new DropDownField()
{
Id = s.ID,
Value = s.Name
}).ToList(),
PostalCode = new List {
new PostalCodefield {Id =1, Code="00130", City = "Colombo"},
new PostalCodefield { Id = 1, Code = "20000", City = "Kandy" },
new PostalCodefield { Id = 1, Code = "80000", City = "Galle" } }
};
return masterData;
}
}
}
using Microsoft.AspNetCore.Mvc;
using input_restrictions_api.Service;
using input_restrictions_dal.Dto;
using System.Net;
namespace input_restrictions_api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class StudentController : ControllerBase
{
private readonly IStudentService _studentService;
public StudentController()
{
_studentService = new StudentService();
}
[HttpGet("masterdata")]
[ProducesResponseType(typeof(MasterData), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public ActionResult MasterData()
{
MasterData masterData = _studentService.GetMasterData();
return Ok(masterData);
}
}
}
import { Component, OnInit } from '@angular/core';
import { MasterData } from './shared/masterdata.model';
import { DataService } from './shared/data.service';
import { Member } from './shared/member.model';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
masterData: MasterData;
member: Member;
constructor(private dataService : DataService) {
}
ngOnInit(){
this.masterData = new MasterData();
this.member = new Member();
this.GetMasterData();
}
GetMasterData () {
this.dataService.GetMasterData().subscribe(
data => {
this.masterData = data;
},
err => {
throw err;
}
);
}
onPostalNoChange(event) {
if (event == null) {
this.member.postalPlace = '';
}
else {
this.masterData.postalCode.forEach(entry => {
if (entry.code == event.code) {
this.member.postalPlace = entry.city;
}
});
}
}
You can see the app.module file, all the imported modules and custom directives we created are added.
import { NgSelectModule } from '@ng-select/ng-select';
import { HttpClientModule } from '@angular/common/http';
import { LettersOnlyDirective} from './directives/letters.directive';
import {PhoneNoDirective} from './directives/phoneno.directive';
import {SSNDirective} from './directives/ssn.directive';
import {LettersNumbersOnly} from './directives/letters-numbers.directive';
import {DecimalNumberOnlyDirective} from './directives/decimal-number.directive';
import { HttpClient } from '@angular/common/http'
import { Observable } from 'rxjs'
import { MasterData } from "./masterdata.model";
import { environment } from "src/environments/environment";
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class DataService {
constructor(private http : HttpClient) {}
GetMasterData () : Observable<MasterData>{
return this.http.get(environment.api_url
+ '/student/masterdata');
}
}
Let's see the model classes in the client application. we have to map service method data in the frontend classes, so we created a MasterData class as shown below.
export class PostalCodeMasterData {
zipCodeId : number;
code : string;
city : string;
}
export class Member {
firstName: string;
lastName: string;
phone: string;
email: string;
ssn: string;
gender: string;
postalNo: number;
postalPlace: string;
addressLine1: string;
addressLine2: string;
gpa: string;
certificateIssued: boolean;
}
We can restrict inputs in firstname and lastname fields using this directive, specials keys are allowed other than that.
//validate firstname and lastname
import { Directive, ElementRef, HostListener} from '@angular/core';
@Directive({
selector: '[lettersOnly]'
})
export class LettersOnlyDirective{
private regex: RegExp = new RegExp(/^[a-zA-Z]+$/g);
private specialKeys: Array = ['Backspace', 'Tab', 'End', 'Home'];
constructor(private el: ElementRef) {
}
@HostListener('paste', ['$event']) onkeydown(e: any) {
var value = e.clipboardData.getData('Text');
if (value && !String(value).match(this.regex)) {
event.preventDefault();
}
}
@HostListener('keydown', ['$event'])
onKeyDown(event: KeyboardEvent) {
if (this.specialKeys.indexOf(event.key) !== -1) {
return;
}
let current: string = this.el.nativeElement.value;
let next: string = current.concat(event.key);
if (next && !String(next).match(this.regex)) {
event.preventDefault();
}
}
}
We can restrict inputs for a phone no filed using this directive, it allows only to enter + value and then numbers
//validate phone no
import { Directive, ElementRef, HostListener } from '@angular/core';
@Directive({
selector: '[phoneOnly]'
})
export class PhoneNoDirective {
private regex: RegExp = new RegExp(/^(\+)[0-9]{0,11}$/g);
private specialKeys: Array = ['Backspace', 'Tab', 'End', 'Home'];
constructor(private el: ElementRef) {
}
@HostListener('paste', ['$event']) onkeydown(e: any) {
var pastedvalue = e.clipboardData.getData('Text');
var value = this.el.nativeElement.value + pastedvalue;
if (value && !String(value).match(this.regex)) {
event.preventDefault();
}
}
@HostListener('keydown', ['$event'])
onKeyDown(event: KeyboardEvent) {
if (this.specialKeys.indexOf(event.key) !== -1) {
return;
}
let current: string = this.el.nativeElement.value;
let next: string = current.concat(event.key);
if (next && !String(next).match(this.regex)) {
event.preventDefault();
}
}
}
Check this directive restricts ssn no in noregian format, please have a look for sample ssn nos, How to generate a ssn number in norway
//validate ssn
import { Directive, ElementRef, HostListener } from '@angular/core';
@Directive({
selector: '[ssnNumber]'
})
export class SSNDirective {
private regex: RegExp = new RegExp(/^[0-9]{1,6}( ){0,1}([0-9]){0,5}$/g);
private specialKeys: Array = ['Backspace', 'Tab', 'End', 'Home'];
constructor(private el: ElementRef) {
}
@HostListener('paste', ['$event']) onkeydown(e: any) {
var pastedvalue = e.clipboardData.getData('Text');
var value = this.el.nativeElement.value + pastedvalue;
if (value && !String(value).match(this.regex)) {
event.preventDefault();
}
}
@HostListener('keydown', ['$event'])
onKeyDown(event: KeyboardEvent) {
if (this.specialKeys.indexOf(event.key) !== -1) {
return;
}
let current: string = this.el.nativeElement.value;
let next: string = current.concat(event.key);
if (next && !String(next).match(this.regex)) {
event.preventDefault();
}
}
}
We can validate address fields with the given directive, it allows to enter letters and number only, no special characters
//validate address line 1 and 2
import { Directive, ElementRef, HostListener } from '@angular/core';
@Directive({
selector: '[lettersNumbersOnly]'
})
export class LettersNumbersOnly {
private regex: RegExp = new RegExp(/^[a-zA-Z0-9 ]+$/g);
private specialKeys: Array = ['Backspace', 'Tab', 'End', 'Home'];
constructor(private el: ElementRef) {
}
@HostListener('paste', ['$event']) onkeydown(e: any) {
var value = e.clipboardData.getData('Text');
if (value && !String(value).match(this.regex)) {
event.preventDefault();
}
}
@HostListener('keydown', ['$event'])
onKeyDown(event: KeyboardEvent) {
if (this.specialKeys.indexOf(event.key) !== -1) {
return;
}
let current: string = this.el.nativeElement.value;
let next: string = current.concat(event.key);
if (next && !String(next).match(this.regex)) {
event.preventDefault();
}
}
}
We can validate for decimal numbers like gpa value with following regular expression