AngularJS - Utwórz dyrektywę, która używa modelu ng


294

Próbuję utworzyć dyrektywę, która utworzyłaby pole wejściowe z tym samym modelem ng co element, który tworzy dyrektywę.

Oto, co do tej pory wymyśliłem:

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);
    }
  };
});

Nie jestem jednak pewien, czy jest to właściwy sposób na poradzenie sobie z tym scenariuszem, i jest błąd, że moja kontrola nie jest inicjalizowana wartością pola docelowego modelu ng.

Oto Plunker powyższego kodu: http://plnkr.co/edit/IvrDbJ

Jaki jest właściwy sposób radzenia sobie z tym?

EDYCJA : po usunięciu ng-model="value"z szablonu wydaje się, że działa dobrze. Pozostawię to pytanie otwarte, ponieważ chcę dokładnie sprawdzić, czy jest to właściwy sposób na zrobienie tego.


1
Co jeśli usuniesz scopei ustawisz na scope: false? Jak wiązać się ng-modelw takim przypadku?
Saeed Neamati,

Odpowiedzi:


210

EDYCJA : Ta odpowiedź jest stara i prawdopodobnie nieaktualna. Tylko jedna głowa do góry, żeby nie zmylić ludzi. Nie używam już Angulara, więc nie jestem w dobrej sytuacji, aby dokonywać ulepszeń.


To jest całkiem niezła logika, ale można trochę uprościć.

Dyrektywa

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 z dyrektywą

<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;
}

Możesz zobaczyć to w akcji dzięki temu Plunkerowi .

Oto co widzę:

  • Rozumiem, dlaczego chcesz użyć „modelu ng”, ale w twoim przypadku nie jest to konieczne. ng-model ma połączyć istniejące elementy HTML z wartością w zakresie. Ponieważ sam tworzysz dyrektywę, tworzysz „nowy” element HTML, więc nie potrzebujesz modelu ng.

EDYCJA Jak wspomniał Mark w swoim komentarzu, nie ma powodu, dla którego nie można używać modelu ng, aby zachować konwencję.

  • Poprzez wyraźne utworzenie zakresu w twojej dyrektywie (zakres „izolowany”), zakres dyrektywy nie może uzyskać dostępu do zmiennej „name” w zakresie nadrzędnym (dlatego myślę, że chciałeś użyć modelu ng).
  • Usunąłem ngModel z twojej dyrektywy i zastąpiłem go niestandardową nazwą, którą możesz zmienić na cokolwiek.
  • Rzeczą, która sprawia, że ​​wszystko wciąż działa, jest znak „=” w zakresie. Sprawdź dokumenty w nagłówku „scope”.

Ogólnie rzecz biorąc, twoje dyrektywy powinny używać izolowanego zakresu (co zrobiłeś poprawnie) i używać zakresu typu „=”, jeśli chcesz, aby wartość w twojej dyrektywie zawsze była mapowana na wartość w zakresie nadrzędnym.


18
+1, ale nie jestem pewien, czy zgadzam się ze stwierdzeniem „ng-model polega na połączeniu istniejących elementów HTML z wartością w zakresie”. Dwa contenteditabledyrektywa przykłady w kątowym Dokumenty - stronę formy , strona NgModelController - zarówno stosowanie ng modelu. A strona ngModelController mówi, że ten kontroler „ma być rozszerzony przez inne dyrektywy”.
Mark Rajcok

33
Nie jestem pewien, dlaczego ta odpowiedź jest tak wysoko oceniana, ponieważ nie spełnia tego, co postawiono w pierwotnym pytaniu - jakim jest użycie ngModel. Tak, można uniknąć używania ngModel poprzez umieszczenie stanu w kontrolerze nadrzędnym, ale dzieje się to kosztem ścisłego powiązania dwóch kontrolerów i niemożności ich niezależnego użycia / ponownego użycia. To jak użycie zmiennej globalnej zamiast konfigurowania detektora między dwoma komponentami - technicznie może być prostsze, ale w większości przypadków nie jest dobrym rozwiązaniem.
Pat Niemeyer,

Dodałbym, że jeśli chciałby polegać na kontrolerze nadrzędnym, powinien i tak wprowadzić „wymagaj: ^ rodzica” - aby w razie potrzeby mógł jawnie określić zależność i opcjonalnie.
Pat Niemeyer,

3
@Jeroen Moim zdaniem główną zaletą jest spójność z innymi miejscami, w których model jest przekazywany jako hg-model(a nie kwestia sprzężenia, IMO). W ten sposób kontekst danych zawsze korzysta z modelu ng, niezależnie od tego, czy jest to <input>dyrektywa niestandardowa, czy też niestandardowa, co upraszcza narzut związany z pisaniem HTML. To znaczy, że oszczędza to pisarzowi HTML, który musi się nazywać my-directive-vardla każdej dyrektywy, zwłaszcza że nie ma autouzupełniania, które by ci pomogło.
zai chang

2
umm ... ok ... ale teraz to już nie działa z ng-model-optionsżadnymi innymi modelami, prawda?
George Mauer,

