I'm developing an Angular 2 SPA. My application is composed by:
One component
One directive
I've builded one directive that format text input using onfocus and onblur events. On focus event remove dots to text value, on blur event add thousand dots to text value.
Following component's code:
<div>
<input id="input" [(ngModel)]="numero" InputNumber />
</div>
Following component's TypeScript code:
import { Component } from '#angular/core';
#Component({
selector: 'counter',
templateUrl: './counter.component.html'
})
export class CounterComponent {
numero: number;
public incrementCounter() {
}
ngOnInit() {
this.numero = 100100100;
}
}
Following directive's TypeScript code:
import { Directive, HostListener, ElementRef, OnInit } from "#angular/core";
#Directive({ selector: "[InputNumber]" })
export class InputNumber implements OnInit, OnChanges {
private el: HTMLInputElement;
constructor(private elementRef: ElementRef) {
this.el = this.elementRef.nativeElement;
}
ngOnInit(): void {
// this.el.value is empty
console.log("Init " + this.el.value);
this.el.value = this.numberWithCommas(this.el.value);
}
ngOnChanges(changes: any): void {
// OnChanging value this code is not executed...
console.log("Change " + this.el.value);
this.el.value = this.numberWithCommas(this.el.value);
}
#HostListener("focus", ["$event.target.value"])
onFocus(value: string) {
this.el.value = this.replaceAll(value, ".", "");
}
#HostListener("blur", ["$event.target.value"])
onBlur(value: string) {
this.el.value = this.numberWithCommas(value);
}
private numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}
private escapeRegExp(str) {
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
private replaceAll(str, find, replace) {
return str.replace(new RegExp(this.escapeRegExp(find), 'g'), replace);
}
}
The following code works except that I need lost focus for show my number like "100.100.100". How can I perform this action on init data loading?
I add one example at this link: Plnkr example
Thanks
You can do this by using a Pipe which takes a boolean parameter that represents your focus/no focus action.
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({name: 'dots'})
export class DotsPipe implements PipeTransform {
transform(value: number, hasFocus:boolean): any {
if(hasFocus){
return value.toString().replace(/\./g,'');
}else{
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}
}
}
Then you have to apply the Pipe on your [ngModel] and use Angular events (focus) and (focusout) to change your variable.
<input [ngModel]="numero | dots : hasFocus" (focus)="hasFocus=true" (focusout)="hasFocus=false" (ngModelChange)="numero=$event" />
I think that your directive should implement ControlValueAccessor interface https://angular.io/docs/ts/latest/api/forms/index/ControlValueAccessor-interface.html
It is needed for writing model in your directive. ControlValueAccessor interface has writeValue(value: any) method that will be initially called.
So your writeValue method will be something like this:
private onChange: (_: any) => {};
...
writeValue(val) {
const editedValue = this.numberWithCommas(val);
this._onChange(val);
}
registerOnChange(fn: any) : void {
this._onChange = fn;
}
Related
i want made a generic pipe for call a component's function into component's html.
The wrong way is eg.:
{{ test('foo') }}
my idea is:
{{ 'foo' | fn:test }}
this is the pipe code
import { ChangeDetectorRef, EmbeddedViewRef, Type } from "#angular/core";
import { Pipe } from "#angular/core";
import { PipeTransform } from "#angular/core";
#Pipe({
name: "fn",
pure: true
})
export class FnPipe implements PipeTransform {
private context: any;
constructor(cdRef: ChangeDetectorRef) {
// retrive component instance (this is a workaround)
this.context = (cdRef as EmbeddedViewRef<Type<any>>).context;
}
public transform(
headArgument: any,
fnReference: Function,
...tailArguments: any[]
): any {
return fnReference.apply(this.context, [headArgument, ...tailArguments]);
}
}
and this is a component example
import { Component } from "#angular/core";
#Component({
selector: "my-app",
template: `
<!-- testFromPipe har a third parameter name for trigger pipe refresh -->
PIPE: {{ "arg1" | fn: testFromPipe:"arg2":name }}<br /><br />
<!-- wrong way for call a function nto html just for test the result -->
HTML: {{ testFromHtml("arg1", "arg2") }}<br /><br />
<button (click)="triggerCD()">test</button>
`
})
export class AppComponent {
name = null;
constructor() {
this.triggerCD();
}
test(a: string, b: string) {
// test multiple arguments anch this scope works
return `a:${a}; b:${b}; name:${this.name};`;
}
testFromHtml(a: string, b: string) {
console.log("FUNCTION");
return this.test(a, b);
}
testFromPipe(a: string, b: string) {
console.log("PIPE");
return this.test(a, b);
}
triggerCD() {
this.name = new Date().getMinutes();
}
}
this is a live example https://stackblitz.com/edit/angular-ivy-jvmcgz
the code seem works but is based on retrive the component instance into the pipe by private property context of ChangeDetectorRef.
constructor(cdRef: ChangeDetectorRef) {
this.context = (cdRef as EmbeddedViewRef<Type<any>>).context;
}
This is unsafe and future Angular update can break this trick.
There is a safe way to access to component instance into Pipe?
I have the following directive groupingFormat which perform grouping to an input text
when user use the key up:
#Directive({
selector: '[groupingFormat]'
})
export class GroupingFormatDirective {
private el: HTMLInputElement;
constructor(elRef: ElementRef) {
this.el = elRef.nativeElement;
}
ngAfterViewInit(): void {
let elem : HTMLInputElement = this.el;
elem.addEventListener('keyup',() => {
this.el.value = this.digitGrouping(this.el.value);
});
}
}
Example of usage:
<input type="text" #myValue="ngModel" name="my_value" [(ngModel)]="myObj.myValue" id="my_value" required groupingFormat>
This directive is working as expected but now I have new requirement: The input
text should use the directive also when the page is load and also if the a form
is open inside the page with the input becoming visible.
Is there an easy way to update the directive to support this functionality or
any alternative solution? Attach another directive ?
Thanks.
<input type="text" name="my_value" [appInputevent]="myValue" [(ngModel)]="myValue">
directive file
import { Directive,HostListener,ElementRef, Input } from '#angular/core';
#Directive({
selector: '[appInputevent]'
})
export class InputeventDirective {
constructor(private el:ElementRef) { }
#Input('appInputevent') params: string;
#HostListener('keyup', ['$event'])
onKeyUp(event: KeyboardEvent) {
console.log('got parameters: '+this.params);
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
change hostlistner event according to your need.
For demonstration-- https://stackblitz.com/edit/ionic2-test?file=app%2Finputevent.directive.ts
it is practically the following
I want the native HTML input tag to extend the native and typescript properties
example:
from:
<input type="checkbox"
formControlName="aComponent"
name="aComponent"
[checked]="data.get('aComponent').value">
to:
<my-input type="checkbox"
formControlName="aComponent"
name="aComponent"
[checked]="data.get('aComponent').value">
or
<my-input type="checkbox"
formControlName="aComponent"
name="aComponent"
[checked]="data.get('aComponent').value"> </my-input>
[checked] and formControlName is a property that provides angular, which also would like to inherit and native features such as: type and name
I know it's a little crazy but I would like to know if it is possible
in my component I try to do the following but, it only imports the native elements of HTML but not the features that Angular provides
import {Component, ElementRef, Inject, Input, OnInit} from '#angular/core';
#Component({
selector: 'my-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.css']
})
export class MyInputComponent extends HTMLElement implements OnInit {
#Input() checked; // angular feature that does not work
constructor() {
super();
}
ngOnInit() {
}
}
You should use ControlValueAccessor in order to use formControlName and ngModel to your component.
The below two tutorials will definitely help you to create custom form controls.
ControlValueAccsor to act your component as a form control
Using ControlValueAccessor to create custom form controls
import {Component, ElementRef, Inject, Input, OnInit} from '#angular/core';
import {ControlValueAccessor} from '#angular/forms';
#Component({
selector: 'my-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.css']
})
export class MyInputComponent extends HTMLElement implements OnInit, ControlValueAccessor{
#Input() checked; // angular feature that does not work
constructor() {
super();
}
ngOnInit() {
}
private innerValue: T;
private changed = new Array<(value: T) => void>();
private touched = new Array<() => void>();
get value(): T {
return this.innerValue;
}
set value(value: T) {
if (this.innerValue !== value) {
this.innerValue = value;
this.changed.forEach(f => f(value));
}
}
writeValue(value: T) {
this.innerValue = value;
}
registerOnChange(fn: (value: T) => void) {
this.changed.push(fn);
}
registerOnTouched(fn: () => void) {
this.touched.push(fn);
}
touch() {
this.touched.forEach(f => f());
}
}
I'm writing an angular2 validator directive. I have the following situation:
There's a number A and there's a number B, i need to validate if the number in the B field is lesser or equal the number in A field, i'm using template driven forms.
The code in directive is
import { Directive, Input, OnChanges, SimpleChanges } from '#angular/core';
import { AbstractControl, NG_VALIDATORS, Validator, ValidatorFn, Validators } from '#angular/forms';
export function validateLesserOrEqual(number: number): ValidatorFn {
console.log(number);
return (c: AbstractControl) => {
return c.value <= number ? null : {
validateLesserOrEqual: {
valid: false
}
}
}
}
#Directive({
selector: '[lesserOrEqual][ngModel]'
})
export class LesserOrEqualValidator implements Validator, OnChanges {
#Input() lesserOrEqualInput: number;
private _validator = Validators.nullValidator;
ngOnChanges(changes: SimpleChanges) {
const change = changes['lesserOrEqual'];
if(change) {
const val: number = change.currentValue;
this._validator = validateLesserOrEqual(val);
}
else {
this._validator = Validators.nullValidator;
}
}
validate(c: AbstractControl): {[number: number]: any} {
return this._validator(c);
}
}
and in my input i have the following [lesserOrEqual]="movement.parcel_number", but when i put the square brackets surrounding the directive i got the following error Unhandled Promise rejection: Template parse errors:
Can't bind to 'lesserOrEqual' since it isn't a known property of 'input'. ("dirty}"
Hi guys!
I'm learning AngularJS 2 for a while now and now creating my own app based on Laravel 5 REST API. Anyway - that isn't very important atm.
What is important is that I want to provide the translation for the whole application and I found an issue that is hard to solve for me.
So - from the beginning... I'm created my ResourcesService that's translating the string:
getTranslation ( key: string, replace: Array<TranslationReplace> = null, locale: string = null, fallback: boolean = null ): Observable<Resource> {
var params = "key=" + key +
( replace ? "&replace=" + JSON.stringify(replace) : '') +
( locale ? "&locale=" + locale : '') +
( fallback ? "&fallback=" + fallback : '');
var headers = new Headers({'Content-Type':'application/x-www-form-urlencoded'});
return this.http.post(this.apiUrl + 'getTranslation', params, {headers: headers})
.map(this.extractData)
.startWith({ name: 'Loading...', value: 'Translating...' })
.catch(this.handleError);
}
And I created a TranslateComponent that's providing the translation, here's the whole component:
import {Component, Input, Injectable, OnInit, OnChanges, SimpleChange} from "#angular/core";
import {ResourcesService} from "../services/resources.service";
import {TranslationReplace} from "../models/TranslationReplace";
#Component({
selector: 'translate',
template: `{{translation}}`
})
#Injectable()
export class TranslateComponent implements OnInit, OnChanges {
#Input() ref: string;
#Input() replace: Array<TranslationReplace>;
#Input() locale: string;
#Input() fallback: boolean;
private translation: string;
constructor(private resourcesService: ResourcesService) {}
ngOnInit() : void {
this.getTranslation();
}
ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
for (let propName in changes) {
if(propName == 'replace') {
this.getTranslation();
}
}
}
private getTranslation(): void {
this.resourcesService.getTranslation(this.ref, this.replace, this.locale, this.fallback).forEach(translation => this.translation = translation.value );
}
}
All is working just perfect and to call for the translation I have to simply call the selector like that:
<translate [ref]="'string.to_translate'"></translate>
But...
Now I'd like to use the translation in the attribute.
So I found the ugly way to achieve it by creating the reference of the translation and the call it in the attribute. But it's very nasty...
First of all I need to add this bit to my template:
<translate [ref]="'string.to_translate'" style="display:none;" #myStringTranslation></translate>
And next in my element call it and ask for the property by the reference:
<input type="text" [(ngModel)]="input" #input="ngModel [placeholder]="myStringTranslation.translation">
And I really don't like the idea.
What I'm looking for is to call it somehow, I don't know... emit it? And make it looks better. Don't create extra elements.
So my question is:
Can I do it better? Can I somehow call the translation directly from the attribute without the reference?
** ----- UPDATE ----- **
Ok, I learn my lesson :) Thanks to Meir for showing me the right direction and also the Angular.io site for the tutorials.
So finally I added a TranslateDirective to my application:
import {Directive, Input, ElementRef, OnChanges, OnInit, SimpleChange, Renderer} from "#angular/core";
import {TranslationReplace} from "../models/TranslationReplace";
import {ResourcesService} from "../services/resources.service";
#Directive({
selector: '[translate]'
})
export class TranslateDirective implements OnInit, OnChanges {
#Input('translate') ref: string;
#Input('translateReplace') replace: Array<TranslationReplace>;
#Input('translateLocale') locale: string;
#Input('translateFallback') fallback: boolean;
#Input('translateAttr') attr: string;
private translation: string;
constructor(
private elRef: ElementRef,
private renderer: Renderer,
private resourcesService: ResourcesService
) {}
ngOnInit():void {
this.getTranslation();
}
ngOnChanges(changes: {[propKey: string]: SimpleChange}):void {
for (let propName in changes) {
if(propName == 'replace') {
this.getTranslation();
}
}
}
private getTranslation(): void {
if(this.attr)
this.resourcesService.getTranslation(this.ref, this.replace, this.locale, this.fallback).forEach(translation =>
{
this.translation = translation.value;
this.renderer.setElementAttribute(this.elRef.nativeElement,this.attr,this.translation);
});
}
}
And now can easily add the translations to the attributes like that:
<input type="text" [(ngModel)]="input" #input="ngModel [translate]="'string.to_translate'" [translateAttr]="'placeholder'">
Thanks for your help!!
You can turn it into an attribute directive:
#Directive({
selector: 'translate'
})
export class TranslateDirectiev {
#Input() translate: string;
constructor(private elRef: ElementRef){}
ngOnChanges(changes: SimpleChanges): void {
if(this.translate){
var translatedText: string = translateSvc.translate(this.translate);
this.renderer.setElementProperty(this.elementRef.nativeElement, 'innerHTML', translatedText);
}
}
}
This is a simple example without the service injection. Also, for input fields you might need to have a different approach and update the value attribute and not the innerHtml