Imposta la messa a fuoco dell'elemento in modo angolare


113

Dopo aver cercato esempi di come impostare gli elementi di messa a fuoco con angolare, ho visto che la maggior parte di loro usa alcune variabili per guardare per poi impostare la messa a fuoco, e la maggior parte di loro usa una variabile diversa per ogni campo che vogliono impostare. In una forma, con molti campi, ciò implica molte variabili diverse.

Con il modo jquery in mente, ma volendo farlo in modo angolare, ho fatto una soluzione che mettiamo a fuoco in qualsiasi funzione usando l'id dell'elemento, quindi, poiché sono molto nuovo in angolare, mi piacerebbe avere alcune opinioni se in questo modo è giusto, avere problemi, qualunque cosa, qualsiasi cosa che possa aiutarmi a farlo nel modo migliore in angolare.

Fondamentalmente, creo una direttiva che controlla un valore di ambito definito dall'utente con la direttiva, o il focusElement di default, e quando quel valore è uguale all'id dell'elemento, quell'elemento imposta il focus stesso.

angular.module('appnamehere')
  .directive('myFocus', function () {
    return {
      restrict: 'A',
      link: function postLink(scope, element, attrs) {
        if (attrs.myFocus == "") {
          attrs.myFocus = "focusElement";
        }
        scope.$watch(attrs.myFocus, function(value) {
          if(value == attrs.id) {
            element[0].focus();
          }
        });
        element.on("blur", function() {
          scope[attrs.myFocus] = "";
          scope.$apply();
        })        
      }
    };
  });

Un input che deve essere focalizzato per qualche motivo, farà in questo modo

<input my-focus id="input1" type="text" />

Qui qualsiasi elemento per mettere a fuoco:

<a href="" ng-click="clickButton()" >Set focus</a>

E la funzione di esempio che mette a fuoco:

$scope.clickButton = function() {
    $scope.focusElement = "input1";
}

È una buona soluzione in angolare? Ha problemi che con la mia scarsa esperienza non vedo ancora?

Risposte:


173

Il problema con la tua soluzione è che non funziona bene quando è legata ad altre direttive che creano un nuovo ambito, ad es ng-repeat. Una soluzione migliore sarebbe semplicemente creare una funzione di servizio che ti permetta di focalizzare gli elementi in modo imperativo all'interno dei tuoi controller o di focalizzare gli elementi in modo dichiarativo nell'html.

DEMO

JAVASCRIPT

Servizio

 .factory('focus', function($timeout, $window) {
    return function(id) {
      // timeout makes sure that it is invoked after any other event has been triggered.
      // e.g. click events that need to run before the focus or
      // inputs elements that are in a disabled state but are enabled when those events
      // are triggered.
      $timeout(function() {
        var element = $window.document.getElementById(id);
        if(element)
          element.focus();
      });
    };
  });

Direttiva

  .directive('eventFocus', function(focus) {
    return function(scope, elem, attr) {
      elem.on(attr.eventFocus, function() {
        focus(attr.eventFocusId);
      });

      // Removes bound events in the element itself
      // when the scope is destroyed
      scope.$on('$destroy', function() {
        elem.off(attr.eventFocus);
      });
    };
  });

controllore

.controller('Ctrl', function($scope, focus) {
    $scope.doSomething = function() {
      // do something awesome
      focus('email');
    };
  });

HTML

<input type="email" id="email" class="form-control">
<button event-focus="click" event-focus-id="email">Declarative Focus</button>
<button ng-click="doSomething()">Imperative Focus</button>

Mi piace molto questa soluzione. Puoi spiegare, tuttavia, il motivo per utilizzare $ timeout un po 'di più? Il motivo per cui l'hai usato è dovuto a una "cosa angolare" o "cosa DOM"?
user1821052

Si assicura che venga eseguito dopo tutti i cicli di digest che angular esegue, ma questo esclude i cicli di digest che sono interessati dopo un'azione asincrona eseguita dopo il timeout.
ryeballar

