Angular2 Dynamic Input Field Lose Focus When Input Changes

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:

  1. Losing focus means that the component unmounts and remounts every time.

  2. 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



Leave a reply



Submit