I am trying to create a directive that would create an input field with the same ng-model as the element that creates the directive.
Here's what I came up with so far:
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);
}
};
});
However, I am not confident this is the right way to handle this scenario, and there is a bug that my control is not getting initialized with the value of the ng-model target field.
Here's a Plunker of the code above: http://plnkr.co/edit/IvrDbJ
What's the correct way of handling this?
EDIT: After removing the ng-model="value"
from the template, this seems to be working fine. However, I will keep this question open because I want to double check this is the right way of doing this.
scope
and set it to scope: false
? How to bind to ng-model
in that case?
EDIT: This answer is old and likely out of date. Just a heads up so it doesn't lead folks astray. I no longer use Angular so I'm not in a good position to make improvements.
It's actually pretty good logic but you can simplify things a bit.
Directive
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 with directive
<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;
}
You can see it in action with this Plunker.
Here's what I see:
I understand why you want to use 'ng-model' but in your case it's not necessary. ng-model is to link existing html elements with a value in the scope. Since you're creating a directive yourself you're creating a 'new' html element, so you don't need ng-model.
EDIT As mentioned by Mark in his comment, there's no reason that you can't use ng-model, just to keep with convention.
By explicitly creating a scope in your directive (an 'isolated' scope), the directive's scope cannot access the 'name' variable on the parent scope (which is why, I think, you wanted to use ng-model).
I removed ngModel from your directive and replaced it with a custom name that you can change to whatever.
The thing that makes it all still work is that '=' sign in the scope. Checkout the docs docs under the 'scope' header.
In general, your directives should use the isolated scope (which you did correctly) and use the '=' type scope if you want a value in your directive to always map to a value in the parent scope.
I took a combo of all answers, and now have two ways of doing this with the ng-model attribute:
With a new scope which copies ngModel
With the same scope which does a compile on link
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: '
', replace: true }; }); app.directive('myDirectiveWithChildScope', function($compile) { return { restrict: 'E', scope: true, // Notice how label is visible in the scope template: '', 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: '', 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: '', replace: true }; }); app.directive('myReplacedDirectiveChild', function($compile) { return { restrict: 'E', scope: true, template: '', replace: true }; }); app.directive('myReplacedDirective', function($compile) { return { restrict: 'E', template: '', replace: true }; }); .some { border: 1px solid #cacaca; padding: 10px; }Try typing in the child scope ones, they copy the value into the child scope which breaks the link with the parent scope.
Also notice how removing jQuery makes it so only the new-isolate-scope version works.
Finally, note that the replace+isolate scope only works in AngularJS >=1.2.0
I'm not sure I like the compiling at link time. However, if you're just replacing the element with another you don't need to do that.
All in all I prefer the first one. Simply set scope to {ngModel:"="}
and set ng-model="ngModel"
where you want it in your template.
Update: I inlined the code snippet and updated it for Angular v1.2. Turns out that isolate scope is still best, especially when not using jQuery. So it boils down to:
Are you replacing a single element: Just replace it, leave the scope alone, but note that replace is deprecated for v2.0: app.directive('myReplacedDirective', function($compile) { return { restrict: 'E', template: '', replace: true }; });
Otherwise use this: app.directive('myDirectiveWithScope', function() { return { restrict: 'E', scope: { ngModel: '=', }, template: '
' }; });ng-model
in an isolated scope?
it' s not so complicated: in your dirctive, use an alias: scope:{alias:'=ngModel'}
.directive('dateselect', function () {
return {
restrict: 'E',
transclude: true,
scope:{
bindModel:'=ngModel'
},
template:'<input ng-model="bindModel"/>'
}
in your html, use as normal
<dateselect ng-model="birthday"></dateselect>
You only need ng-model when you need to access the model's $viewValue or $modelValue. See NgModelController. And in that case, you would use require: '^ngModel'
.
For the rest, see Roys answer.
^
should be there only if the ng-model is applied in a parent element
This is a little late answer, but I found this awesome post about NgModelController
, which I think is exactly what you were looking for.
TL;DR - you can use require: 'ngModel'
and then add NgModelController
to your linking function:
link: function(scope, iElement, iAttrs, ngModelCtrl) {
//TODO
}
This way, no hacks needed - you are using Angular's built-in ng-model
I wouldn't set the ngmodel via an attribute, you can specify it right in the template:
template: '<div class="some"><label>{{label}}</label><input data-ng-model="ngModel"></div>',
plunker: http://plnkr.co/edit/9vtmnw?p=preview
Since Angular 1.5 it's possible to use Components. Components are the-way-to-go and solves this problem easy.
<myComponent data-ng-model="$ctrl.result"></myComponent>
app.component("myComponent", {
templateUrl: "yourTemplate.html",
controller: YourController,
bindings: {
ngModel: "="
}
});
Inside YourController all you need to do is:
this.ngModel = "x"; //$scope.$apply("$ctrl.ngModel"); if needed
input ng-model="$ctrl.ngModel"
and it will sync with with $ctrl.result also.
Creating an isolate scope is undesirable. I would avoid using the scope attribute and do something like this. scope:true gives you a new child scope but not isolate. Then use parse to point a local scope variable to the same object the user has supplied to the ngModel attribute.
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);
}
};
}]);
Success story sharing
contenteditable
directive examples in the Angular docs -- forms page, NgModelController page -- both use ng-model. And the ngModelController page says that this controller is "meant to be extended by other directives."hg-model
(and not the issue of coupling, IMO). This way the data context always uses ng-model whether it is a<input>
or a custom directive, thus simplifying cognitive overhead for the HTML writer. I.e. it saves the HTML writer having to find out what the the name formy-directive-var
is for each directive, especially since there's no autocomplete to help you.ng-model-options
or any of the other ng model things, does it?