A couple of days back I faced trouble adding autofocus to an input field that was rendered inside a Bootstrap modal. The project is built using Angular 5 and Bootstrap 4. Also, I was using ng-bootstrap component library. The issue was that input field was auto focussed only the first time I opened the modal. In subsequent requests, the input field was not auto-focussed. It took me sometime to figure out why this is happening and how to fix the problem. In this quick post, I will walk you through how to enable autofocus in an input field in an Angular 5 Bootstrap 4 application.
Prerequisite
To follow along you will need following on your machine.
- Node.js : Download and install the latest version of node.
- Angular CLI: It is the official way to bootstrap Angular applications. You can install it using your favourite package manager. For npm, you can type
npm install -g @angular/cli
on your command-line. If you use yarn, you can typeyarn global add @angular/cli
on your command-line.
Now that we have installed all the prerequisites let’s get started.
Step 1: Create an Angular 5 application
We will use Angular CLI to create an Angular 5 application.
$ ng new modal-app
This will generate the sample project in the modal-app
directory and install all the dependencies using npm package manager. You can run the application using ng serve
command.
You can start the application by running following commands.
$ cd modal-app $ npm start
Step 2: Install bootstrap 4, ng-bootstrap, and their dependencies
Now, that we have created the Angular 5 application we can install the dependencies that we need for this application. This includes bootstrap 4, ng-bootstrap, jQuery, and popper.js.
$ npm install bootstrap@4.0.0-beta.2 @ng-bootstrap/ng-bootstrap jquery popper.js --save
Step 3: Configure ng-bootstrap
Next, we will import NgbModule
and declare it in the app.module.ts as shown below. In Angular every application has one application level module that bootstraps the application. The convention is to use AppModule
as the name of the module class and app.module.ts
as name of the file.
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, NgbModule.forRoot() ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
As shown in the code snippet above, we added declaration of NgbModule
in the imports
section. imports
defines all the dependencies of a module. The forRoot is a convention for modules that expose a singleton service.
Step 4: Add bootstrap to the project
If you see the application, you will notice that bootstrap styles are not currently applied. This is because we still have to add bootstrap related CSS and javascript files. To do that, replace the styles and scripts tags of the .angular-cli.json with the one shown below:
"styles": [ "styles.css", "../node_modules/bootstrap/dist/css/bootstrap.min.css" ], "scripts": [ "../node_modules/jquery/dist/jquery.min.js", "../node_modules/popper.js/dist/umd/popper.min.js", "../node_modules/bootstrap/dist/js/bootstrap.min.js" ],
Step 5: Add modal to the AppComponent
Update app.component.html with the one shown below.
<!--The content below is only a placeholder and can be replaced.--> <div class="container"> <div class="row"> <div class="col-12"> Open form </div> </div> </div> <ng-template #content l> <div class="modal-header"> <h4 class="modal-title">Create User</h4> </div> <div class="modal-body"> <div class="form-body"> <div class="form-group row"> Name: <div class="col-md-9"> </div> </div> </div> </div> </ng-template>
Update app.component.ts with the one shown below.
import { Component } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { constructor(private modalService: NgbModal) { } open(content) { this.modalService.open(content).result.then((result) => { console.log(result); }, (reason) => { console.log('Closed with reason', reason); }); } }
Now, when you open the modal by pressing the Open form
button, you will see that autofocus is only respected the first time you open the modal. On subsequent request, autofocus is not respected.
This happens because of the way autofocus is defined in the HTML 5 specification. The specification says
The autofocus content attribute allows the author to indicate that a control is to be focused as soon as the page is loaded, allowing the user to just start typing without having to manually focus the main control.
The autofocus attribute only works when page is loaded. The one time it works because it happens after the page load. When we press the button next time, browser does not respect autofocus value as page is already solution.
This is also a popular issue in bootstrap Github repository.
Step 6: Angular Directive to the rescue
Create a new directive using the command shown below.
$ ng g directive autofocus
It will generate the autofocus directive stub. Replace content of autofocus.directive.ts with the one shown below.
import { Directive, AfterViewInit, ElementRef } from '@angular/core'; @Directive({ selector: '[appAutofocus]' }) export class AutofocusDirective implements AfterViewInit { constructor(private el: ElementRef) { } ngAfterViewInit() { this.el.nativeElement.focus(); } }
Now, update the input tag to use this directive as shown below.
<input type="text" id="name" class="form-control" name="name" ngModel appAutofocus required>
Now, your app will correctly work with autofocus.
Glad to see I’m not mental and this is actually an issue other people are having. why doesn’t your fix work for me?
the ANgular-cli adds the right lines to app.modules.ts automatically but when I replace `autofocus` with `ngModel appAutofocus required` my fields aren’t focused at all and they loose their passed `[value]`? I also tried ` ngModel appAutofocus`, same result. what’s going on?
oh I think it might be because I’m using bootstrap and not ng-boostrap, I though “ngModel” was an angualr dirrective.
Thanks so much for the idea of using a directive to solve this! I ended up using a different selector (`input[autofocus]`) so that the directive will automatically be applied everywhere it’s needed.
That works like a charm! Many BIG THANKS!! 🙂
Hi..Does this work for Modal popups also?
how to use [appAutofocus] with condition?
Its not wrking in Andriod Chrome 😦