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.