How to immediately set focus on a form control in Angular Material without needing a click event

Currently working with Angular 9 and Material, we have implemented a Stepper to assist users in navigating through our workflow steps. Our goal is to enable users to go through these steps using only the keyboard, without relying on mouse clicks for control navigation.

We have explored various examples of setting focus on controls via click events, but want to avoid requiring users to click a button to set initial focus on the first text area. Is there a way to achieve this automatically when the stepper is initialized? We attempted using element reference in ngAfterViewInit, but it did not produce the desired outcome. Any insights or guidance on how to accomplish this would be greatly appreciated. Thank you.

Below is the relevant HTML code:

<pseudo-common-tool-header title="{{this.title}}"></pseudo-common-tool-header>
<div class="pseudo-content-manager-tool-area">
<mat-horizontal-stepper #stepper>
<mat-step [stepControl]="formGroup">
    <form [formGroup]="formGroup">
        <ng-template matStepLabel>Workstation Ids</ng-template>
        <div>
            <mat-form-field appearance="outline" class="eris-width-45" > ;
            <mat-label>List of Old/New Workstations to swap (one pair per line)</mat-label>
            <textarea #wkstns rows="8" #workstations matInput placeholder="Old Workstation, New Workstation" formControlName="workstations" required></textarea>
            </mat-form-field>
        </div>
        <div>
            <mat-form-field appearance="outline" >
                <mat-label>Soft Delete Date</mat-label>
                <input matInput #softDate [min]="minDate" [matDatepicker]="picker3" formControlName="removalDate">
                        <mat-datepicker-toggle matSuffix [for]="picker3"></mat-datepicker-toggle>
                        <mat-datepicker #picker3 color="primary"></mat-datepicker>
            </mat-form-field>
        </div>
        <div>
            <button color="primary" #step1next mat-raised-button matStepperNext>Next</button>
        </div>
    </form>
</mat-step>
<mat-step  label="Validate">
    <div>
        <button mat-raised-button color="secondary" matStepperPrevious>Back</button>
        <button mat-raised-button color="secondary" matStepperNext>Next</button>
      </div>
</mat-step>
<mat-step>
    <ng-template matStepLabel>Epic Build</ng-template>
</mat-step>

And here is the corresponding Component code snippet:

@Component({
 templateUrl: './swap-workstations.component.html',
 styleUrls: ['./swap-workstations.component.scss']
})

 @PseudoTool({
 title: 'Check Workstation Existence in Epic',
 componentName: 'CheckWorkstationExistenceInEpicComponent'
})

export class SwapWorkstationsComponent extends BasePseudoTool implements OnInit {
     formGroup: FormGroup;
    // minimum date for date picker
    minDate: Date;
 @ViewChild('wkstns') wkstns: ElementRef;
 @ViewChild('softDate') softDate: ElementRef;
 @ViewChild('step1next') step1next: ElementRef;

 constructor(private formBuilder: FormBuilder) {
     super();
 }

 ngOnInit(): void {
   this.formGroup = this.formBuilder.group({
     workstations: ['', Validators.required],
     removalDate: ['', Validators.required]
   });

// Set minimum date for date pickers.
this.minDate = new Date();
 }

 ngViewAfterInit(): void {
   this.wkstns.nativeElement.focus();
 }   

}

Answer №1

When implementing focus on form elements, the autofocus attribute can be unreliable at times. To ensure consistent functionality, I opted to create a directive that guarantees focus is set on the first element in the form. This allows users to easily navigate the form using keyboard inputs.

import { coerceBooleanProperty } from "@angular/cdk/coercion";
import { DOCUMENT } from "@angular/common";
import {
  AfterViewInit,
  Directive,
  ElementRef,
  Inject,
  Input
} from "@angular/core";

@Directive({
  selector: "[autofocus]"
})
export class AutofocusDirective implements AfterViewInit {
  private _host: HTMLElement;
  private _focused: Element | null;
  private _autofocus = true;

  @Input()
  set autofocus(value: boolean | string) {
    this._autofocus = coerceBooleanProperty(value);
  }

  constructor(
    private readonly element: ElementRef,
    @Inject(DOCUMENT) document: HTMLDocument
  ) {
    if (typeof this.element.nativeElement.focus !== "function") {
      throw new Error("Html element must be focusable");
    }
    this._host = element.nativeElement;
    this._focused = document.activeElement;
  }

  public ngAfterViewInit(): void {
    if (this._autofocus && this._host && this._host !== this._focused) {
      setTimeout(() => this.element.nativeElement.focus());
    }
  }
}

To utilize this directive, simply add the autofocus attribute to the desired textarea within your component. This approach not only maintains cleanliness within the component but also ensures reusability across different forms.

Answer №2

Unsure if utilizing setTimeout is the most effective method for achieving this. It seems a bit like a workaround, I might be mistaken but it does get the job done.

ngViewAfterInit(): void {
   setTimeout(() => this.wkstns.nativeElement.focus());
 } 

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Issue: Failed to locate module @angular/core

When attempting to run an Angular 4 application, I consistently encounter the following error: ERROR in Could not resolve module @angular/core There are no additional errors present. There are no dependency issues whatsoever as @angular/core is located i ...