3
Grazie! Per coloro che si chiedono dove sia fatto riferimento nei documenti angolari, ecco il link (mi ci è voluto un'eternità per trovarlo)
user1821052

@ryeballar, grazie !. Bella soluzione semplice. Solo una domanda però. Posso usare la fabbrica creata tramite attributo invece di aspettare che accada qualche evento?
Pratik Gaikwad

4
È folle la quantità di lavoro necessaria in angolare solo per focalizzare un input.
Bruno Santos

19

A proposito di questa soluzione, potremmo semplicemente creare una direttiva e collegarla all'elemento DOM che deve ottenere il focus quando una data condizione è soddisfatta. Seguendo questo approccio evitiamo di accoppiare il controller agli ID dell'elemento DOM.

Direttiva sul codice di esempio:

gbndirectives.directive('focusOnCondition', ['$timeout',
    function ($timeout) {
        var checkDirectivePrerequisites = function (attrs) {
          if (!attrs.focusOnCondition && attrs.focusOnCondition != "") {
                throw "FocusOnCondition missing attribute to evaluate";
          }
        }

        return {            
            restrict: "A",
            link: function (scope, element, attrs, ctrls) {
                checkDirectivePrerequisites(attrs);

                scope.$watch(attrs.focusOnCondition, function (currentValue, lastValue) {
                    if(currentValue == true) {
                        $timeout(function () {                                                
                            element.focus();
                        });
                    }
                });
            }
        };
    }
]);

Un possibile utilizzo

.controller('Ctrl', function($scope) {
   $scope.myCondition = false;
   // you can just add this to a radiobutton click value
   // or just watch for a value to change...
   $scope.doSomething = function(newMyConditionValue) {
       // do something awesome
       $scope.myCondition = newMyConditionValue;
  };

});

HTML

<input focus-on-condition="myCondition">

1
cosa accadrà quando la myConditionvariabile $ scope è già stata impostata su true e quindi l'utente sceglie di concentrarsi su un altro elemento, puoi comunque riattivare il focus quando myConditionè già vero, il tuo codice guarda le modifiche per l'attributo focusOnConditionma non si attiverà quando il valore che cerchi di cambiare è sempre lo stesso.
ryeballar

Aggiornerò l'esempio, nel nostro caso abbiamo due pulsanti di opzione e impostiamo il flag su true o false a seconda del valore, potresti semplicemente cambiare il flag myCondition su true o false
Braulio

Sembra una soluzione generica. Meglio che dipendere dagli ID. Mi piace.
mortb

Nel caso in cui qualcun altro lo provasse e non funzionasse, ho dovuto cambiare element.focus (); all'elemento [0] .focus ();
Adrian Carr,

1
Questa soluzione è molto più "angolare" dell'hack basato su id di cui sopra.
setec

11

Mi piace evitare le ricerche DOM, gli orologi e gli emettitori globali quando possibile, quindi utilizzo un approccio più diretto. Usa una direttiva per assegnare una semplice funzione che si concentri sull'elemento direttiva. Quindi chiamare quella funzione ovunque sia necessario nell'ambito del controller.

Ecco un approccio semplificato per collegarlo all'ambito. Vedere lo snippet completo per la gestione della sintassi controller-as.

Direttiva:

app.directive('inputFocusFunction', function () {
    'use strict';
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            scope[attr.inputFocusFunction] = function () {
                element[0].focus();
            };
        }
    };
});

e in html:

<input input-focus-function="focusOnSaveInput" ng-model="saveName">
<button ng-click="focusOnSaveInput()">Focus</button>

o nel controller:

$scope.focusOnSaveInput();

Modificato per fornire ulteriori spiegazioni sul motivo di questo approccio e per estendere lo snippet di codice per l'utilizzo come controller.


È molto bello e ha funzionato bene per me. Ma ora ho una serie di input che utilizzano ng-repeate voglio solo impostare la funzione di messa a fuoco per il primo. Qualche idea su come potrei impostare in modo condizionale una funzione di messa a fuoco <input>basata, $indexad esempio, su?
Garret Wilson,

Sono contento che sia utile. Il mio angolare 1 è un po 'arrugginito, ma dovresti essere in grado di aggiungere un altro attributo all'input, come assign-focus-function-if="{{$index===0}}", e poi come prima riga della direttiva esci prima di assegnare una funzione se ciò non è vero: if (attr.assignFocusFunctionIf===false) return; Nota sto controllando se è esplicitamente falsee non solo falso, quindi la direttiva continuerà a funzionare se quell'attributo non è definito.
cstricklan

Controller-as è molto più semplice con lodash. _.set(scope, attributes.focusOnSaveInput, function() { element.focus(); }).
Atomosk

9

Puoi provare

angular.element('#<elementId>').focus();

per es.

angular.element('#txtUserId').focus();

sta funzionando per me.


4
Nota: questo funzionerebbe solo se si utilizza jQuery completo anziché fare affidamento su jqLite incorporato in Angular. Vedi docs.angularjs.org/api/ng/function/angular.element
John Rix

4
Questo è il modo jQuery per farlo, non un modo angolare. La domanda chiede specificamente come farlo in modo angolare.
perdonato il

4

Un'altra opzione sarebbe quella di utilizzare l'architettura pub-sub incorporata di Angular per notificare alla tua direttiva di concentrarsi. Simile agli altri approcci, ma non è quindi direttamente legato a una proprietà e ascolta invece il suo ambito per una particolare chiave.

Direttiva:

angular.module("app").directive("focusOn", function($timeout) {
  return {
    restrict: "A",
    link: function(scope, element, attrs) {
      scope.$on(attrs.focusOn, function(e) {
        $timeout((function() {
          element[0].focus();
        }), 10);
      });
    }
  };
});

HTML:

<input type="text" name="text_input" ng-model="ctrl.model" focus-on="focusTextInput" />

controller:

//Assume this is within your controller
//And you've hit the point where you want to focus the input:
$scope.$broadcast("focusTextInput");

3

Ho preferito usare un'espressione. Questo mi consente di fare cose come concentrarmi su un pulsante quando un campo è valido, raggiunge una certa lunghezza e, naturalmente, dopo il caricamento.

<button type="button" moo-focus-expression="form.phone.$valid">
<button type="submit" moo-focus-expression="smsconfirm.length == 6">
<input type="text" moo-focus-expression="true">

In una forma complessa, ciò riduce anche la necessità di creare variabili di ambito aggiuntive ai fini della focalizzazione.

Vedi https://stackoverflow.com/a/29963695/937997

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.