68

Wziąłem kombinację wszystkich odpowiedzi i teraz mam dwa sposoby na zrobienie tego z atrybutem ng-model:

  • Z nowym zakresem, który kopiuje ngModel
  • Z tym samym zakresem, który wykonuje kompilację na łączu

Nie jestem pewien, czy podoba mi się kompilacja w czasie linku. Jeśli jednak zamieniasz element na inny, nie musisz tego robić.

W sumie wolę ten pierwszy. Po prostu ustaw zakres {ngModel:"="}i ustaw ng-model="ngModel"go w swoim szablonie.

Aktualizacja : wstawiłem fragment kodu i zaktualizowałem go dla Angulara 1.2. Okazuje się, że zakres izolowania jest nadal najlepszy, szczególnie gdy nie używa się jQuery. Sprowadza się to do:

  • Czy zastępujesz pojedynczy element: Po prostu zamień, pozostaw zakres w spokoju, ale pamiętaj, że zastąpienie jest przestarzałe w wersji 2.0:

    app.directive('myReplacedDirective', function($compile) {
      return {
        restrict: 'E',
        template: '<input class="some">',
        replace: true
      };
    });
  • W przeciwnym razie użyj tego:

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

1
Zaktualizowałem plunker ze wszystkimi trzema możliwościami zakresu i dla elementów potomnych szablonu lub elementu głównego szablonu.
w00t

1
To świetnie, ale jak zasadniczo uczynić to opcjonalnym? Tworzę dyrektywę pola tekstowego dla biblioteki interfejsu użytkownika i chcę, aby model był opcjonalny, co oznacza, że ​​pole tekstowe nadal będzie działać, jeśli ngModel nie jest ustawiony.
Nick Radford

1
@NickRadford Po prostu sprawdź, czy ngModel jest zdefiniowany w zakresie $, a jeśli nie, nie używaj go?
w00t,

1
Czy pojawią się jakieś problemy lub dodatkowe koszty związane z ponownym użyciem ng-modelw izolowanym zakresie?
Jeff Ling

2
@jeffling nie jestem pewien, ale nie sądzę. Kopiowanie ngModel jest dość lekkie, a izolowany zakres ogranicza ekspozycję.
w00t

52

to nie jest takie skomplikowane: w swoim kursie użyj aliasu: scope:{alias:'=ngModel'}

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

w swoim htmlu używaj normalnie

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

1
Jest to o wiele łatwiejsze w przypadku bibliotek takich jak Kendo UI. Dzięki!
bytebender

30

Potrzebujesz modelu ng tylko wtedy, gdy potrzebujesz dostępu do modelu $ viewValue lub $ modelValue. Zobacz NgModelController . I w takim przypadku byś użył require: '^ngModel'.

Resztę znajdziesz w odpowiedzi Roysa .


2
ng-model jest również przydatny, nawet jeśli nie potrzebujesz $ viewValue lub $ modelValue. Jest to przydatne, nawet jeśli chcesz tylko funkcji wiązania danych w modelu ng, takich jak przykład @ kolrie.
Mark Rajcok

1
I ^powinno tam być tylko wtedy, gdy model ng jest zastosowany w elemencie nadrzędnym
georgiosd

18

To trochę późna odpowiedź, ale znalazłem ten niesamowity post, o NgModelControllerktórym myślę, że właśnie tego szukałeś.

TL; DR - możesz użyć, require: 'ngModel'a następnie dodać NgModelControllerdo swojej funkcji łączenia:

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

W ten sposób nie potrzebujesz żadnych hacków - korzystasz z wbudowanej aplikacji Angular ng-model



0

Od wersji Angular 1.5 można używać Komponentów. Komponenty są dostępne i łatwo rozwiązują ten problem.

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

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

Wewnątrz kontrolera wszystko, co musisz zrobić, to:

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

Odkryłem, że działa, jeśli rzeczywiście używasz „=” zamiast „<”, co w innym przypadku jest najlepszą praktyką przy użyciu Komponentów. Nie jestem pewien, co oznacza część „inside YourController” tej odpowiedzi, czy nie chodzi o to, aby ustawić ngModel wewnątrz komponentu?
Marc Stober,

1
@MarcStober Dzięki „inside YourController” chciałem tylko pokazać, że ngModel jest dostępny jako getter i setter. W tym przykładzie $ ctrl.result zmieni się na „x”.
Niels Steenbeek

Ok. Myślę, że drugą ważną rzeczą jest to, że możesz także zrobić w szablonie kontrolera, input ng-model="$ctrl.ngModel"który zsynchronizuje się również z $ ctrl.result.
Marc Stober,

0

Tworzenie zakresu izolowanego jest niepożądane. Unikałbym używania atrybutu scope i robiłbym coś takiego. scope: true daje nowy zakres potomny, ale nie izoluje. Następnie użyj parsowania, aby wskazać lokalną zmienną zakresu na ten sam obiekt, który użytkownik dostarczył do atrybutu 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);
        }
    };
}]);
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.