11/08/2018, 20:20

quick note angular 1 autofocus fix

Vừa nãy mình làm một cái search component dùng angular 1, gồm một cái toggle khi ấn vào thì overlay search hiện ra. Tất nhiên cái này thì piece of cake mẹ rồi. Tuy nhiên vấn đề là cái search text input phải tự focus vào nó chứ không thể để người dùng phải thêm 1 extra click được. OK, vậy anh thêm ...

Vừa nãy mình làm một cái search component dùng angular 1, gồm một cái toggle khi ấn vào thì overlay search hiện ra. Tất nhiên cái này thì piece of cake mẹ rồi. Tuy nhiên vấn đề là cái search text input phải tự focus vào nó chứ không thể để người dùng phải thêm 1 extra click được. OK, vậy anh thêm cái attribute autofocus vào cái input là xong chứ gì.

Tuy nhiên đếch biết tại sao input chỉ focus vào lần đầu tiên search overlay được render, còn những lần sau không hoạt động nữa. Tôi đoán là do angular cache lại các element này chứ không tạo lại nữa, còn autofocus chỉ hoạt động khi element vừa được khởi tạo.

Thế là đành viết một directive extend cái attribute autofocus. Nguyên nhân là muốn focus phải manipulate DOM, mà component lại không có link function (Angular khuyến cáo không manipulate DOM trong controller, vì nó được gọi lúc DOM chưa render xong)

 
<input type="text" autofocus/>
function autofocusDirective() {
    return {
        restrict: 'A',
        link(scope, elm, attr, ctrl) {
            elm[0].focus()
        }
    }
}

module.directive('autofocus', autofocusDirective)

Tuy nhiên không hoạt động :smile:

Mất mười phút không hiểu chuyện gì xảy ra. Trong một nỗ lực tuyệt vọng, mình thêm cái setTImeout vào để defer việc gọi focus sang next tick, để cho DOM có thêm 1 chút thời gian render

// ...
setTimeout(() => {
    elm[0].focus()
}, 0)

Thế là tự nhiên chạy. Thần kỳ vãi lúa :smile:

Bonus: component lifecycle hook

Có một cách can thiệp vào DOM ngay trong controller là sử dụng component lifecycle hook $postLink(). Hàm này cũng giống hàm link của directive, túc là được gọi lúc directive đã gắn vào cây DOM.

SearchResultController.$inject = [
    '$element' //inject cái service này để tương tác được với DOM
]
function SearchResultController($element) {
    var vm = this;

    vm.$postLink = function() {
        setTimeout(function() {
            $elment.find('input').focus()
        }, 0);
    }
}
0