AngularJS - Crea una direttiva che utilizza ng-model


294

Sto cercando di creare una direttiva che crei un campo di input con lo stesso modello ng dell'elemento che crea la direttiva.

Ecco cosa mi è venuto in mente finora:

HTML

<!doctype html>
<html ng-app="plunker" >
<head>
  <meta charset="utf-8">
  <title>AngularJS Plunker</title>
  <link rel="stylesheet" href="style.css">
  <script>document.write("<base href=\"" + document.location + "\" />");</script>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.js"></script>
  <script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
  This scope value <input ng-model="name">
  <my-directive ng-model="name"></my-directive>
</body>
</html>

JavaScript

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.name = "Felipe";
});

app.directive('myDirective', function($compile) {
  return {
    restrict: 'E',
    scope: {
      ngModel: '='
    },
    template: '<div class="some"><label for="{{id}}">{{label}}</label>' +
      '<input id="{{id}}" ng-model="value"></div>',
    replace: true,
    require: 'ngModel',
    link: function($scope, elem, attr, ctrl) {
      $scope.label = attr.ngModel;
      $scope.id = attr.ngModel;
      console.debug(attr.ngModel);
      console.debug($scope.$parent.$eval(attr.ngModel));
      var textField = $('input', elem).
        attr('ng-model', attr.ngModel).
        val($scope.$parent.$eval(attr.ngModel));

      $compile(textField)($scope.$parent);
    }
  };
});

Tuttavia, non sono sicuro che questo sia il modo giusto di gestire questo scenario e c'è un bug che il mio controllo non viene inizializzato con il valore del campo target ng-model.

Ecco un Plunker del codice sopra: http://plnkr.co/edit/IvrDbJ

Qual è il modo corretto di gestirlo?

EDIT : dopo aver rimosso il ng-model="value"dal modello, questo sembra funzionare bene. Tuttavia, terrò aperta questa domanda perché voglio ricontrollare che questo è il modo giusto di farlo.


1
Cosa succede se lo rimuovi scopee lo imposti scope: false? Come legarsi ng-modelin quel caso?
Saeed Neamati,

Risposte:


210

EDIT : questa risposta è vecchia e probabilmente obsoleta. Solo un avvertimento in modo che non porti fuori strada la gente. Non utilizzo più Angular, quindi non sono in una buona posizione per apportare miglioramenti.


In realtà è una logica piuttosto buona, ma puoi semplificare un po 'le cose.

Direttiva

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.model = { name: 'World' };
  $scope.name = "Felipe";
});

app.directive('myDirective', function($compile) {
  return {
    restrict: 'AE', //attribute or element
    scope: {
      myDirectiveVar: '=',
     //bindAttr: '='
    },
    template: '<div class="some">' +
      '<input ng-model="myDirectiveVar"></div>',
    replace: true,
    //require: 'ngModel',
    link: function($scope, elem, attr, ctrl) {
      console.debug($scope);
      //var textField = $('input', elem).attr('ng-model', 'myDirectiveVar');
      // $compile(textField)($scope.$parent);
    }
  };
});

HTML con direttiva

<body ng-controller="MainCtrl">
  This scope value <input ng-model="name">
  <my-directive my-directive-var="name"></my-directive>
</body>

CSS

.some {
  border: 1px solid #cacaca;
  padding: 10px;
}

Puoi vederlo in azione con questo Plunker .

Ecco cosa vedo:

  • Capisco perché vuoi usare 'ng-model' ma nel tuo caso non è necessario. ng-model è quello di collegare elementi html esistenti con un valore nell'ambito. Dato che stai creando una direttiva tu stesso stai creando un 'nuovo' elemento html, quindi non hai bisogno di ng-model.

MODIFICA Come menzionato da Mark nel suo commento, non vi è alcun motivo per cui non è possibile utilizzare ng-model, solo per restare d'accordo.

  • Creando esplicitamente un ambito nella direttiva (un ambito "isolato"), l'ambito della direttiva non può accedere alla variabile "nome" sull'ambito padre (motivo per cui, a mio avviso, volevi usare ng-model).
  • Ho rimosso ngModel dalla tua direttiva e l'ho sostituito con un nome personalizzato che puoi modificare in qualunque modo.
  • La cosa che fa ancora funzionare tutto è che '=' firma nell'ambito. Controlla i documenti della documentazione sotto l'intestazione "scope".

