How to add event to input in Angular 5 app using directive? - javascript

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

Related

Angular Material how to select a component in JS

I have an angular webapp using angular material. In my HTML I have two ButtonToggles that you can toggle with a simple click event handler which I handle myself. However, there should also be a way to toggle the buttons with a keyboard shortcut. I have a keypress event listener that correctly intercepts the keypress, but I have no idea how I can toggle the associated button because I can't pass it in to the event handler.
Here is my html
<mat-button-toggle-group [multiple]="schema.multi">
<mat-button-toggle *ngFor="let label of schema.labels"
(click)="toggleAnnotation(label, localButton)"
[value]="label.name"
#localButton
[style.background-color]="localButton.checked == true ? label.backgroundColor : 'ghostwhite'">
{{label.name}} ({{label.shortcut}})
</mat-button-toggle>
</mat-button-toggle-group>
</div>
And the related typescript:
import {Component, EventEmitter, HostListener, Input, OnChanges, Output, SimpleChanges} from '#angular/core';
import {Label} from '../sequence/models/label';
import {TagAssignment} from '../../../models/tag-assignment';
import {MatButtonToggle, MatButtonToggleGroup} from "#angular/material/button-toggle";
export class CategoricalTaggerSchema {
multi: boolean; // can the user select multiple tags at once
prompt: string; // message to display before showing the tagger document
labels: Label[]; // categories to select from
}
#Component({
selector: 'app-categorical',
templateUrl: './categorical-tagger.component.html',
styleUrls: ['./categorical-tagger.component.css']
})
export class CategoricalTaggerComponent implements OnChanges {
#Input() config: TagAssignment = new TagAssignment(); // default to some value
#Output() valueChange = new EventEmitter<string>();
#Output() validChange = new EventEmitter<boolean>();
#Input() disabled = false;
schema: CategoricalTaggerSchema = {multi: false, prompt: '', labels: []};
annotations = new Set<string>(); // much simpler then sequence tagging, just a list of named labels
constructor() {}
ngOnChanges(changes: SimpleChanges): void {
if (changes.hasOwnProperty('config')) {
this.schema = JSON.parse(this.config.tag_schema);
}
}
toggleAnnotation(label: Label, localButton) {
if (!this.disabled) {
if (this.annotations.has(label.name)) {
this.annotations.delete(label.name);
localButton.checked = false;
} else { // creating new annotation
if (!this.schema.multi) { // only one annotation allowed
this.annotations.clear();
}
this.annotations.add(label.name);
}
}
this.emitChanges();
console.log(this.annotations);
}
emitChanges() {
this.valueChange.emit(this.getValue());
this.validChange.emit(this.isValid());
}
#HostListener('document:keypress', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
// detect keypress for shortcut
for (const label of this.schema.labels) {
if (label.shortcut === event.key) {
this.toggleAnnotation(label, null);
break;
}
}
}
getValue(): string {
return JSON.stringify(Array.from(this.annotations));
}
isValid(): boolean {
return (this.annotations.size > 0);
}
reset(): void {
this.annotations.clear();
}
}
The only thing I can think of is somehow fire a function from the HTML on component load that adds all the toggle buttons to an array or map which the TS has access to, and search them up by shortcut when I need them, but this seems like a hacky solution.
EDIT: I've tried using ViewChild, but since I can't initialize the ids dynamically (angular viewChild for dynamic elements inside ngFor) i cannot access the components to modify their checked state.

Angular: Blur and empty a form field on click without jQuery?

I have a form input which I would like to blur (de-focus) and empty when a button is clicked.
In AngularJS, I did this in the controller like so:
angular.element('#search-input').val('');
angular.element('#search-input').blur();
In Angular (4.4.4) I have it working like so:
$('#search-input').val('');
$('#search-input').blur();
But I'd rather not use jQuery. What's the proper way to do this in Angular?
Here's the whole component:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'pb-header',
templateUrl: './header.component.html'
})
export class HeaderComponent implements OnInit {
private searchActive: boolean;
constructor() {
this.searchActive = false;
}
ngOnInit() {
}
toggleSearch = function () {
if (this.searchActive) {
this.searchActive = false;
$('#search-input').val('');
$('#search-input').blur();
} else {
this.searchActive = true;
}
};
}
You can use NgForms reset method:
#ViewChild(NgForm)
public form:NgForm;
form.reset(<value>);
Also there is no need for blur, since you are clicking button and it will take focus, if it does not, set tabindex attribute of the button.

How can I execute action after data-bind in Angular 2?

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;
}

TypeScript | JavaScript | Angular 2: Dynamically Set #HostListener Argument

