I did create a directive which prevents any character to be typed in an input if it doesn't match a given pattern.
import { Directive, Input, Output, HostListener, EventEmitter } from "#angular/core"
#Directive({
selector: '[ngModel][typingPattern]'
})
export class TypingPatternDirective {
#Input() typingPattern: string
#Input() ngModel
#Output() ngModelChange = new EventEmitter()
#Input() global: boolean = true // Global modifier
private oldValue: any
/* On key down, we record the old value */
#HostListener('keydown', ['$event'])
onKeyDown($event) {
this.oldValue = $event.target.value
}
#HostListener('input', ['$event'])
onInput($event) {
if (!$event.target.value.match( new RegExp(this.typingPattern, this.global ? 'g' : '')) ) {
$event.target.value = this.oldValue.trim()
}
this.ngModelChange.emit($event.target.value)
}
#HostListener('paste', ['$event'])
onPaste($event) {
this.onInput($event)
}
}
And here is how I use it on a input element:
<input type="text" [ngModel]="..." [typingPattern]="$[0-9]{0,8}^" required>
The only bug I currently have happens if in that particular example I type any characters like h. The key is gonna be prevented by my directive, but the required property is gonna considered that a character has been added and thus set the input to valid even though my ngModel value is empty and nothing is displayed. I probably need to event.preventDefault() but I am not sure where.
I manage to get around this issue by encapsulating the this.ngModelChange.emit($event.target.value)
in a setTimeout()
by doing so, the input validation is retriggered again after, and thus the state of my input get updated correctly (the directive can thus be used correctly with required or other validators). It works for now, but it's definitely a bit hacky, and a better handler of the events should lead to a better solution.
If i'm understanding what you're trying to do, you want to have your input filtered out by the regex passed to typingPattern. If so, then your ngModelChange EventEmitter should be emitting the 'new value' after it has been checked (and cleaned) by the RegEx. So, if the user types, in sequence: 1,2,y, then your ngModelChange should emit: 1, 12, 12.
If the above is true, then you need to capture the old value of your input BEFORE the keydown event. Then, on each keydown, if the new character is acceptable, you can ngModelChange.emit the value of your input. If, however, the new character is not acceptable, then you should emit the old value that you previously stored.
Does that make sense?
Related
I have a simple Angular component which takes a debounce time as an Input parameter. The parameter type is a number with a default value equals to 0. It is used later on as a debounceTime value.
input.component.ts
import { AfterViewInit, Component, Input } from '#angular/core';
import { debounceTime } from 'rxjs';
#Component({
selector: 'app-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.scss']
})
export class InputComponent implements AfterViewInit {
#Input() debounce: number = 0;
ngAfterViewInit(): void {
//(...).pipe(debounceTime(this.debounce)).subscribe(...);
}
}
I would like to pass the debounce value from the parent component without binding to the local variable or constant.
app.component.html
<app-input debounce="200"></app-input>
It doesn't work because debounce value comes as a string type from a template. It does work when I use binding to the local constant. A console error confirms the type.
Error: src/app/app.component.html:3:12 - error TS2322: Type 'string' is not assignable to type 'number'.
I can disable strict templates checks as this is a default for Angular 15 but rxjs "debounceTime" function won't accept a string parameter.
How can I pass a number directly from the template without using bindings?
You can just use [] for input to pass a number like this:
<app-input [debounce]="200"></app-input>
you'll now get a number.
Use a setter
debounce: number = 0;
#Input() set _debounce(value:any){
this.debounce=+value;
}
Or use
.pipe(debounceTime(+this.debounce)
NOTE: The + is a confortable way to parse a string to number
I have an Angular 2.4.0 application I'm working on with a form that has some backing Javascript validating/formatting a couple of the fields. When the formatting of the fields completes, the view does not update if the value returned from the formatting matches the original value attached to the model. Is there a way to force the view to update? Since there isn't a model change, forcing a component refresh hasn't had any effect. I'm guessing I'll need to update the view separately with something like jQuery, but I wanted to check if there was a better solution first.
Component:
export class Component {
field: string
formatField(updatedField: string) {
this.field = updatedField.replace(new Regexp("[^\\d]", "g"), ""); // remove everything except numbers
}
}
HTML:
<html>
<body>
<input type="text" [ngModel]="field" (ngModelChange)="formatField($event)">
</body>
</html>
In the above example, if the model is "1", then I enter "1;['];[", and formatField returns "1", the screen will still display "1;['];[" when I'm expecting "1" to display instead (which is the returned value of the formatField call).
Edit: fixed ngModelUpdate to ngModelChange in example (typo). Added Angular 2 version to description.
To refresh the view you need to explicitly run the change detector after you make a change to the model, then you can modify the model and ngModel will update the value.
constructor(private cd: ChangeDetectorRef) {}
formatField(updatedField: string) {
this.field = updatedField;
this.cd.detectChanges();
this.field = updatedField.replace(new RegExp("[^\\d]", "g"), ""); // remove everything except numbers
}
I have an input field where a user can search different user in my app. But I want to make a search in such a way that when the user types in the input it should simultaneously search.
I have implemented my fuction which calls my back-end but i just want to know how can i send the value of input field with every keystroke.
Please help
You should handle the ngModelChange Angular event (see this stackblitz). It takes into account any kind of change in the input field (key strokes, cut/paste with context menu, etc.), and ignores key strokes that do not result in an actual change (e.g. pressing and releasing Shift by itself does not trigger ngModelChange).
<input [ngModel]="searchStr" (ngModelChange)="modelChange($event)" />
import { Component } from "#angular/core";
import { FormsModule } from "#angular/forms";
#Component({
})
export class AppComponent {
public searchStr: string = "";
public modelChange(str: string): void {
this.searchStr = str;
// Add code for searching here
}
}
Use (keyup) event on your input and pass the ngModel value to your service
<input #term (keyup)="search(term.value)" type="text" class="form-control search-text-indent searchbar-positioning" >
and in component.ts
search(term: string): void {
//pass it to your service
}
I would use a form control to expose the valueChanges observable. This observable emits every time the value of the form control changes.
<input [formControl]="search">
This allows you to control the flow of the typeahead search
// component.ts
ngOnInit(){
this.search.valueChanges
.pipe(
// debounce input for 400 milliseconds
debounceTime(400),
// only emit if emission is different from previous emission
distinctUntilChanged(),
// switch map api call. This will cause previous api call to be ignored if it is still running when new emission comes along
switchMap(res => this.getSearchData(res))
)
.subscribe(res => {
this.result = res;
})
}
With this, you are controlling how often the api call gets called (debounceTime & distinctUntilChanged) and ensuring the order of which the api calls finish (switchMap). Here is a stack blitz demoing this.
I have a form model with some indicators that are represented as checkboxes. Currently they model their value as true/false in the form object json. What I would like is for their value to convert from a boolean to a number, 1/0 respectively. Is there a smart way to do this?
Example code:
#Component
template: `
<form [formGroup]="myForm" (ngSubmit)="save(myForm.value)">
<input type="checkbox" id="myToggle" formControlName="myToggle"/>
</form>
`
export class MyComponent implementes OnInit{
private myForm:FormGroup;
myToggle: number;
constructor(private _fb:FormBuilder) {}
ngOnInit() {
this.myForm = this._fb.group({
myToggle: [0]
});
}
Hopefully the above is demonstrating that I'm trying to set the type of "myToggle" to a number. Initializing the form is setting the default to 0 and correctly leaving the checkbox unchecked. However, updating the checkbox to checked will set the form value to true instead of 1. I want it to be updated to 1. Via this question I see that there are some options for converting booleans to numbers. However, I'm unsure of exactly how to implement this with the reactive form model.
FormControl has registerOnChange method, which allows you to specify a callback executed after every change.
Having your example you can access control let ctrl = this.myForm.controls["myToggle"] (I'd prefer create this manually) and then you can do sth like ctrl.registerOnChange(() => ctrl.patchValue(ctrl.value ? 1 : 0));.
How to update the json 'oup' value from a child component to parent component.
When i try to update the value from child model with output event in to a parent component nothing works.
i given all the details below
Data Handled:
json: [{
"inp": "hello",
"oup": "fello"
}]
parent component
// Parent Component
#Component({
selector: 'parent',
template: `
<div>Parent sharedVarParent: {{sharedVarParent[0].oup}}
<input type="text" [ngModel]="sharedVarParent[0].oup"/>
</div>
<child [(sharedVar)]="sharedVarParent"></child>
`,
directives: [ChildComponent]
})
export class ParentComponent {
sharedVarParent =[{
"inp": "hello",
"oup": "fello"
}]
constructor() { console.clear(); }
}
childcomponent
import {Component, EventEmitter, Input, Output} from 'angular2/core'
// Child Component
#Component({
selector: 'child',
template: `
<p>Child sharedVar: {{sharedVar[0].oup}}</p>
<input [ngModel]="sharedVar[0].oup" (ngModelChange)="change($event)">
`
})
export class ChildComponent {
#Input() sharedVar: string;
#Output() sharedVarChange = new EventEmitter();
change(newValue) {
console.log('newvalue', newValue)
this.sharedVar = newValue;
this.sharedVarChange.emit(newValue);
}
}
This looks a bit weird to me. You assign the fello value to the input and then emit the updated value. It seems to me you want to emit the changed sharedVar instead
<input [(ngModel)]="sharedVar[0].oup" (ngModelChange)="change()">
change() {
this.sharedVarChange.emit(sharedVar);
}
Is there a reason you send your array as the input instead of just the string? plnkr
Looking at your sample there are a few odd things:
Your parent input is a one way binding, I don't know if that is confusing you, it's unusual and adds confusion to the question (fixed)
<input [(ngModel)]="sharedVarParent[0].oup"/>
Since the array is passed by reference as input, there's not really a need for an output of your child component unless you are creating a new array, just use two-way binding on the input in your child and it will update the property on the first object in the shared array (sample)
<input [(ngModel)]="sharedVar[0].oup">
You're mixing passing the sharedVar array as input and outputting a string from the emitter when the child's text box value is updated. When that happens, the binding in the parent changes sharedVarParent to be a string, messing the whole thing up. When the parent instantiates the child, it sets the input sharedVar to the array. When you type in the child's textbox it outputs a string and the parent changes sharedVarParent to be a string, which is then sent as input to the child. plnkr