When HTMLElement focus is activated, it interrupts the flow of execution

(the code presented is in TypeScript and I'm working with Angular 5, but I don't think that's the issue, so prove me wrong!) I have a basic input field that triggers events in an Angular component. (EDIT: I've added the complete compo ...

Roll out a custom-built server for an Angular 7, MongoDB, Express, and Node application

I am seeking to host my Angular application with Node.js, MongoDB, and Express.js on a server of my own. My current deployment method involves: -> ng build --prod (generates output in the dist folder) -> ng serve from the dist directory To start th ...

Developing a unique TypeScript singleton pattern tailored for multiple PouchDB instances

I have developed a node application that interfaces with multiple databases. I've designed a class which allows me to create various databases effortlessly, as they share similar CRUD operations. The Class: class DatabaseService { private dbName: ...

React hook triggering re-render

A function has been implemented to retrieve and decode user claims from a token stored in local storage using a hook. export const useActiveUser = (): { user: IUserTokenClaims | null } => { const [user, setUser] = useState<IUserTokenClaims | nul ...

Creating a channel for communication between sibling components in Angular 4 by storing component references in a shared service

I am searching for a way to establish communication between two sibling Angular 4 components. After reviewing the documentation at https://angular.io/guide/component-interaction, my idea revolves around utilizing a service that stores a reference to the c ...

How can you incorporate a module for typings without including it in the final webpack bundle?

As I venture into the realm of Webpack, I am faced with the challenge of transitioning from TypeScript 1.x to TypeScript 2. In my previous projects, I typically worked with TypeScript in one module using separate files, TSD for typings, and compiling throu ...

Unable to retrieve a property from a variable with various data types

In my implementation, I have created a versatile type that can be either of another type or an interface, allowing me to utilize it in functions. type Value = string | number interface IUser { name: string, id: string } export type IGlobalUser = Value | IU ...

what is the reason for why the subscribe method returns a Subscriber instead of the actual value

Within my application, User configurations are retrieved based on a config ID that is stored in the node server. exports.getConfig() = (req, res) => { return res.status(200).send(id); // where id is sourced from a configuration file } In my service ...

How can Angular CLI add extra static resources while live reloading?

Currently, I am utilizing the most recent version of the Angular CLI. The issue I'm facing involves mock http calls that reference local JSON files such as '../app/myfile.json'. When I reload the application, I consistently encounter 404 err ...

What causes the entire set of dynamically created cards to collapse when using the toggle collapse button in ngb-bootstrap's Collapse control?

I am currently implementing the ngb-bootstrap collapse feature in my Angular 9 application. Incorporating the ngb-bootstrap collapse functionality within a Bootstrap card, I have multiple cards being generated. Each card is equipped with a collapse button ...

Is it possible to perform headless Angular 2 E2E tests using Protractor on Vagrant Ubuntu 16.04?

I'm currently utilizing Vagrant to run ubuntu-16.04 and need to execute angular2 end-to-end (e2e) tests in a headless environment for continuous integration. Despite extensively searching through the angular documentation, I have not found any mention ...

Ways to protect the URL link for attachments obtained from cloud services

I have created an Angular form that allows users to upload attachments. Once uploaded, the attachments are securely stored in an Azure Storage Account. Upon successful upload, a unique URL for the attachment is generated and returned. However, the curren ...

What is the best way to eliminate a specific set of characters from a string using TypeScript?

Imagine you have the following lines of code stored in a string variable: let images='<img alt="image1" height="200" src="image1.jpg" width="800"> <img alt="image2" src="image2.jpg" height="501" width="1233"> <img alt="im ...

Navigate back two levels (../../) using Angular2's relative navigation

One issue I am currently experiencing with my application is related to the functionality of a back button that navigates back through multiple levels. When navigating back one level, the code snippet below works perfectly, resulting in a route URL of http ...

Issue with Typescript in react: JSX element lacks construct or call signatures

After upgrading TypeScript, I encountered the error mentioned above in one of my components. In that component's render method, I have the following code: render() { const Tag = props.link ? 'a' : 'div'; return ( < ...

The attribute 'selectionStart' is not a valid property for the type 'EventTarget'

I'm currently utilizing the selectionStart and selectionEnd properties to determine the beginning and ending points of a text selection. Check out the code here: https://codesandbox.io/s/busy-gareth-mr04o Nevertheless, I am facing difficulties in id ...

Is there a way to apply a single mongoose hook to multiple methods in TypeScript?

Referencing the response on How to register same mongoose hook for multiple methods? const hooks = [ 'find', 'findOne', 'update' ]; UserSchema.pre( hooks, function( next ) { // stuff } The provided code functions well wi ...

Unable to find a solution to Angular response options

I'm having trouble saving an item to local storage when receiving a 200 response from the backend. It seems like the request status is not being recognized properly. options = { headers: new HttpHeaders({ 'Content-Type': &apos ...

Utilize Angular Guards and RxJS to coordinate the flow of data between parent and child Guards. Ensure that the Child Guard waits for the parent

Is it possible to validate data in a child component's guard that was fetched from the parent's guard? I have parent components with employees and used a guard to fetch data from the server. Now, I want to verify this data in the child component& ...