Dynamically Setting the #HostListener's Arguments
I have a directive which needs to listen for any event provided declaratively by the engineer. Here's an example:
import { Directive, Input, ElementRef, HostListener, OnInit } from '#angular/core';
//--
import { Sandbox } from '../../../sandbox';
#Directive({ selector: '[addItem]' })
class AddNewItemDirective implements OnInit {
#Input('addItem') data;
#Input() on: string = 'click';
constructor(private $: Sandbox, private element: ElementRef) { }
ngOnInit() { console.log('#INIT', this); }
#HostListener('click', ['$event']) handleEvent(e) {
console.log('add-item', e);
}
}
export { AddNewItemDirective };
Here's its usage:
<button class="btn btn-primary" [addItem]="{ name: 'Jeffrey' }" on="focus">Add New Item</button>
This works fine. However, my intuition told me I should be able to dynamically set the HostListener's arguments at render time based upon an input parameter:
#Directive({ selector: '[addItem]' })
class AddNewItemDirective implements OnInit {
#Input('addItem') data;
#Input() on: string = 'click';
constructor(private $: Sandbox, private element: ElementRef) { }
ngOnInit() { console.log('#INIT', this); }
#HostListener(this.on, ['$event']) handleEvent(e) {
console.log('add-item', e);
}
}
Of course, this.on would not be overwritten with 'focus' until the time ngOnInit is invoked. To my surprise, this.on throws an error because undefined has no property 'on'. So when my directive class is instantiated, for whatever reason, this === undefined.
I found one similar question here, though, its looking to dynamically modify HostListener at runtime while I just need it modified at compile/render/instantiation time.
Can someone please shed light on how I can accomplish this?
Thx
HostListener is not dynamic, it can not be changed at runtime. You should use Renderer class, which provides listen method:
#Input()
public on:string;
private dispose:Function;
constructor(private renderer:Renderer, private elementRef:ElementRef){}
ngOnInit(){
this.dispose = this.renderer.listen(this.elementRef.nativeElement, this.on, e => console.log(e));
}
ngOnDestroy(){
this.dispose();
}

How to trigger change() in a angular form by a custom control without an input

I do want to create a custom control which does not include any input. Whenever the control changes, I do want to save the complete form.
Our current approach uses the form-changed-event like this:
<form #demoForm="ngForm" (change)="onChange()">
<custom-input name="someValue" [(ngModel)]="dataModel">
</custom-input>
</form>
As you can see, we use the "change"-event to react to any change in the form.
This works fine as long as we have inputs, checkboxes, ... as controls.
But our custom control does only exist out of a simple div we can click on. Whenever I click on the div the value of the control is increased by 1. But the "change"-event of the form is not fired. Do I somehow have to link my custom control to the form? Or are there any events which need to be fired?
import { Component, forwardRef } from '#angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '#angular/forms';
#Component({
selector: 'custom-input',
template: `<div (click)="update()">Click</div>`,
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}]
})
export class CustomInputComponent implements ControlValueAccessor {
private onTouchedCallback: () => void = () => {};
private onChangeCallback: (_: any) => void = () => {};
update(){
this.value++;
}
get value(): any {
return this.innerValue;
};
set value(v: any) {
console.log("Change to");
if (v !== this.innerValue) {
this.innerValue = v;
this.onChangeCallback(v);
}
}
writeValue(value: any) {
if (value !== this.innerValue) {
this.innerValue = value;
}
}
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
}
I've created a plunker to demonstrate the problem:
https://plnkr.co/edit/ushMfJfcmIlfP2U1EW6A
Whenever you click on "Click" the model-value is increased, but there is no output on the console, as the change-event is not fired... (There is a console.log linked to the change-event)
Thanks for your replies.
Finally I found the following solution to this problem:
As Claies mentioned in the comment, my custom component does not fire the change event. Therfore the form does never know about the change. This has nothing todo with angular, but as said is the expected behaviour of a input/form.
The easiest solution is to fire the change-event in the customcontrol when a change happens:
constructor(private element: ElementRef, private renderer: Renderer) {
}
public triggerChanged(){
let event = new CustomEvent('change', {bubbles: true});
this.renderer.invokeElementMethod(this.element.nativeElement, 'dispatchEvent', [event]);
}
That's it, whenever I called "onControlChange(..)" in my custom component, then I fire this event afterward.
Be aware, that you need the Custom-Event-Polyfill to support IE!
https://www.npmjs.com/package/custom-event-polyfill
You need to emit the click event of div to its parent. so that you can handle the event.
Plunker Link
Parent component:
import { Component, forwardRef, Output, EventEmitter } from '#angular/core'; // add output and eventEmitter
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '#angular/forms';
#Component({
selector: 'custom-input',
template: `<div (click)="update($event)">Click</div>`,
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}]
})
export class CustomInputComponent implements ControlValueAccessor {
private onTouchedCallback: () => void = () => {};
private onChangeCallback: (_: any) => void = () => {};
#Output() clickEvent = new EventEmitter(); // add this
update(event){
this.value++;
this.clickEvent.emit(event); // emit the event on click event
}
get value(): any {
return this.innerValue;
};
}
child component:
//our root app component
import {Component} from '#angular/core'
#Component({
selector: 'demo-app',
template: `
<p><span class="boldspan">Model data:</span> {{dataModel}}</p>
<form #demoForm="ngForm">
<custom-input name="someValue"
[(ngModel)]="dataModel" (clickEvent) = onChange()> // handling emitted event here
Write in this wrapper control:
</custom-input>
</form>`
})
export class AppComponent {
dataModel: string = '';
public onChange(){
console.log("onChangeCalled");
}
}
Thanks Stefan for pointing me in the right direction.
Unfortuantely Renderer (which has invokeElementMethod()) has recently been deprecated in favor or Renderer2 (which does not have that method)
So the following worked for me
this.elementRef.nativeElement.dispatchEvent(new CustomEvent('change', { bubbles: true }));
It seems that change event is not fired on form when you call ControlValueAccessor onChange callback (callback passed in registerOnChange function), but valueChanges observable (on the whole form) is triggered.
Instead of:
...
<form (change)="onChange()">
...
you can try to use:
this.form.valueChanges
.subscribe((formValues) => {
...
});
Of course, you must get proper form reference in your component.

Categories

Resources