In generale, le vostre direttive dovrebbero usare l'ambito isolato (cosa che avete fatto correttamente) e usare l'ambito di tipo '=' se volete che un valore nella vostra direttiva sia sempre associato a un valore nell'ambito genitore.


18
+1, ma non sono sicuro di essere d'accordo con l'affermazione "ng-model è quello di collegare elementi HTML esistenti con un valore nell'ambito". I due contenteditableesempi di direttiva nella documentazione angolare - pagina dei moduli , pagina NgModelController - usano entrambi ng-model. E la pagina ngModelController dice che questo controller è "destinato ad essere esteso da altre direttive".
Mark Rajcok,

33
Non sono sicuro del motivo per cui questa risposta è valutata così bene perché non soddisfa ciò che è stata posta la domanda originale, ovvero utilizzare ngModel. Sì, si può evitare di utilizzare ngModel inserendo lo stato nel controller principale, ma ciò comporta il costo di avere due controller strettamente legati e di non poterli utilizzare / riutilizzare in modo indipendente. È come usare una variabile globale invece di impostare un listener tra due componenti: potrebbe essere tecnicamente più semplice ma nella maggior parte dei casi non è una buona soluzione.
Pat Niemeyer,

Aggiungo che se volesse fare affidamento sul controllore padre dovrebbe iniettarlo comunque con "request: ^ parent" - in modo da poter rendere esplicita e facoltativa la dipendenza se lo desidera.
Pat Niemeyer,

