I am having trouble getting this directive to work properly, It was working fine until I tried adding a scope to link a function. Here's what I have so far
The directive (in requirejs format hence the module/component name stuff) :
angular.module(metadata.moduleName, [
filterFieldControl.moduleName
]).directive(metadata.componentName,
function(scope) {
return {
controller: filterFieldControl.componentName,
restrict: 'C',
replace: true,
scope: {
filterFn: '='
},
template: builderResultFiltersHTML
};
}
);
The controller
angular.module(metadata.moduleName, []).controller(metadata.componentName, [
'$scope',
function($scope) {
$scope.filterChange = function() {
$scope.filterFn();
};
}
]);
The template:
<div>
<ul>{{filter.name}}
<li ng-repeat="value in filter.values">
<input type="checkbox" ng-model="filterObject[filter.name][value]" ng-change="filterChange()">{{value}}
</li>
</ul>
Where it is being used (and maybe some of the issue is it's being repeated?)
<div ng-repeat="result in searchResults.results" class="builder-search-result" filter-fn="filterClicked" ></div>
So if I take out the scope from the directive it works fine. If i add it in, there are no errors in console but nohting shows up.
If I remove this, it works :
scope: {
filterFn: '='
},
Can't seem to figure out why. Could use some ideas.
Edit: added fiddle here https://jsfiddle.net/vt1uasw7/31/ - you will notice if you remove the scope part of the directive, everything shows up fine.
When you do scope: { ... } in a directive, you're telling Angular to create an isolate scope, which is a scope that doesn't prototypally inherit from any other scope.
Now, take a look at your directive's template:
<div>in
<ul>{{filter.name}}
<li ng-repeat="value in filter.values">
<input type="checkbox" ng-change="filterChange()">{{value}}
</li>
</ul>
</div>
Both filter and filterChange() are defined in the controller's scope, and your directive can't access them because it has its own private scope.
You can fix your code by doing one of two things:
Use $parent.filter and $parent.filterChange();
Add filter and filterChange to the directive's isolate scope:
scope: {
filter: '=',
filterChange: '&'
}
I suggest #2. Here's your updated fiddle.
Related
In one of my Angular.JS controllers, I have the following:
app.controller("MyController", ["$scope", function($scope){
$scope.messages = [
new Message(1),
new Message(2)
];
$scope.addMessage = function(x) { $scope.messages.push(new Message(x)); }
}]);
Then in my main HTML page, I have
<message message="message" ng-repeat="message in messages">
This is bound to a directive:
app.directive("message", function() {
return {
restrict: "E",
scope: {
message: "="
},
templateUrl: "js/Directives/message.html"
};
});
The template file is:
<li class="message">{{message.msg}} </li>
However, when I call addMessage on the controller, while it does add to $scope.messsages, it doesn't actually refresh the ng-repeat and display the new message. How can I do this?
I would suggest some structural changes in your directive.
First of all, why not refer the original array itself instead of referring value at each iteration ??
<message messages="messages">
Then you can actually move ng-repeat part in your directive template, [You must note that since you're using = in message: "=", = binds a local/directive scope property to a parent scope property. So with =, you use the parent model/scope property name as the value of the DOM attribute. ].
Hence your directive will look like :
app.directive("message", function() {
return {
restrict: "E",
scope: {
messages: "="
},
templateUrl: "js/Directives/message.html"
};
});
and the subsequent template will look something like this :
<ul>
<li ng-repeat="message in messages" class="message">{{message.msg}} </li>
</ul>
You can find a demo plunker here
I have an angular project using angularjs 1.3.7 and cannot figure out why an object I'm passing with an attribute from a parent to a child (each with their own isolated scopes), will not pass properly. Logging to the console from the child directive will show this object as undefined whereas logging to the console from the parent will show the object as intended. Below is a simplified version of what I'm currently working with.
Main View Template:
<parent-directive>
Parent Directive:
HTML:
<div>
<div ng-repeat="foo in parentCtrl.foos">
<child-directive foo="foo" bar="parentCtrl.bar"></child-directive>
</div>
</div>
javascript:
angular
.module('parentDirectiveModule', [])
.directive('parentDirective', function() {
var parentDirectiveCtrl = ['$scope', function($scope){
var parentCtrl = this;
parentCtrl.foos = [
{'name': 'foo1', 'id': '1'},
{'name': 'foo2', 'id': '2'}
];
parentCtrl.bar = {'property': 'name'};
return {
scope: {},
templateUrl: '../parentDirective.html',
restrict: 'E',
replace: false,
controller: parentDirectiveCtrl,
controllerAs: 'parentCtrl',
bindToController: true
}
}];
Child Directive:
HTML:
<div>
<span>{{childCtrl.foo}}</span>
<button ng-click="childCtrl.editBar()">{{childCtrl.bar.property}}</button>
</div>
javascript:
angular
.module('childDirectiveModule', [])
.directive('childDirective', function() {
var childDirectiveCtrl = ['$scope', function($scope){
var childCtrl = this;
console.log(childCtrl.foo);
console.log(childCtrl.bar);
childCtrl.editBar = function() {
// update bar and reflect in the parent controller
};
return {
scope: {
foo: '=',
bar: '='
},
templateUrl: '../childDirective.html',
restrict: 'E',
replace: false,
controller: childDirectiveCtrl,
controllerAs: 'childCtrl',
bindToController: true
}
}];
Using the above code, the console log on childCtrl.foo returns the foo object as expected, but the childCtrl.bar returns as undefined.
Thanks in advance
EDIT: fixed a spelling
EDIT EDIT: closed an open " and changed bar to parentCtrl.bar on
Try changing the bar="bar" assignment to this:
<div>
<div ng-repeat="foo in parentCtrl.foos">
<child-directive foo="foo" bar="parentCtrl.bar"></child-directive>
</div>
</div>
At least with a quick glance that might be the cause for your problem, bar being genuinely undefined.
Thanks everyone for your help. So it looks like my issue is actually related to the way angular handles camel-case conversions when copying an attribute into an isolated scope. On the child-directive, I have the property bar="parentCtrl.bar". In actuality, I have a camel case name for this attribute which is something more akin to fooBar="parentCtrl.bar". In the declaration of the isolated scope, angular will pass this attibute as foobar and not as the fooBar I was expecting. As a result, every time I console logged childCtrl.fooBar, I would get undefined.
To fix this problem, I changed the attribute name to foo-bar. Angular did not need to do any more conversions and the object passed through as expected.
I have an infinitely nested data structure, where there is a top level object that has a collection of objects, and each of these objects can also have a collection of objects.
I need to iterate through this tree, which I am currently doing like so:
collection.js
app.directive('collection', function() {
return {
restrict: 'E',
replace: true,
scope: {
collection: '='
},
templateUrl: 'collection.html'
};
});
collection.html
<ul>
<member ng-repeat="member in collection" member="member"></member>
</ul>
member.js
app.directive('member', function($compile) {
return {
restrict: 'E',
replace: true,
scope: {
member: '='
},
templateUrl: 'member.html',
link: function(scope, element, attrs) {
var collection = '<collection collection="member.children"></collection>';
if (scope.member.children) {
$compile(collection)(scope, function(cloned, scope) {
element.append(cloned);
});
}
}
};
});
member.html
<li>
{{ member }}
</li>
index.html
<div data-ng-controller="collectionController">
<collection collection="collection"></collection>
</div>
I need to be able to click on a member, no matter how nested it is, and set the controller's selectedMember property as that member.
so something like this:
app.controller('collectionController', function($scope, collection) {
collection.getCollection().then(function(collection) {
$scope.collection = collection;
});
$scope.selectMember = function(member) {
$scope.selectedMember = member;
};
});
Since I'm calling a function defined in the parent scope (the controller), I think I need to pass down the selectMember function like this:
index.html
...
<collection collection="collection" select-member="selectMember"></collection>
...
collection.html
<member ng-repeat="member in collection" member="member"
select-member="selectMember()" ng-click="selectMember(member)">
</member>
collection.js
...
scope: {
collection: '=',
selectMember: '&selectMember'
}
...
member.js
...
scope: {
member: '=',
selectMember: '='
}
...
I just can't seem to get the function to trigger correctly and set the controller scope's selectedMember property. The parameter passed to the selectMember function is undefined.
I think it's obvious that I'm misunderstanding something about scopes, but the nested nature of the problem I have to solve isn't making things easier.
Any ideas?
Edit:
Heres a plunker: http://plnkr.co/edit/JfxpoLLgpADs9RXSMife
Yes, I think the approach you're taking is correct - i.e. passing the click handler from the outer scope. There is just some minor confusion with how to pass the handler. I wish you created a plunker, but I'll try to go blind. :)
index.html
<collection collection="collection" select-member="selectMember(member)"></collection>
collection.html template
<member ng-repeat="item in collection"
member="item"
select-member="selectMember({member: member})"></member>
collection.js
...
scope: {
collection: '=',
selectMember: '&'
}
...
member.html template
<li ng-click="selectMember({member: member})>{{ member }}</li>
In addition, when you add <collection> for member.children:
<collection collection="member.children"
select-member="selectMember({member: member})"></collection>
member.js
...
scope: {
member: '=',
selectMember: '&'
}
...
EDIT:
Ok, this wasn't trivial :) But it was fun.
A few modifications:
select-member shouldn't just "pass a function" as I incorrectly suggested.
ng-click wasn't properly firing when it was declared on a member - it was firing for both child and parent. I moved it to member.html template.
For clarity, I used item with ng-repeat: ng-repeat="item in collection"
I'm correcting the above code. I also created a fork of you plunker.
Given the following directive:
angular.module('news.directives', [])
.directive('newsArticle', function($location, $timeout) {
return {
restrict: 'AE',
replace: 'true',
templateUrl: 'partials/pages/news/directives/article.html',
scope: true
};
});
And the following template:
<div id="story-{{item.id}}" ng-class="{'red': item.active, 'story-container': true}">
<div class="story-banner-image"></div>
<div class="story stationary">{{ item.title | words: 10 }}</div>
<div class="story-banner-content"></div>
</div>
And the following call to the directive:
<news-article ng-repeat="item in news">
</news-article>
This works. But if I want to use an isolated scope and expose a single item:
scope: {
item: '#'
}
// or
scope: {
news: '#'
}
// or
scope: {}
Then it doesn't. All of the {{item.property}} tags specified in the template return a null value (empty string). Why doesn't item exist in the isolated scope?
It's quite clearly inheriting it's parent properties when scope is set to true, but it's not inheriting when I tell it what it should inherit.
You problem is that you are confused about the way scope configuration is set. In order to setup two-way data binding with isolated scope you should provide corresponding attribute in HTML:
<news-article ng-repeat="item in news" item="item"></news-article>
and then setup directive accordingly:
scope: {
item: '='
}
Demo: http://plnkr.co/edit/b1I8PIc27MvjVeQaCDON?p=preview
ng-repeat inside a directive with isolated scope is not picking up the property that's passed through '=' binding.
HTML:
<body ng-controller="myCtrl">
<div my-directive list="users">
<ul>
<li ng-repeat="item in list">
{{item.name}}
</li>
</ul>
</div>
</body>
JS:
var app = angular.module('myApp', []);
app.controller('myCtrl', function ($scope) {
$scope.users = [
{ name: 'John Doe'},
{ name: 'Jane Doe' },
{ name: 'Jesse Doe' }
];
});
app.directive('myDirective', [function() {
return {
restrict: 'A',
scope: {
list: '='
},
link: function(scope, element, attrs) {
}
}
}]);
Above code works fine with angular 1.0.8:
http://jsfiddle.net/shazmoh/4DN39/7/
but not with angular 1.2.14:
http://jsfiddle.net/shazmoh/4DN39/6/
What got changed with '1.2.x' that I'm missing?
From the migration to 1.2 guide:
Isolate scope only exposed to directives with scope property
Directives without isolate scope do not get the isolate scope from an
isolate directive on the same element. If your code depends on this
behavior (non-isolate directive needs to access state from within the
isolate scope), change the isolate directive to use scope locals to
pass these explicitly.
So, with > 1.2.0, isolated directives are completely isolated.