J'essaie de créer une directive qui créerait un champ d'entrée avec le même modèle-ng que l'élément qui crée la directive.
Voici ce que je suis arrivé jusqu'à présent:
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);
}
};
});
Cependant, je ne suis pas convaincu que ce soit la bonne façon de gérer ce scénario et il y a un problème qui dit que mon contrôle n'est pas initialisé avec la valeur du champ cible ng-model.
Voici un Plunker du code ci-dessus: http://plnkr.co/edit/IvrDbJ
Quelle est la bonne façon de gérer cela?
EDIT: après avoir supprimé le ng-model="value"
du modèle, cela semble fonctionner correctement. Cependant, je vais garder cette question ouverte parce que je veux vérifier que c’est la bonne façon de procéder.
EDIT: cette réponse est ancienne et probablement obsolète. Juste un tête-à-tête pour ne pas égarer les gens. Je n'utilise plus Angular, je ne suis donc pas bien placé pour apporter des améliorations.
C'est en fait une bonne logique mais vous pouvez simplifier un peu les choses.
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);
}
};
});
<body ng-controller="MainCtrl">
This scope value <input ng-model="name">
<my-directive my-directive-var="name"></my-directive>
</body>
.some {
border: 1px solid #cacaca;
padding: 10px;
}
Vous pouvez le voir en action avec ce Plunker .
Voici ce que je vois:
EDIT Comme mentionné par Mark dans son commentaire, il n'y a aucune raison pour que vous {ne puissiez pas utiliser ng-model, juste pour respecter les conventions.
En général, vos directives doivent utiliser la portée isolée (ce que vous avez fait correctement) et utiliser la portée de type '=' si vous souhaitez qu'une valeur de votre directive mappe toujours à une valeur de la portée parent.
J'ai pris une combinaison de toutes les réponses, et maintenant, j'ai deux façons de le faire avec l'attribut ng-model:
var app = angular.module('model', []);
app.controller('MainCtrl', function($scope) {
$scope.name = "Felipe";
$scope.label = "The Label";
});
app.directive('myDirectiveWithScope', function() {
return {
restrict: 'E',
scope: {
ngModel: '=',
},
// Notice how label isn't copied
template: '<div class="some"><label>{{label}}: <input ng-model="ngModel"></label></div>',
replace: true
};
});
app.directive('myDirectiveWithChildScope', function($compile) {
return {
restrict: 'E',
scope: true,
// Notice how label is visible in the scope
template: '<div class="some"><label>{{label}}: <input></label></div>',
replace: true,
link: function ($scope, element) {
// element will be the div which gets the ng-model on the original directive
var model = element.attr('ng-model');
$('input',element).attr('ng-model', model);
return $compile(element)($scope);
}
};
});
app.directive('myDirectiveWithoutScope', function($compile) {
return {
restrict: 'E',
template: '<div class="some"><label>{{$parent.label}}: <input></label></div>',
replace: true,
link: function ($scope, element) {
// element will be the div which gets the ng-model on the original directive
var model = element.attr('ng-model');
return $compile($('input',element).attr('ng-model', model))($scope);
}
};
});
app.directive('myReplacedDirectiveIsolate', function($compile) {
return {
restrict: 'E',
scope: {},
template: '<input class="some">',
replace: true
};
});
app.directive('myReplacedDirectiveChild', function($compile) {
return {
restrict: 'E',
scope: true,
template: '<input class="some">',
replace: true
};
});
app.directive('myReplacedDirective', function($compile) {
return {
restrict: 'E',
template: '<input class="some">',
replace: true
};
});
.some {
border: 1px solid #cacaca;
padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
<div ng-app="model" ng-controller="MainCtrl">
This scope value <input ng-model="name">, label: "{{label}}"
<ul>
<li>With new isolate scope (label from parent):
<my-directive-with-scope ng-model="name"></my-directive-with-scope>
</li>
<li>With new child scope:
<my-directive-with-child-scope ng-model="name"></my-directive-with-child-scope>
</li>
<li>Same scope:
<my-directive-without-scope ng-model="name"></my-directive-without-scope>
</li>
<li>Replaced element, isolate scope:
<my-replaced-directive-isolate ng-model="name"></my-replaced-directive-isolate>
</li>
<li>Replaced element, child scope:
<my-replaced-directive-child ng-model="name"></my-replaced-directive-child>
</li>
<li>Replaced element, same scope:
<my-replaced-directive ng-model="name"></my-replaced-directive>
</li>
</ul>
<p>Try typing in the child scope ones, they copy the value into the child scope which breaks the link with the parent scope.
<p>Also notice how removing jQuery makes it so only the new-isolate-scope version works.
<p>Finally, note that the replace+isolate scope only works in AngularJS >=1.2.0
</div>
Je ne suis pas sûr d'aimer la compilation au moment du lien. Cependant, si vous ne remplacez que l'élément par un autre, vous n'avez pas besoin de le faire.
Dans l'ensemble, je préfère le premier. Il suffit de définir la portée sur {ngModel:"="}
et de définir ng-model="ngModel"
où vous le souhaitez dans votre modèle.
Update : J'ai inséré l'extrait de code et l'ai mis à jour pour Angular v1.2. Il s'avère que la portée isolée est toujours préférable, surtout lorsque vous n'utilisez pas jQuery. Cela revient donc à:
Remplacez-vous un seul élément: remplacez-le, laissez la portée, mais notez que le remplacement est obsolète pour la v2.0:
app.directive('myReplacedDirective', function($compile) {
return {
restrict: 'E',
template: '<input class="some">',
replace: true
};
});
Sinon, utilisez ceci:
app.directive('myDirectiveWithScope', function() {
return {
restrict: 'E',
scope: {
ngModel: '=',
},
template: '<div class="some"><input ng-model="ngModel"></div>'
};
});
ce n'est pas si compliqué: dans votre dirctive, utilisez un alias: scope:{alias:'=ngModel'}
.directive('dateselect', function () {
return {
restrict: 'E',
transclude: true,
scope:{
bindModel:'=ngModel'
},
template:'<input ng-model="bindModel"/>'
}
dans votre html, utilisez comme d'habitude
<dateselect ng-model="birthday"></dateselect>
Vous n'avez besoin de ng-model que pour accéder aux $ viewValue ou $ modelValue du modèle. Voir NgModelController . Et dans ce cas, vous utiliseriez require: '^ngModel'
.
Pour le reste, voir Roys répond .
C’est une réponse un peu tardive, mais j’ai trouvé cet impressionnant post à propos de NgModelController
, que je pense être exactement ce que vous cherchiez.
TL; DR - vous pouvez utiliser require: 'ngModel'
puis ajouter NgModelController
à votre fonction de liaison:
link: function(scope, iElement, iAttrs, ngModelCtrl) {
//TODO
}
De cette façon, aucun piratage n'est nécessaire - vous utilisez le ng-model
intégré à Angular
Je ne définirais pas le ngmodel via un attribut, vous pouvez le spécifier directement dans le modèle:
template: '<div class="some"><label>{{label}}</label><input data-ng-model="ngModel"></div>',
Créer une portée isolée n'est pas souhaitable. Je voudrais éviter d'utiliser l'attribut scope et faire quelque chose comme ça. scope: true vous donne une nouvelle portée enfant mais pas isoler. Utilisez ensuite analyse pour faire pointer une variable de portée locale sur le même objet que l'utilisateur a fourni à l'attribut 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);
}
};
}]);
Depuis Angular 1.5, il est possible d'utiliser des composants. Les composants sont la voie à suivre et résolvent ce problème facilement.
<myComponent data-ng-model="$ctrl.result"></myComponent>
app.component("myComponent", {
templateUrl: "yourTemplate.html",
controller: YourController,
bindings: {
ngModel: "="
}
});
Dans YourController, tout ce que vous devez faire est de:
this.ngModel = "x"; //$scope.$apply("$ctrl.ngModel"); if needed