简体   繁体   中英

How to set focus on the elements of the modal as soon as they are opened in Angular 2?

I am a newbie to Angular JS and with the new version of Angular 2 in place, I am facing trouble implementing the directive which can handle the force focus on the modal which is opened on clicking a button.

There are several similar questions which were asked in the past and the answers are in Angular 1 as follows:

app.directive('focusMe',
    ['$timeout',
        function ($timeout) {
            return {
                link: {
                    pre: function preLink(scope, element, attr) {
                      // ...
                    },
                    post: function postLink(scope, element, attr) {
                        $timeout(function () {
                            element[0].focus();
                        }, 0);

                    }
                }
            }
        }]);
});

I am trying to convert this same piece of code in Angular 2. But I am not sure how to achieve it. Could anyone point me in the right direction by giving me more information on how to achieve this.

Edit:

I tried to implement the directive as follows and when I debug the code, I even see that this directive is being called, but I am still unable to get the focus on the elements on the modal dialog:

import { Directive, ElementRef } from "@angular/core";

@Directive({
    selector: "[ModFocus]"
})

export class ModalFocus {

    constructor(private _el: ElementRef) {

        this._el.nativeElement.focus();
    }

}

Am I doing something wrong here? Or do I have to do something else other than just calling focus() on the nativeElement?

HTML Modal:

<div class="modal-dialog modal-sm" tabindex="-1">
        <div class="modal-content">
            <div class="modal-header">
                <h4 class="modal-title">Are you sure?</h4>
            </div>
            <div class="modal-body">
                Warning
            </div>
        <div class="modal-footer ok-cancel" ModFocus>
                <button type="button" class="btn btn-default" (click)="cancel()">Cancel</button>
                <button type="button" class="btn btn-primary" (click)="delete()" data-dismiss="modal">Delete</button>
            </div>
        </div>
    </div>

Thank you.

When you create a component with Angular 2, you use a different syntax comparing to AngularJS. Your sample code might look like:

import { Component, ElementRef, ViewChild } from "@angular/core";

@Component({
    selector: "whatever",
    template: `<input #myControl type='input'/>`
})
export class MyComponent {
    @ViewChild("myControl") myCtrl: ElementRef;

    constructor () {
        // This is wrong. It should be done in ngOnInit not in the
        // constructor as the element might not yet be available.
        //this.myCtrl.nativeElement.focus();   
    }
}

I was writing this from top of my mind without checking it, but this should give you a pretty good idea about the direction in which you should go.

UPDATE:

As you updated the question, here is something that I think is wrong. In your new code you are setting the focus in the constructor. It might be the case that your view is still not generated and therefore the element to which you want to set the focus is still not available ( in my previous example I misguided you as I instantiated it in the constructor, when I wanted OnInit . I apologize for that. ). I would do the following:

import { Directive, ElementRef, OnInit } from "@angular/core";

@Directive({
    selector: "[ModFocus]"
})

export class ModalFocus implements OnInit {

    constructor(private _el: ElementRef) {

    }

    ngOnInit(): any {
        // At this point, your element should be available.
        this._el.nativeElement.focus();
    }
}

OnInit is one of the lifecycle hooks that Angular 2 emits. I would suggest that you go through them to get the better understanding when they are invoked.

UPDATE 2:

The problem is that Directives don't have templates. The act upon the element to which they were added. When a directives is created its constructor looks like:

constructor(private el: ElementRef, private renderer: Renderer) { }

The el should have access to this.el.nativeElement.focus()

Have a look at this article on angular.io about Attribute Directives

My implementation of directive in my question was actually correct. The problem why the focus was not going onto the modal was because of the tabindex = -1 placement was wrong.

The following is the directive which I created. I didn't use the ElementRef anymore directly. Instead, I used the Renderer as Angular docs clearly mentioned to avoid the classes which are tagged with security risk .

import { Directive, ElementRef, Renderer} from "@angular/core";
@Directive({
    selector: "[ModFocus]"
})

export class modalFocus {
    constructor(private _el: ElementRef, private renderer: Renderer) {
        this.renderer.invokeElementMethod(this._el.nativeElement, 'focus');
    }

}

HTML:

<div class="modal fade" tabindex="-1"> 
<div class="modal-dialog modal-sm">
        <div class="modal-content">
            <div class="modal-header">
                <h4 class="modal-title">Are you sure?</h4>
            </div>
            <div class="modal-body">
                Warning
            </div>
        <div class="modal-footer ok-cancel">
                <button type="button" class="btn btn-default" (click)="cancel()" ModFocus>Cancel</button>
                <button type="button" class="btn btn-primary" (click)="delete()" data-dismiss="modal">Delete</button>
            </div>
        </div>
    </div>
</div>

In the above html, I was actually missing the tabindex on the main tag with class modal fade. Adding tabindex there took the focus to the buttons on the modal when opened.

Husein gave valuable inputs which were really helpful. Hence, I am accepting his answer. Thank you once again Husein.

trapFocus(){
    // add all the elements inside modal which you want to make focusable
const  focusableElements = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
const modal:any = document.querySelector('#deactivate-modal'); // select the modal by it's id

const firstFocusableElement = modal.querySelectorAll(focusableElements)[0]; // get first element to be focused inside modal
const focusableContent = modal.querySelectorAll(focusableElements);
const lastFocusableElement = focusableContent[focusableContent.length - 1]; // get last element to be focused inside modal


document.addEventListener('keydown', function(e) {
let isTabPressed = e.key === 'Tab' || e.keyCode === 9;

if (!isTabPressed) {
return;
}

if (e.shiftKey) { // if shift key pressed for shift + tab combination
if (document.activeElement === firstFocusableElement) {
  lastFocusableElement.focus(); // add focus for the last focusable element
  e.preventDefault();
}
} else { // if tab key is pressed
if (document.activeElement === lastFocusableElement) { // if focused has reached to last focusable element then focus first focusable element after pressing tab
  firstFocusableElement.focus(); // add focus for the first focusable element
  e.preventDefault();
}
}
});

firstFocusableElement.focus();

  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM