Angular2 Dynamic input field lose focus when input changes
This happens when the array is a primitive type, in your case a String
array. This can be solved by using TrackBy
. So change your template to match the following:
<div *ngFor="let value of field.values; let i=index; trackBy:trackByFn">
<input type="text" [(ngModel)]="field.values[i]" /><br/>
</div>
<div>
<button (click)="addValue(field)">Click</button>
</div>
and in the ts file add the function trackByFn
, which returns the (unique) index
of the value:
trackByFn(index: any, item: any) {
return index;
}
This is a link about the same issue, except the issue is for AngularJS, but the problem corresponds yours. Most important excerpt from that page:
You are repeating over an array and you are changing the items of the array (note that your items are strings, which are primitives in JS and thus compared "by value"). Since new items are detected, old elements are removed from the DOM and new ones are created (which obviously don't get focus).
With TrackBy
Angular can track which items have been added (or removed) according to the unique identifier and create or destroy only the things that changed which means you don't lose focus on your input field :)
As seen in the link you can also modify your array to contain objects which are unique and use [(ngModel)]="value.id"
for example, but that's maybe not what you need.
Input loses focus when filtering an array, rendered using *ngFor and Clarity's Angular components
trackBy
is perfectly fine while the issue is with clrDropdownItem
directive applied to <li>
which was not required.
This directive auto gains the focus to the first element as soon as list is updated.
I just removed it and now your code is working fine.
Here is a link with working code - https://stackblitz.com/edit/clarity-dark-theme-v5-cat3jm?devtoolsheight=33&file=src%2Fapp%2Fapp.component.html
Autofocus on a dynamically added input field
a directive like
@Directive({
selector: "[autofocus]"
})
export class AutoFocusDirective {
constructor(public elementRef: ElementRef) {}
ngOnInit() {
setTimeout(() => {
this.elementRef.nativeElement.focus();
});
}
}
In a
<div *ngFor="let item of items">
<input autofocus>
</div>
<button (click)="items.push(1)">add</button>
See stackblitz
Must be work
Value appears in all dynamic input fields
The values will be synchronized because all of your inputs have the same form control name. They also all have the same id which is not good.
I think you are looking for a solution like this:
counter = 1;
fields = [];
userProfile = new FormGroup({
username: new FormControl(''),
address: new FormControl(''),
});
ngOnInit() {
this.refreshFields();
}
refreshFields() {
this.fields = Object.getOwnPropertyNames(this.userProfile.controls);
}
addField() {
const name = `extra-field-${this.counter++}`;
this.userProfile.addControl(name, new FormControl(''));
this.refreshFields();
}
<form [formGroup]="userProfile">
<div *ngFor="let field of fields">
<input
id="{{ field }}"
formControlName="{{ field }}"
type="text"
placeholder="Type something here..."
/>
</div>
<button (click)="addField()">Add Field</button>
</form>
I use a counter to generate a unique name for each new field, but you can do it however you want. Just need to make sure you don't get any naming collisions.
This also allows deleting fields like so:
deleteField(name: string) {
this.userProfile.removeControl(name);
this.refreshFields();
}
Stackblitz: https://stackblitz.com/edit/angular-formbuilder-z3w5a6?file=src/app/app.component.ts
in Angular2 how to know when ANY form input field lost focus
The blur
event doesn't bubble, therefore we need to listen on every input element directly. Angular provides a nice solution for this situation.
A directive that applies to all input elements inside your template.
This directive uses a host-listener to listen for the blur
events on all elements where the selector applies and forwards a bubbling input-blur
event:
@Directive({
selector: 'input,select',
host: {'(blur)': 'onBlur($event)'}
})
class BlurForwarder {
constructor(private elRef:ElementRef, private renderer:Renderer) {}
onBlur($event) {
this.renderer.invokeElementMethod(this.elRef.nativeElement,
'dispatchEvent',
[new CustomEvent('input-blur', { bubbles: true })]);
// or just
// el.dispatchEvent(new CustomEvent('input-blur', { bubbles: true }));
// if you don't care about webworker compatibility
}
}
By adding the BlurForwarder
directive to directives: [...]
it will be applied to all elements in its template that match the selector.
The host-listener listens for bubbling input-blur
events and calls our event handler:
@Component({
selector: 'my-component',
directives: [BlurForwarder],
host: {'(input-blur)':'onInputBlur($event)'},
template: `
<form>
<input type="text" [(ngModel)]="xxx">
<input type="text" [(ngModel)]="yyy">
<input type="text" [(ngModel)]="zzz">
</form>`
}) {
onInputBlur(event) {
doSomething();
}
}
Input fields lose focus on each value change in a complex Formik form
Solved the issue
I've removed all the possible component creation code and moved everything inside the component
props of the Step
component provided by the formik-wizard-form
library.
Here's my working solution: https://codesandbox.io/s/formik-form-wizard-test-lz3x7 (hope this will help somebody)
Many thanks to everyone in the comments section!
Main insights:
Losing focus means that the component
unmounts and remounts
every time.This unmounting/remounting behaviour on every change was caused by the creation of components inside the
render
function of another component. Consequently, on every re-render the input components were created anew.
My final working code:
const MyForm = props => {
return (
<FormikWizardProvider {...props}>
{renderProps => (
<Wizard {...renderProps}>
<StepsList>
{formSetup.pages.map(page => (
<Step
title={page.name}
key={page.name}
component={() =>
page.fields.map(field => {
if (
field.props &&
field.props.visibilityCheckbox &&
!props.values[field.props.visibilityCheckbox]
) {
return null;
}
const fieldProps = {
formik: props,
key: field.props.name,
...field.props
};
switch (field.type) {
case "input":
return <MyTextInput {...fieldProps} />;
case "radio":
return <RadioButtonGroup {...fieldProps} />;
case "checkbox":
return <MyCheckbox {...fieldProps} />;
case "select":
return <MySelect {...fieldProps} />;
default:
return null;
}
})
}
/>
))}
</StepsList>
<ButtonsList>
<PreviousButton />
<NextButton />
<SubmitButton />
</ButtonsList>
</Wizard>
)}
</FormikWizardProvider>
);
};
export default decorateWizardWithFormik(MyForm);
Related Topics
How to Download File from Server
React - Preventing Form Submission
Leaflet Drawing Tiles Disjointly
What Is Difference Between Axios and Fetch
How to Add a Key Prop to a React Fragment
Associative Array Versus Object in JavaScript
Method for Streaming Data from Browser to Server via Http
About Closure, Lexicalenvironment and Gc
Google Map API V3 - Set Bounds and Center
How to Disable Signature Checking for Firefox Add-Ons
How to Declare a Global Variable in JavaScript
Can't Throw Error from Within an Async Promise Executor Function
Does Using $This Instead of $(This) Provide a Performance Enhancement
How to Get the Difference of Two Dates in Mm-Dd-Hh Format in JavaScript