Very new to Angular, and just trying to get a feel for it. I have an input component:
import { Component } from '#angular/core';
#Component({
selector: 'app-input',
templateUrl: './input.component.html',
})
export class InputComponent {
q = '';
queryChange(value: string) {
this.q = value;
}
}
It's html:
<div>
<input #query type="text" (change)="queryChange(query.value)" />
<button>Search</button>
<div>{{ q }}</div>
</div>
When I type into the input, the {{ q }} doesn't update until I click anywhere on the screen, or hit enter. Almost like refocusing. Coming from React I'm confused as to why this happens with the Angular's (change) rather than updating as I type.
My first thought was that maybe because I'm passing the value of the input to queryChange(query.value) instead of passing the event value like I would usually do in React.
I think the problem is about the DOM and not Angular. You should use (input) instead of (change) if you want the event to trigger every time you type.
<input #query type="text" (input)="queryChange(query.value)" />
See this StackBlitz, as well as change and input MDN references. Specifically, MDN says about change:
Unlike the input event, the change event is not necessarily fired for each alteration to an element's value.
Related
I am using npm package vue-range-component with which I change the value with a slider and then that value is reactively changed in the input
My problem is that I can’t apply the onChange event for inputs, I need to be able to change the value in the input, for example, put the number 70, and then this number was applied to the vue-range-component, something like that
Here is my sandbox code
<template>
<div class="app-content">
<div>
<input type="text" v-model="value[0]" />
<input type="text" v-model="value[1]" />
</div>
<vue-range-slider
v-model="value"
:min="min"
:max="max"
:formatter="formatter"
style="margin-top: 40px"
></vue-range-slider>
<div class="multi-range-value-container">
<p>{{ value[0] }}</p>
<p>{{ value[1] }}</p>
</div>
</div>
</template>
<script>
import "vue-range-component/dist/vue-range-slider.css";
import VueRangeSlider from "vue-range-component";
export default {
data() {
return {
value: [0, 100],
};
},
methods: {
onChange(event) {
console.log(event.target.value);
},
},
components: {
VueRangeSlider,
},
created() {
this.min = 0;
this.max = 1000;
this.formatter = (value) => `$${value}`;
},
};
</script>
One caveat of Vue's reactivity system is that you cannot detect changes to an array when you directly set the value of an item via its index.
As explained in the link, there are two main methods of making array modifications reactive:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
Using these methods forces the VueJS reactivity system to update its state.
To implement this solution in your example, you can use a watcher on a modelled value, an #change event or a #keyup event.
A watcher is likely the most resource intensive approach. I would suggest using #keyup (which would fire on every keypress as opposed to when you unfocus the input with #change) and then debouncing the input if the value is used to filter/sort/load anything.
If you wanted to as well, you could also directly set the entire array.
For example instead of setting value[0] = something you could do value = [...value, something];and this would be reactive.
It is indeed Vue reactivity issuse
You can fix it by not using v-model syntactic sugar but split it like this:
From:
<input type="text" v-model="value[0]" />
to:
<input type="text" :value="value[0]" #input="changeRange(0, $event.target.value)" />
define a method:
methods: {
changeRange(index, value) {
const v = parseInt(value, 10) // value from input is always string
this.$set(this.value, index, v)
}
}
Update
Above code is a fix you would need anyway but unfortunately the vue-range-component has a different issue as it blocks all user keyboard input to any input element on the same page - see this and this issue
It was already fixed by this pull request but it wasn't released yet to npm
You can use some of the workarounds described in the issues BUT given the way how the maintainer (not) handles the issue, it would be probably better for you to look around for another component with similar functionality...
I have an Angular 7 application in which I'm trying to handle a text input in ngAfterViewChecked().
The text input is a node in a mat-tree. It's visibility depends on an ngIf condition. If that condition is not met, I display a span instead. Essentially, if the user double clicks on a node in the tree (a span element), it becomes a text input so that the user can edit the text:
<mat-tree [dataSource]="nestedDataSource" [treeControl]="nestedTreeControl">
<mat-tree-node *matTreeNodeDef="let node">
<li>
<span *ngIf="!node.isInput" (dblClick)="nodeDoubleClicked(node)">{{ node.name }}</span>
<input *ngIf="node.isInput" #nodeNameInput type="text" [(ngModel)]="node.name" (blur)="doneEditting(node)" (keypress)="keyPressed($event, node)" />
</li>
</mat-tree-node>
<mat-nested-tree-node *matTreeNodeDef="let node; when: hasNestedChild">
<button mat-icon-button matTreeNodeToggle>
<mat-icon>
{{ nestedTreeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}
</mat-icon>
</button>
<span *ngIf="!node.isInput" (dblClick)="nodeDoubleClicked(node)">{{ node.name }}</span>
<input *ngIf="node.isInput" #nodeNameInput type="text" [(ngModel)]="node.name" (blur)="doneEditting(node)" (keypress)="keyPressed($event, node)" />
<ul [class.collapsed]="!nestedTreeControl.isExpanded(node)">
<ng-container matTreeNodeOutlet></ng-container>
</ul>
</mat-nested-tree-node>
</mat-tree>
When the user double clicks on a node, I not only want it to turn into an input text, I want it to gain focus and select the text inside. In order to do this, I have to get the native element and call .focus() and .select() on it. In order to get the native element, I have to use ViewChildren (where the input is tagged with #nodeNameInput as you can see in the code snippet above). And finally, I need to hook into ngAfterViewChecked() in order to be sure that the QueryList of the ViewChildren is ready.
Here is the code for the component:
#ViewChildren('nodeNameInput') nodeNameInputs: QueryList<ElementRef>;
...
ngAfterViewChecked() {
if (this.nodeNameInputs && this.nodeNameInputs.length) {
this.nodeNameInputs.first.nativeElement.focus();
this.nodeNameInputs.first.nativeElement.select();
}
}
I've ensured that there is only ever one node being edited at a time, so it's safe to use first rather than search through nodeNameInputs to find the one to put in focus and select the text.
This seems to work, but there is a problem. It seems like for every key stroke, ngAfterViewChecked() is also called. What this means is that as the user is editing the text for the node, it gets re-selected for every key stroke. This results in the text the user enters being overwritten on every key stroke.
I have a workaround to this problem:
ngAfterViewChecked() {
if (this.nodeNameInputs && this.nodeNameInputs.length) {
this.nodeNameInputs.first.nativeElement.focus();
if (!this.keyStroked) {
this.nodeNameInputs.first.nativeElement.select();
}
}
}
...where keyStroked is set in the keyPressed handler and set to false in the blur handler.
But I'm wondering if there is another hook that can reliably be used to focus the input and select its text while not responding to key strokes. I chose ngAfterViewChecked because a test showed that it was the only hook in which nodeNameInputs was consistently ready every time (i.e. this.nodeNameInputs.length was always 1). But maybe I missed certain hooks.
My workaround seems like a hack. How would you solve this problem?
Create a focus directive and place that on the input you want focused, you wont have to worry about life cycle events.
import { Directive, ElementRef } from '#angular/core';
#Directive({
selector: '[focus]'
})
export class FocusDirective {
constructor(elm: ElementRef) {
elm.nativeElement.focus();
}
}
and use it
<input focus>
https://stackblitz.com/edit/angular-qnjw1s?file=src%2Fapp%2Fapp.component.html
I have following code with readonly input. am trying to change the value if this readonly input from TS File, but, I am unable to detect change from any function. Below is the example.
<input type="text" class="form-control" id="field1" name="field1" readonly [(ngModel)]="field1" (ngModelChange)="onChange()">
ngModelChange is not working in this senario.
You can try to use the (change) instead of (ngModelChange).
Here is some explanation to help you understand this.
Use input like this:
(input)="handle($event)"
And Handle event in your typescript accordingly.
FormControl to the rescue!!
FormControls and Reactive forms are great and it will save your day! :)
Mark ReactiveFormsModule in your app.module, import FormControl to your component aaand... For demo I used a simple timeout to change the value to trigger the valueChanges:
myControl = new FormControl('')
constructor() { }
ngOnInit() {
setTimeout(() => {
this.myControl.setValue('something');
}, 2000);
this.myControl.valueChanges.subscribe((data) => {
console.log('changed!')
});
}
And template:
<input type="text" readonly [formControl]="myControl">
StackBlitz
P.S if this is input is part of a template driven form, this wouldn't work, since ngModel and formControl shouldn't be used together. So you would be notified of that. So in that case I suggest you go reactive way ;)
I'm working with angular (typescript) and I have a modelform in html where the user has to insert a code field and a description field.
The code field must always be entered by the user, always in uppercase.
I found and followed this question:
How to convert input value to uppercase in angular 2 (value passing to ngControl)
but the last letter that the user inserts remains lowercase however ..
The fundamental thing is that the database always comes in all uppercase (I also put a limit of 4 characters that works properly)
this is my code now, but as written above it does not work properly:
<input type="text" id="code" #code class="form-control" formControlName="code" maxlength="4"
(input)="code.value=$event.target.value.toUpperCase()">
has anyone found a quick, functional and fast solution?
thank you!
You can simply add oninput="this.value = this.value.toUpperCase()" in your <input> tag and it will instantly convert any input in your input field to Uppercase.
If you're working with ngModel, you can use ngModelChange to do it this way with JavaScript's .ToUpperCase().
<input [ngModel]="person.Name" (ngModelChange)="person.Name = $event.toUpperCase()">
Based on #Ali Heikal answer it should be:
<input #code formControlName="code" (input)="code.value = code.value.toUpperCase()" type="text">
Or use .toLocaleUpperCase() if necessary.
This worked for me. I just used keyup and whenever a user adds a character it automatically converts it to Uppercase, not just in the UI but in the model as well.
(keyup)="form.patchValue({name: $event.target.value.toUpperCase()})"
A bit late, but I'm surprised to don't see the css alternative.
Add on the input field:
.input-upper{
text-transform: uppercase;
}
You could create an Angular Directive which transforms all user input to uppercase.
The Directive extends DefaultValueAccessor:
#Directive({
selector: 'input[toUppercase]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => UpperCaseInputDirective),
},
],
})
export class UpperCaseInputDirective extends DefaultValueAccessor {
#HostListener('input', ['$event']) input($event: InputEvent) {
const target = $event.target as HTMLInputElement;
const start = target.selectionStart;
target.value = target.value.toUpperCase();
target.setSelectionRange(start, start);
this.onChange(target.value);
}
constructor(renderer: Renderer2, elementRef: ElementRef) {
super(renderer, elementRef, false);
}
}
Use the Directive like this on the input:
<input name="myTdfInput" type="text" [(ngModel)]="value" toUppercase>
The solution works in both Reactive Forms and Template Driven Forms.
See working demo on Stackblitz: https://stackblitz.com/edit/angular-input-field-to-accept-only-numbers-czivy3?file=src/app/to-uppercase.directive.ts
Credits for the solution go to: https://twitter.com/BartBurgov (See his tweet here: https://twitter.com/BartBurgov/status/1582394428748009477?s=20&t=RiefRcZ1B8IF42wp2rWzaQ)
You can use a pattern attribute to limit the allowed input. You may also want to assist the user by upper-casing their input.
<input type="text" pattern="[A-Z]*" name="example" />
I implemented a custom component which is a wrapper for an input with NgModel. I connected them with a ControlValueAccessor. It works well, I can easily access values from my parent component.
But if I try to call markAsDirty() the touched flag is only changing on my component, it has no effect to my input inside the component. I will give you an example:
// Parent Component
onSubmit(form: NgForm) {
this.form.controls.registerEmail.markAsDirty();
}
// Thats how the component looks like in my form:
<form #form="ngForm" (ngSubmit)="onSubmit(form)" [ngClass]="{'has-error': !form.valid}">
<form-text label="E-Mail" name="registerEmail" email required placeholder="" [(ngModel)]="eMail"></form-text>
</form>
// Result
<form-text label="E-Mail" name="registerEmail" class="ng-untouched ng-invalid ng-dirty">
<label for="form-text-2">E-Mail</label>
<input class="input-control invalid ng-untouched ng-pristine ng-invalid" type="text" id="form-text-2">
</form-text>
As you can see the form-text has the "ng-dirty" class, the input inside remains pristine.
To implement my custom component I used one the many instructions you find on the web. Here is the one I used: angular2 custom form control with validation json input
I want to mark every input field as dirty when the submit button is pressed. Because my validation shows up, when you blur the input.
I figured out that there is the problem my component inherits from ControlValueAccessor. The only connection between my component and my NgForm is over its NgModel. The NgForm can use my component as FormControl because it has its own NgModel. Over events it's possible to pass values in two directions. But it's not possible with methods like markAsDirty() or markAsTouched(). Inside the component it's no problem. But my NgForm has no real access to components. Only to NgModel.
Is there any way to implement that? I thought it's not that hard to figure it out, but I am struggling for a long time with that. My only solution for the moment is to iterate over every input with jQuery to fire a focus. There must be a cleaner solution for that.
Thx
You can pass as input in the component which implements ControlValueAccessor the dirty property of the form and then update the state of your inner input using ReactiveFormsModule and FormControl.
The component which holds your form:
<form #myForm="ngForm" (submit)="onSubmit($event)">
<my-input name="my-input" [(ngModel)]="myInput" [formDirty]="myForm.dirty"></my-input>
<button type="submit">Submit</button>
</form>
Then in the component which implements ControlValueAccessor:
ngOnChanges({ formDirty }: SimpleChanges) {
if (formDirty.currentValue) {
this.inputCtrl.markAsDirty();
} else {
this.inputCtrl.markAsPristine();
}
}
Here you can find the relevant snippet.
You need to call onTouched() (this._onTouchedCallback) from inside your component when you want the controls status set to touched. Same for this._onChangeCallback
For example by adding (ngModelChange)="onTouched(value)" to the input tag in my-custom-input
Copied from:
https://github.com/angular/angular/issues/10151
The problem is that there is no easy way to get any notification when dirty state changes. See https://github.com/angular/angular/issues/10887.
Helper directive:
#Directive({
selector: 'my-input'
})
export class MagickDirective implements DoCheck {
constructor(private control:NgModel){
}
ngDoCheck(): void {
//you can do whatever you want
if(this.control.dirty) {
(this.control.valueAccessor as MyInputComponent).setDirty(true);
}
}
}