I have read some posts here on so regarding this subject but all the answers and all of the fiddles I saw not one working properly. I got my plunkr to bind one-way from the dynamically generated field bound through ng-model inside an directive isolated scope to the form in the parent. However is does not bind from the form back to the directive. My initial values are disregarded, so are the changes I make inside the parent scope and I would really like a solution. Here's a code fragment.
<div ng-repeat="field in fields">
<app-field field="field" ng-model="selected[field.name]" form="form"></app-field>
</div>
...
.directive("appField", function($http, $compile) {
return {
restrict: "AE",
scope: {
field: "=",
ngModel: "=",
form: "="
},
link: function($scope, element, attrs) {
$http.get("field.html").then(function (result) {
element.html(result.data);
$compile(element.contents())($scope);
});
}
}
})
Here's my fiddle: http://plnkr.co/edit/ZzC4jS9M9Ev5i6gxUVxB?p=preview
Any help would be appreciated.
The property is scope, not $scope. Then, remove the scope:false and you are done!
Also, you can use the templateUrl property instead of link (in this case):
.directive("appField", function($http, $compile) {
return {
restrict: "AE",
scope: {
field: "=",
ngModel: "=",
form: "="
},
templateUrl: 'field.html'
}
})
Related
I have a tab-content component which gets a dynamic url from the controller
<tab-content url="{{tabContentUrl}}"></tab-content>
Directive of the component:
myApp.directive("tabContent", () => {
return {
restrict: "E",
scope: true,
link: (scope, element, attrs) => {
scope.getContentUrl = () => {
return attrs.url || "views/new-week-stats.html";
}
},
template: "<div ng-include='getContentUrl()'></div>"
}
});
At this point, it can only inject the initial values of the $scope in the controller. When something is changed in the scope it is not refreshed in the DOM. How can I enable two way binding in the directive?
ngInclude directive already does what you want for you, just use the directive scope 2 way bindings.
myApp.directive("tabContent", () => {
return {
restrict: "E",
scope: {
url: '='
},
template: '<div ng-include="url || 'views/new-week-stats.html'"></div>'
}
});
That way, any change in the url on the tabContent element will trigger a change in the ngInclude.
Quick Answer
You have two options.
Same scope as parent with two way binding (scope:false):
HTML:
<tab-content ng-model="tabContentUrl"></tab-content>
JS:
myApp.controller("MainCtrl", function($scope){
$scope.default = "views/new-week-stats.html";
$scope.tabContentUrl = "views/example.html";
});
myApp.directive("tabContent", () => {
return {
restrict: "E",
scope: false,
template: "<div ng-include='tabContentUrl || default'></div>"
}
});
Isolated scope with two way binding (scope:{url: '='}):
HTML:
<div ng-controller="MainCtrl">
<tab-content url="tabContentUrl" default="views/new-week-stats.html">
</tab-content>
</div>
JS:
myApp.controller("MainCtrl", function($scope){
$scope.tabContentUrl = "views/example.html"
});
myApp.directive("tabContent", () => {
return {
restrict: "E",
scope: {
url: '=',
default: '#'
},
template: "<div ng-include='url || default'></div>"
}
});
Explanation:
All directives have a scope associated with them. They use this scope for accessing data/methods inside the template and link function. By default, unless explicitly set, directives don’t create their own scope. Therefore, directives use their parent scope (usually a controller) as their own.
In your code on line 4 you specified scope: true,
When a directive scope is set to “true”, AngularJS will create a new scope object and assign to the directive. This newly created scope object is prototypically inherited from its parent scope (the controller scope where it’s been used).
In total there are three options:
Same scope as parent with two way binding.
scope: false,
Isolated scope but a one way binding.
scope: true,
Isolated scope with options.
scope: {
"#" // Creates a copy as text
"=" // Direct model with two-way binding
"&" // For passing functions
}
add a watcher in your directive:
HTML
<tab-content url="tabContentUrl"></tab-content>
JS
myApp.directive( "tabContent", () => {
return {
restrict: "E",
scope : true,
link : ( scope, element, attrs ) => {
scope.$watch( () => scope.$eval( attrs.url ), newValue => {
scope.url = newValue;
} );
},
template: "<div ng-include='url'></div>"
};
} );
You can also use $parse instead of $eval, but you don't got an isolated scope, so it should be ok, as long as you don't overwrite the parameters in your directive
You can use an observer to dynamically change the scope value inside your directive.
link: function(scope, element, attrs){
attrs.$observe('tabContentUrl', function(val) {
scope.tabContentUrl = val;
})
}
I have a directive defined as such:
angular.module("main.directives").directive("todo", function() {
return {
restrict: "A",
scope: {
todo: "=entity"
},
replace: false,
templateUrl: "todo.html",
link: function(scope, element, attributes) {
}
};
});
which I use like this from templates:
<div todo entity="todoData"></div>
todoData comes from a controller or some other the local scope. Anyway it all works like a charm, so that's cool!
My question is the following: How do I have to modify the directive definition so that it also works with a markup of this type:
<div todo="todoData"></div>
As you can see the data is now passed in as the value of the attribute marking the directive. Just like ng- directives do:
<p ng-repeat="bit in data"></p>
<p ng-click="whatever()"></p>
How can that be achieved?
Thanks
Replace
scope: {
todo: "=entity"
},
by
scope: {
todo: "=todo"
},
or simply
scope: {
todo: "="
},
When you write an attribute directive in angularjs you might want to have it fed by an attribute value.
For example, something like this:
<div my-attribute="somevalue"></div>
How then do you create a new scope that takes that in? It's not obvious. Any here's how you do it:
app.directive('myAttribute', function() {
return {
restrict: 'A',
scope: {
myAttribute: '='
},
template: '<div style="font-weight:bold">{{ myAttribute | number:2}}</div>'
};
});
The trick to notice is that the "self attribute" because of the name of the attribute in camel case.
Here is the Reference to This Answer!
you must eval the value of the attribute inself. The isolate scope is not one of my favorites kind of scopes for a directive. Instead you can use, scope = true, to inherit from the parent controller. This will allow you to use all the variable exposes on the parents scopes.
in your case.
angular.module("main.directives").directive("todo", function() {
return {
restrict: "A",
scope: true,
replace: false,
templateUrl: "todo.html",
link: function(scope, element, attributes) {
scope.todo = scope.$eval(attributes[todo]);
}
};
});
now your todo directive could be used. Like any other ng- directive.
example:
<div todo="getTodoList()"></div>
<div todo="[{description:'hahahha'}]"></div>
I'm using a directive with an ng-repeat
<page ng-repeat='page in pages' id='{{page.id}}' title='{{page.title}}'></page>
And my directive :
app.directive('page', function(){
return {
restrict: 'EA',
scope: {
id: '#',
title: '#'
},
templateUrl: 'views/tpl/page.html',
controller: 'PageController'
});
Is there any way to avoid having to pass in manually all the attributes into the directive's isolate scope ? My goal would be to populate the directive's scope automatically with every attribute in the "page" object.
Thanks !
You can pass the whole page object. Directives Guid
html:
<page ng-repeat='page in pages' page-object='page'></page>
js:
app.directive('page', function(){
return {
restrict: 'EA',
scope: {
pageObject: '='
},
templateUrl: 'views/tpl/page.html',
controller: 'PageController'
});
My dear friends,
We can bind a scope property of a directive to the value of DOM attribute.
This works:
module.directive 'MyDirective', ->
scope:
directiveVar: '='
...
<div class='MyDirective' directive-var='parentVar'></div>
In example above we bind directive's directiveVar property to parentVar property of the parent scope.
This is a bi-directional binding, so if directiveVar is changed parentVar is automatically updated, and vice versa.
My question is:
Is there a way I can bind a deep child property of my directive's scope instead? Like scope.lv1.directiveVar or scope.lv1.lv2.lv3.directiveVar instead of scope.directiveVar?
Docs I read
What I want to achieve
I have an object in directive scope named lv1. I want to bind its property directiveVar to parent property.
This does not work:
scope:
lv1:
directiveVar: '='
And this does not work:
scope:
"lv1.directiveVar": '=myVar'
Demo
This is what works: http://plnkr.co/edit/OClnZ2Cl3BXr60PC2qVP?p=preview
This is what I want to achieve: http://plnkr.co/edit/tQEHeKOzGjGyplCwUtU2?p=preview
I hope this code will help. You can pass in an object and watch its properties or you can nest things in parent/child directives. Either way adding the "=" will enable two way binding on the entire object.
Controller:
$scope.directiveVar = {
level1: {
greeting: 'Hello'
}
};
$scope.otherVar = {
levelA: {
foo: 'bar'
}
};
Markup:
<div data-my-parent-directive data-other-var="otherVar">
<div data-my-directive data-directive-var="directiveVar"></div>
</div>
Directive:
angular.module('MyDirective', [])
.directive('myParentDirective', ['$window',
function ($window) {
return{
restrict: 'AE',
scope:{
otherVar: '='
},
controller: function($scope, $element) {
var othis = this;
this.element = $element;
this.otherVar = $scope.otherVar;
}
};
}
])
.directive('myDirective', ['$window',
function ($window) {
return {
restrict: 'AE',
require: '?myParentDirective',
scope: {
directiveVar: '='
},
link: function(scope, elm, attr, myParentDirectiveCtrl) {
console.log(myParentDirectiveCtrl.otherVar);
console.log(myDirectiveParentCtrl.otherVar.levelA);
scope.$watch('directiveVar.level1', function (newValue, oldValue){
console.log(newValue, oldValue);
});
}
};
}
])
Edit:
You could simply do this:
<div data-my-parent-directive data-other-var="otherVar">
<div data-my-directive data-directive-var="directiveVar.level1"></div>
</div>
Which is useful when modeling data. For example:
var items = apiService.getItems();
var layers = [...],
$scope.map = {
points: items,
layers: layers,
highAltMap: 'Streets',
lowAltMap: 'Satellite'
};
That said if you are modelling data see this ng-conf video which I believe the speaker touched on some OO patterns.
Edit 2:
You could use a service like this plunker
I know date of the question, but...
Create json object in attribute
return {
scope: {
date: "=",
formChange: "=?",
ngDisabled: "=?",
deepInclude: "=?"
},
...
<user-date-without-current-date date="cv.dateBirthday" deep-include="{cnt:cv.dateBirthday}" form-change="$parent.form_change"></user-date-without-current-date>
Is it usefull?
I’ve create a custom directive that contains a select input field.
I’m using ng-options to populate the select options and I'm currently passing in the data for the options using an options attribute bound to an isolate scope. See below.
<script>
recManagerApp.directive(myDirective, function () {
return {
restrict: 'E',
templateUrl: '/templates/directives/mydirective.html',
scope: {
mySelectedValue: "=",
options : "="
}
};
});
</script>
<my-directive my-selected-value="usersValue" options="myDataService.availbleOptions"></my-directive>
<div>
<select data-ng-model="mySelectedValue" data-ng-options="item for item in options">
<option value="">Select something</option>
</select>
</div>
The above works as expected, correctly populates the options, selects the correct value and has two-way binding to the property in the parent scope.
However, I’d rather not pass in the options using an attribute on the my-directive element and instead inject in a service (myDataService) that can provide the data for the ng-options. However, when I try this (various ways) no options are created, despite the service being injected correctly and the data being available. Can anyone suggest a way to do this?
recManagerApp.directive(myDirective, function (myDataService) {
return {
restrict: 'E',
templateUrl: '/templates/directives/mydirective.html',
scope: {
mySelectedValue: "=",
options : myDataService.availableOptions
}
};
});
Thanks
Mat
In my opinion, you have several options (as pointed out in the comments):
1. create controller for the directive
In you directive's template, use a controller, i.e.
<div ng-controller="SelectController">
<!-- your select with the ngOptions -->
</div>
and create the SelectController as a regular controller:
var app = angular.module("app.controllers", [])
app.controller("SelectController", ['$scope', 'myDataService', function(scope, service) {
scope.options = service.whatEverYourServiceDoesToProvideThis()
}]);
You can also give your directive a controller, which works just the same:
recManagerApp.directive(myDirective, function () {
return {
restrict: 'E',
templateUrl: '/templates/directives/mydirective.html',
scope: {
mySelectedValue: "=",
},
controller: ['$scope', 'myDataService', function(scope, service) {
scope.options = service.whatEverYourServiceDoesToProvideThis()
}]
};
});
2. injecting it into the directive and using it within link
recManagerApp.directive(myDirective, function (myDataService) {
return {
restrict: 'E',
templateUrl: '/templates/directives/mydirective.html',
scope: {
mySelectedValue: "="
},
link: function(scope) {
scope.options = myDataService.whatEverYourServiceDoesToProvideThis()
}
};
});