import { Component } from '@angular/core';
import { FormGroup, FormControl, FormBuilder } from '@angular/forms';
import { AccountService } from './bank-account.service';
@Component({
selector: 'my-app',
templateUrl: 'app/app.component.html'})
export class AppComponent {
constructor() { }
vmName: string = "Bob Lee";
vmAccount1: string = '';
vmAccount2: BankAccount = { acc1: '08', acc2: '6523', acc3: '1954512', acc4: '001' };
vmAccount3: BankAccount = { acc1: '', acc2: '', acc3: '', acc4: '' };
submit(value: any) {
console.log('AppComponent.submit()', value);
}
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { AccountService } from './bank-account.service';
import { AccountValidator } from './bank-account-validator.directive';
import { AccountModelDrivenComponent } from './bank-account-model-driven.component';
import { AccountTemplateDrivenComponent } from './bank-account-template-driven.component';
@NgModule({
imports: [ BrowserModule, FormsModule, ReactiveFormsModule ],
declarations: [
AppComponent,
AccountValidator,
AccountModelDrivenComponent,
AccountTemplateDrivenComponent
],
providers: [ AccountService ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);
/*
Copyright 2016 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/
<!DOCTYPE html>
<html>
<head>
<title>Angular 2 QuickStart</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="//netdna.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
<!-- 1. Load libraries -->
<!-- Polyfill(s) for older browsers -->
<script src="https://unpkg.com/core-js/client/shim.min.js"></script>
<script src="https://unpkg.com/zone.js@0.6.23?main=browser"></script>
<script src="https://unpkg.com/reflect-metadata@0.1.3"></script>
<script src="https://unpkg.com/systemjs@0.19.27/dist/system.src.js"></script>
<!-- 2. Configure SystemJS -->
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
</head>
<!-- 3. Display the application -->
<body>
<my-app>Loading...</my-app>
</body>
</html>
<!--
Copyright 2016 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
-->
### Angular 2 component for bank account with validation
- custom form component with custom validation
- ignores key press other than numeric (0..9)
- validates bank account number
- assumes the only valid number: 08-6523-1954512-001
/**
* PLUNKER VERSION
* (based on systemjs.config.js in angular.io)
* System configuration for Angular 2 samples
* Adjust as necessary for your application needs.
*/
(function (global) {
System.config({
// DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
transpiler: 'ts',
typescriptOptions: {
tsconfig: true
},
meta: {
'typescript': {
"exports": "ts"
}
},
paths: {
// paths serve as alias
'npm:': 'https://unpkg.com/'
},
// map tells the System loader where to look for things
map: {
// our app is within the app folder
app: 'app',
// angular bundles
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
'@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
// other libraries
'rxjs': 'npm:rxjs',
'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
'ts': 'npm:plugin-typescript@4.0.10/lib/plugin.js',
'typescript': 'npm:typescript@2.0.2/lib/typescript.js',
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
app: {
main: './main.ts',
defaultExtension: 'ts'
},
rxjs: {
defaultExtension: 'js'
},
'angular2-in-memory-web-api': {
main: './index.js',
defaultExtension: 'js'
}
}
});
})(this);
/*
Copyright 2016 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
}
}
<h3>Angular 2 component for bank account with validation</h3>
<ul>
<li>custom form component with custom validation</li>
<li>ignores key press other than numeric (0..9)</li>
<li>validates bank account number</li>
<li>assumes the only valid number: 08-6523-1954512-001</li>
</ul>
<form #theForm="ngForm" name="theForm" novalidate (ngSubmit)="submit(theForm)">
<div class="form-group" [ngClass]="{ 'has-error' : name.invalid }">
<label class="control-label col-md-3">Name</label>
<div class="col-md-9">
<input class="form-control text-box single-line" type="text" name="name" #name="ngModel" [(ngModel)]="vmName" required />
<div class="text-danger" [hidden]="name.valid || !name.errors.required">Name is required</div>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-3">Account 1</label>
<div class="col-md-9" [ngClass]="{ 'has-error' : account1.invalid }">
<input class="form-control text-box single-line" type="text" name="account1"
#account1="ngModel" [(ngModel)]="vmAccount1" validateAccount required
placeholder="bank account number" />
<div class="text-danger" [hidden]="account1.valid || !account1.errors.required">Bank account is required</div>
<div class="text-danger" [hidden]="account1.valid || !account1.errors.validateAccount">Bank account number is not valid</div>
</div>
</div>
<bank-account-model-driven name="account2"
#account2="ngModel" [myModel]="account2" [myForm]="theForm" [(ngModel)]="vmAccount2"
[myRequired]="true">
</bank-account-model-driven>
<bank-account-template-driven name="account3"
#account3="ngModel" [myModel]="account3" [myForm]="theForm" [(ngModel)]="vmAccount3"
[myRequired]="true">
</bank-account-template-driven>
<button type="submit" class="btn btn-default btn-primary" >Submit</button>
</form>
<!--
-->
<pre>theForm.valid={{theForm.valid}}, account1.valid={{account1.valid}}, account2.valid={{account2.valid}}, account3.valid={{account3.valid}}</pre>
<pre>theForm.value={{theForm.value | json}}</pre>
/*
Reference document:
Non-Resident Withholding Tax And Resident Withholding Tax Specification Document,
section 8, BANK ACCOUNT NUMBER VALIDATION,
Inland Revenue
*/
import { Injectable } from '@angular/core';
export interface BankAccount { acc1: string, acc2: string, acc3: string, acc4: string }
interface range { from: string, to: string }
@Injectable()
export class AccountService {
private banks: { [id: string]: range[] } = {};
private types: { [id: string]: range[] } = {};
private algorithms: { [id: string]: range[] } = {};
constructor() {
let ranges01 = [];
ranges01.push({ from: '0001', to: '0999' });
ranges01.push({ from: '1100', to: '1199' });
ranges01.push({ from: '1800', to: '1899' });
this.banks['01'] = ranges01;
// ...
}
public isValid(bank: string, branch: string, body: string, suffix: string): boolean { // assumes correct length inputs (2 for bank, 4 for branch, 7 for body, 2/3 for suffix)
if (!bank && !branch && !body && !suffix) return true; // empty is valid
var valid = bank && bank.length === 2 &&
branch && (branch.length === 3 || branch.length === 4) &&
body && body.length === 7 &&
suffix && (suffix.length === 2 || suffix.length === 3);
if (!valid) return false;
// real implementation is out of interest here, just check against the only valid number '08-6523-1954512-001'
return bank === '08' && branch === '6523' && body === '1954512' && suffix === '001';
}
}
import { Component, forwardRef, Input } from '@angular/core';
import {
FormControl,
FormGroup,
FormBuilder,
ControlValueAccessor,
NG_VALUE_ACCESSOR,
NG_VALIDATORS
} from '@angular/forms';
import { AccountService } from './bank-account.service';
import {
ignoreSome,
validateAccountGroupFactory
} from './bank-account-validator.directive';
@Component ({
selector: 'bank-account-model-driven',
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AccountModelDrivenComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => AccountModelDrivenComponent), multi: true }
],
templateUrl: 'app/bank-account-model-driven.component.html'
})
export class AccountModelDrivenComponent implements ControlValueAccessor, OnInit {
accountNumber: FormGroup;
@Input() myForm: FormGroup;
@Input() myModel: NgModel;
@Input() myRequired: boolean;
validator: Function;
constructor(accountService: AccountService, private formBuilder: FormBuilder) {
this.validator = validateAccountGroupFactory(accountService);
}
ngOnInit() {
this.accountNumber = this.formBuilder.group({
acc1: '',
acc2: '',
acc3: '',
acc4: ''
});
}
onKeypress(event) {
ignoreSome(event);
}
validate(c: FormGroup) {
return this.validator(c, this.myRequired);
}
writeValue(value: BankAccount) {
if (value) {
this.accountNumber.setValue(value);
}
}
registerOnChange(fn: (value: any) => void) {
this.accountNumber.valueChanges.subscribe(fn);
}
registerOnTouched() {}
}
<div class="form-group" [formGroup]="accountNumber">
<label class="control-label col-md-3">Account 2</label>
<div class="col-md-9" [ngClass]="{ 'has-error' : myModel.invalid }">
<div class="form-inline">
<input class="form-control" type="text" formControlName="acc1" (keypress)="onKeypress($event)" [attr.size]="acc1Size" [attr.maxlength]="acc1Size" placeholder="bank" />
<input class="form-control" type="text" formControlName="acc2" (keypress)="onKeypress($event)" [attr.size]="acc2Size" [attr.maxlength]="acc2Size" placeholder="branch" />
<input class="form-control" type="text" formControlName="acc3" (keypress)="onKeypress($event)" [attr.size]="acc3Size" [attr.maxlength]="acc3Size" placeholder="account" />
<input class="form-control" type="text" formControlName="acc4" (keypress)="onKeypress($event)" [attr.size]="acc4Size" [attr.maxlength]="acc4Size" placeholder="suffix" />
</div>
<div class="text-danger" [hidden]="myModel.valid || !myModel.errors.myRequired">Bank account is required</div>
<div class="text-danger" [hidden]="myModel.valid || !myModel.errors.validateAccount">Bank account number is not valid</div>
</div>
</div>
import { Component, forwardRef, Input } from '@angular/core';
import {
FormControl,
FormGroup,
FormBuilder,
ControlValueAccessor,
NG_VALUE_ACCESSOR,
NG_VALIDATORS
} from '@angular/forms';
import { AccountService } from './bank-account.service';
import {
ignoreSome,
validateAccountGroupFactory
} from './bank-account-validator.directive';
@Component ({
selector: 'bank-account-template-driven',
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AccountTemplateDrivenComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => AccountTemplateDrivenComponent), multi: true }
],
templateUrl: 'app/bank-account-template-driven.component.html'
})
export class AccountTemplateDrivenComponent implements ControlValueAccessor, OnInit {
accountNumber = {
acc1: '',
acc2: '',
acc3: '',
acc4: ''
}
@Input() myForm: FormGroup;
@Input() myModel: NgModel;
@Input() myRequired: boolean;
validator: Function;
constructor(accountService: AccountService) {
this.validator = validateAccountGroupFactory(accountService);
}
ngOnInit() { }
change(prop, value) {
this.accountNumber[prop] = value;
this.propagateChange(this.accountNumber);
}
onKeypress(event) {
ignoreSome(event);
}
validate(c: FormGroup) {
return this.validator(c, this.myRequired);
}
writeValue(value: BankAccount) {
if (value) {
this.accountNumber = value;
}
}
propagateChange = (_: any) => {};
registerOnChange(fn: (value: any) => void) {
this.propagateChange = fn;
}
registerOnTouched() {}
}
<form>
<div class="form-group" ngModelGroup="accountNumber">
<label class="control-label col-md-3">Account 3</label>
<div class="col-md-9" [ngClass]="{ 'has-error' : myModel.invalid }">
<div class="form-inline">
<input class="form-control" type="text" name="acc1" [ngModel]="accountNumber.acc1" (ngModelChange)="change('acc1', $event)" (keypress)="onKeypress($event)" [attr.size]="acc1Size" [attr.maxlength]="acc1Size" placeholder="bank" />
<input class="form-control" type="text" name="acc2" [ngModel]="accountNumber.acc2" (ngModelChange)="change('acc2', $event)" (keypress)="onKeypress($event)" [attr.size]="acc2Size" [attr.maxlength]="acc2Size" placeholder="branch" />
<input class="form-control" type="text" name="acc3" [ngModel]="accountNumber.acc3" (ngModelChange)="change('acc3', $event)" (keypress)="onKeypress($event)" [attr.size]="acc3Size" [attr.maxlength]="acc3Size" placeholder="account" />
<input class="form-control" type="text" name="acc4" [ngModel]="accountNumber.acc4" (ngModelChange)="change('acc4', $event)" (keypress)="onKeypress($event)" [attr.size]="acc4Size" [attr.maxlength]="acc4Size" placeholder="suffix" />
</div>
<div class="text-danger" [hidden]="myModel.valid || !myModel.errors.myRequired">Bank account is required</div>
<div class="text-danger" [hidden]="myModel.valid || !myModel.errors.validateAccount">Bank account number is not valid</div>
</div>
</div>
</form>
import { Directive, forwardRef, HostListener } from '@angular/core';
import { NG_VALIDATORS, FormControl } from '@angular/forms';
import { AccountService } from './bank-account.service';
@Directive({
selector: '[validateAccount][ngModel]',
providers: [
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => AccountValidator), multi: true }
]
})
export class AccountValidator {
validator: Function;
constructor(accountService: AccountService) {
this.validator = validateAccountFactory(accountService);
}
@HostListener('keypress', ['$event'])
onKeypress(event) {
ignoreSome(event);
}
validate(c: FormControl) {
return this.validator(c);
}
}
export function ignoreSome(event) {
if (event.key !== undefined) {
let k = event.key;
if (k !== '0' && k !== '1' && k !== '2' && k !== '3' && k !== '4' && k !== '5' && k !== '6' && k !== '7' && k !== '8' && k !== '9'
&& k !== 'Backspace' && k !== 'Tab' && k !== 'Delete' && k !== 'ArrowLeft' && k !== 'ArrowRight') { // Firefox
console.log(`ignoreSome(key ${k})`);
event.preventDefault();
}
} else if (event.keyCode !== undefined) {
let k = event.keyCode;
if (k === 32 || // space
k < 48 || k > 57) { // not decimal
console.log(`ignoreSome(keyCode ${k})`);
event.preventDefault();
}
}
}
function validateAccountFactory(accountService: AccountService) {
return (c: FormControl) => {
if (!c || !c.value) return null; // empty is valid
let invalid = { validateAccount: { valid: false } };
if (c.value.length === 15 || c.value.length === 16) {
let acc = c.value,
acc1 = acc.slice(0, 2),
acc2 = acc.slice(2, 6),
acc3 = acc.slice(6, 13),
acc4 = acc.slice(13);
if (accountService.isValid(acc1, acc2, acc3, acc4))
return null; // valid
}
return invalid;
};
}
export function validateAccountGroupFactory(accountService: AccountService) {
return (c: FormGroup, required: boolean) => {
let invalid = { validateAccount: { valid: false } },
myRequired = { myRequired: { valid: false } };
if (!c || !c.value || (!c.value.acc1 && !c.value.acc2 && !c.value.acc3 && !c.value.acc4)) { // empty
if (required) return myRequired;
else return null;
}
return accountService.isValid(c.value.acc1, c.value.acc2, c.value.acc3, c.value.acc4) ? null : invalid;
};
}