3
@Jeroen A mio avviso, il vantaggio principale è la coerenza con altri luoghi in cui il modello viene passato come hg-model(e non il problema dell'accoppiamento, IMO). In questo modo il contesto dei dati utilizza sempre ng-model sia che si tratti di una <input>o una direttiva personalizzata, semplificando così l'overhead cognitivo per il writer HTML. Cioè salva lo scrittore HTML che deve scoprire qual è il nome my-directive-varper ogni direttiva, soprattutto perché non c'è il completamento automatico per aiutarti.
zai chang,

2
umm ... ok ... ma ora questo non funziona più con ng-model-optionsnessuna delle altre cose del modello, vero?
George Mauer,

68

Ho preso una combinazione di tutte le risposte e ora ho due modi per farlo con l'attributo ng-model:

  • Con un nuovo ambito che copia ngModel
  • Con lo stesso ambito che esegue una compilazione sul collegamento

Non sono sicuro che mi piaccia la compilazione al momento del collegamento. Tuttavia, se stai semplicemente sostituendo l'elemento con un altro, non è necessario farlo.

Tutto sommato preferisco il primo. Basta impostare l'ambito su {ngModel:"="}e impostare ng-model="ngModel"dove lo si desidera nel modello.

Aggiornamento : ho incorporato lo snippet di codice e l'ho aggiornato per Angular v1.2. Si scopre che l'ambito dell'isolato è ancora il migliore, specialmente quando non si utilizza jQuery. Quindi si riduce a:

  • Stai sostituendo un singolo elemento: sostituiscilo, lascia solo l'ambito, ma nota che sostituisci è deprecato per v2.0:

    app.directive('myReplacedDirective', function($compile) {
      return {
        restrict: 'E',
        template: '<input class="some">',
        replace: true
      };
    });
  • Altrimenti usa questo:

    app.directive('myDirectiveWithScope', function() {
      return {
        restrict: 'E',
        scope: {
          ngModel: '=',
        },
        template: '<div class="some"><input ng-model="ngModel"></div>'
      };
    });

1
Ho aggiornato il plunker con tutte e tre le possibilità di ambito e per gli elementi figlio del modello o l'elemento radice del modello.
wt

1
Questo è fantastico, ma come essenzialmente lo rendi opzionale? Sto creando una direttiva textbox per una libreria UI e voglio che il modello sia facoltativo, il che significa che la casella di testo continuerà a funzionare se ngModel non è impostato.
Nick Radford,

1
@NickRadford Basta controllare se ngModel è definito nell'ambito $ scope e, in caso contrario, non utilizzarlo?
wt

1
Ci saranno problemi o costi aggiuntivi aggiuntivi con il riutilizzo ng-modelin un ambito isolato?
Jeff Ling,

2
@jeffling non ne sono sicuro ma io non la penso così. La copia di ngModel è piuttosto leggera e l'oscilloscopio isolato limita l'esposizione.
wt

52

non è così complicato: nella tua dirctive, usa un alias: scope:{alias:'=ngModel'}

.directive('dateselect', function () {
return {
    restrict: 'E',
    transclude: true,
    scope:{
        bindModel:'=ngModel'
    },
    template:'<input ng-model="bindModel"/>'
}

nel tuo HTML, usa normalmente

<dateselect ng-model="birthday"></dateselect>

1
Questo è molto più semplice quando si tratta di librerie come l'interfaccia utente di Kendo. Grazie!
bytebender

30

È necessario ng-model solo quando è necessario accedere a $ viewValue o $ modelValue del modello. Vedi NgModelController . E in quel caso, useresti require: '^ngModel'.

Per il resto, vedi la risposta di Roys .


2
ng-model è utile anche se non hai bisogno di $ viewValue o $ modelValue. È utile anche se vuoi solo le funzionalità di associazione dei dati di ng-model, come nell'esempio di @ kolrie.
Mark Rajcok,

1
E ^dovrebbe esserci solo se il modello ng è applicato in un elemento genitore
georgiosd

18

Questo è un po 'in ritardo risposta, ma ho trovato questo fantastico posto circa NgModelController, che credo sia esattamente quello che stavi cercando.

TL; DR : è possibile utilizzare require: 'ngModel'e quindi aggiungere NgModelControlleralla funzione di collegamento:

link: function(scope, iElement, iAttrs, ngModelCtrl) {
  //TODO
}

In questo modo, non sono necessari hack: stai utilizzando il sistema integrato di Angular ng-model



0

Da Angular 1.5 è possibile utilizzare i componenti. I componenti sono la strada da percorrere e risolvono facilmente questo problema.

<myComponent data-ng-model="$ctrl.result"></myComponent>

app.component("myComponent", {
    templateUrl: "yourTemplate.html",
    controller: YourController,
    bindings: {
        ngModel: "="
    }
});

All'interno di YourController tutto ciò che devi fare è:

this.ngModel = "x"; //$scope.$apply("$ctrl.ngModel"); if needed

Quello che ho scoperto è che funziona se in effetti usi "=" anziché "<", che è altrimenti la migliore pratica usando i componenti. Non sono sicuro di cosa significhi la parte "dentro YourController" di questa risposta, il punto di ciò non è impostare ngModel all'interno del componente?
Marc Stober,

1
@MarcStober Con "Inside YourController" volevo solo dimostrare che ngModel è disponibile come getter e setter. In questo esempio $ ctrl.result diventerà "x".
Niels Steenbeek,

Ok. Penso che l'altra parte che è importante sia che anche tu, nel tuo modello di controller, input ng-model="$ctrl.ngModel"possa fare e si sincronizzerà con $ ctrl.result.
Marc Stober,

0

La creazione di un ambito di isolamento non è auspicabile. Eviterei di usare l'attributo scope e fare qualcosa del genere. scope: true ti dà un nuovo ambito figlio ma non isolato. Quindi utilizzare l'analisi per puntare una variabile di ambito locale sullo stesso oggetto che l'utente ha fornito all'attributo ngModel.

app.directive('myDir', ['$parse', function ($parse) {
    return {
        restrict: 'EA',
        scope: true,
        link: function (scope, elem, attrs) {
            if(!attrs.ngModel) {return;}
            var model = $parse(attrs.ngModel);
            scope.model = model(scope);
        }
    };
}]);
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.