Thursday, August 10, 2017

Angular - Numbers only input (or Regex input)

Hi,
In this post we'll learn how to enforce some kind of behavior on input by using Angular core.

Our goal: 
Create an input which can get only numbers (or empty value). 
If any character other than a number is typed - input should not be updated!

Here is how angular performs when using an input:



1 + 2 - Anonymous function inside registerOnChange (1) sent to fn which is set to onChange (2).

3 + 4 - input event handler is defined to handleInput which just executes onChange.


Directive:




It's possible to solve the issue by writing a behavior directive.
This directive gets angular defaultValueAccessor from injector and override onChange so it won't trigger fn immediately - only after it validates input value.

Step-by-step explanation of app.ts :


@Directive({ selector: '[ngModel][numbersOnlyInput]'})

In order to use this directive - an html DOM element should have ngModel and numbersOnlyInput attributes.


constructor(private valueAccessor: DefaultValueAccessor, private model: NgModel

Constructor gets 2 references from Angular's injector: 
DefaultValueAccessor - which is actually Angular default wrapper that write and listen to changes of some form elements (https://angular.io/api/forms/DefaultValueAccessor).
NgModel - will be used in to extract and override the value in case it isn't valid.


valueAccessor.registerOnChange = (fn: (val: any) => void) => {  
valueAccessor.onChange = (val) => { } 

Overriding default onChange functionality.
Please note that actual change implementation should be written inside onChange method. Inside registerOnChange one should define the onChange method.


let regexp = new RegExp('\\d+(?:[\.]\\d{0,})?$'); 
let isNumber : boolean = regexp.test(val);
let isEmpty : boolean = val == '';

This is onChange implementation. val is the value that was now written to the input.
val is tested against a regex that enforces only numbers and isNumber gets the boolean result.
Input can also be empty - so val is also tested against an empty value and isEmpty gets the boolean result.


if(!isNumber && !isEmpty) {
   model.control.setValue(model.value);
   return;
}

return fn(val);

First, remember that onChange signature is (_: any) => {} which means that it's a function that gets a value of any type and returns a function.
- If val is not number and is not empty - model.value has the input value before something changed because input model hasn't updated yet.
model.control.setValue(model.value); sets previous input text into the input element. 
After setting the old value - change cycle is stopped so no function is returned to onChange.
- If val is a number or empty - change cycle continues regularly by return default fn with val.

Note: If this part isn't that clear - take a look again at the flow diagram above.


<input type="text" [(ngModel)]="val" numbersOnlyInput>

As mentioned, by writing ngModel and numbersOnlyInput - NumbersOnlyDirective is activated on input. 

Note: If you're using directives in your app - it's recommended to define each one on a separate file.

Custom Input Component:



Suppose you don't need that kind of functionality across all application and you need to write a custom input which behaves differently than Angular's input.

Step-by-step explanation of custom-input.ts:

export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => CustomInputComponent),
    multi: true
};

NG_VALUE_ACCESSOR is an angular token which tells angular dependency injection (DI) core that this class implements ControlValueAccessor. Now Angular DI system can grab an instance of it. Using this token allows to use this input in a dynamic form as angular would recognize it as a form control (In this awesome custom form component blogpost refer to the paragraph that starts with "The beauty of this is that we never have to tell NgForm what our inputs are").


@Component({
    selector: 'custom-input',
    template: `<div class="form-group">
                    <label><ng-content></ng-content>
                        <input  (input)="handleInput($event.target)" 
                                (blur)="onBlur()" >
                    </label>
                </div>`,
    providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
})

In contrast to the directive solution - now all events must be handled by custom-input implementation.


//From ControlValueAccessor interface
registerOnChange(fn: any) {
    this.onChangeCallback = fn;
}

onChange gets fn as is. fn is the same anonymous function from the diagram above. Overriding that function won't help like it did before. This is a new input (with a new ControlValueAccessor) which doesn't trigger onChange. The component's developer should state when or where to execute it.


handleInput(target: any): void {
    const val = target.value;
    let regexp = new RegExp('\\d+(?:[\.]\\d{0,})?$');
    let isNumber = regexp.test(val);
    let isEmpty = val == '';
    if(!isNumber && !isEmpty) {
       let model = this._inj.get(NgControl);  // get NgModel
       model.control.setValue(model.value);
       target.value = model.value;
       return;
        }
     
    this.onChangeCallback(val);
}

In this case, handleInput which has "numbers-only" input functionality decides if to trigger onChange
or not (continue change cycle or stop).

Saturday, July 1, 2017

Career path change - Full Stack Developer

Hi,

Since I started my career I mainly developed complex integration\ESB systems. 
I had a chance to work with many frameworks, products and tools:
.NET, BizTalk Server, TIBCO BW, EMS & MSMQ, SQL Server, Redis, ElasticSearch, SSIS, Reporting services and so on.

During those years I still managed to maintain some web knowledge with some ASP .NET and node.js programming. I also got to know little of Angular & React cool frameworks.

4 months ago i've decided to take a turn in my career path and began to work as a Full Stack Developer at Ayehu.
Our team develops SASS platform of EyeShare product which allows to create automation processes.

Besides working in a fun company along with great team members - I also have a chance to write client-side code in cutting-edge frameworks: 
- Angular4 & TypeScript
- HTML5
- SASS, CSS3

And some server-side:
- C# .Net
- T-SQL
- RabbitMQ

Wednesday, January 11, 2017

Continuous Integration – TIBCO BW & Jenkins and TFS

As mentioned in my previous posts, my team worked on a big project – migrate organization's offline interfaces from Microsoft Biztalk to TIBCO BW.
We decided to configure continuous integration using Jenkins in order to short and automate development process.

Main goal was:
A developer checks-in TIBCO BW project → Jenkins triggers the check-in → Validate project by "validateproject.exe" tool →Build a project ear file (used for deployment) by "buildear.exe" tool → Deploy project to development domain by "AppManage.exe" tool

First step – Install and Configure Jenkins

  1. Next thing is to log in with "admin" user name (Initial pass stored in: Jenkins\secrets\initialAdminPassword).

  1. From the left menu choose: Manage Jenkins Manage Plugins
Please Install: Team Foundation Server Plug In, PowerShell Plug In
(You might need to install all their dependencies before).

  1. From the left menu choose: Manage Jenkins Configure System
  1. Set TFS URL with relevant credentials. Test connection when done:




  1. Set Jenkins location and System Admin e-mail address. This address would appear in e-mail notifications as "From":
  1. Set SMTP Server and try to send a test e-mail:

Second step – Configure new items
  1. Click on Jenkins logo on top-left:

In this screen you would see all items configured (currently none).

  1. Choose from the left menu New Item.
Type the item's name and choose Freestyle project.
It's also possible to create an exact copy from an existing item and after just modify configurations. This is VERY useful if you create many items which has almost the same configurations.

  1. In the item config screen you might want to check "Discard Old Builds" in order to keep just relevant content and to not overload storage.

  1. Next thing is to configure which project the item would monitor.
Type Collection URL and path to project (starts with "$" sign. For instance: $/Development/Test_Project):


  1. It's possible to define many build triggers. We needed a simple trigger: once a developer checks-in a project.
Best way to achieve it is to mark: "Build when a changed is pushed to TFS/Team Services". Unfortunately, this option would work only since TFS 2015.
We picked "Poll SCM" and typed "H/5 * * * *" (poll for change every 5 minutes).

  1. Next is configuring build steps. You can add as many as you wish.
In this tutorial we would have 3 build steps for a tibco project:
  1. Validate project
  2. Create an ear file for deployment
  3. Deploy
  1. It's possible to configure many post-build actions. We just used the E-Mail notification to the team's mail:


Third step – Writing the build scripts: Validate, Build Ear, Deploy Ear

Tibco 5.13 offers 3 executables:
Validate project:
C:\tibco\designer\5.10\bin\validateproject.exe [arguments]

Build .ear file:
C:\tibco\tra\5.10\bin\buildear.exe [arguments]

Deploy .ear file:
C:\tibco\tra\5.10\bin\AppManage –upload –ear [arguments]

Each one produces an output which we needed to analyze with regex.
It's impossible to get only regex matches using Command-Line so we had to use PowerShell.

Project configuration would look like:


Build Step 1: Validate project
We've added a path to a powershell script in order to validate a project:

It's a good practice to keep the script in a file. This file is shared across all projects and any change in it would affect all immediately.
Let's take a look on this script content (please note the remarks):
# Sets current location to "validateproject.exe" folder
Set-Location "C:\tibco\designer\5.10\bin"

# Executes validateproject
# VERY IMPORTANT –  "-a" sets the alias library references. It's crucial for the #validation process
$tmp = .\validateproject.exe -a "C:\TibcoData\properties\FileAlias.properties" $ENV:WORKSPACE

# write output to console just for tracing
echo $tmp;

# This regex filters the number of errors
$regex = '[0-9]+ errors'
$output = select-string -InputObject $tmp -Pattern $regex -AllMatches | % {  $_.Matches } | % { $_.Value } | Out-String

# If regex output is NOT "0 errors" (that means that validation process has errors)
if($output -NotMatch "0 errors")
{
# exit code 10 (If it's not 0, then Jenkins would fail the build)
exit 10
}

Build Step 2: Build project ear file
Second step is to build project ear file which will be used for deployment in the next step.

# Sets current location to "buildear.exe" folder
Set-Location "C:\Tibco\tra\5.10\bin"

# Executes BuildEar
$tmp = .\buildear -a "C:\TibcoData\properties\FileAlias.properties" -x -v -s -ear ""/Deploy/$ENV:JOB_NAME.archive""  -o ""C:\TibcoData\ears\deployment\Jenkins\$ENV:JOB_NAME.ear"" -p ""$ENV:WORKSPACE""

# write output to console just for tracing
echo $tmp;

# Checks if "Ear created in:" (until line break) string is inside $tmp (output of buildear)
$regex = 'Ear created in:.*'
$output = select-string -InputObject $tmp -Pattern $regex -AllMatches | % {  $_.Matches } | % { $_.Value } | Out-String

# If output doesn't contain "Ear created in" then fail the build (exit code 10)
if($output -notlike "Ear created in:*")
{
exit 10
}

Build Step 3: Deploy project ear file
Last step is to deploy ear file to development domain.

# Sets current location to "AppManage.exe" folder
Set-Location "C:\tibco\tra\5.10\bin"

# Deploy ear to development domain
$tmp = .\AppManage -user "admin" -pw "admin" -domain "DEV_01" -app "$ENV:JOB_NAME" -upload -ear "C:\TibcoData\ears\deployment\Jenkins\$ENV:JOB_NAME.ear"

# Write deploy output to console
echo $tmp;

# Check if deployment finished successfully
$regex = 'Finished successfully in.*'
$output = select-string -InputObject $tmp -Pattern $regex -AllMatches | % {  $_.Matches } | % { $_.Value } | Out-String

# If output doesn't contain "Finished successfully in" then fail the build (exit code 10)
if($output -notlike "Finished successfully in*")
{
exit 10
}


Thank you Blogger, hello Medium

Hey guys, I've been writing in Blogger for almost 10 years this is a time to move on. I'm happy to announce my new blog at Med...