$scope and ng-model in the same directive - javascript

I have a directive that saves in a model in the Controller. It is a "text-button" (as per requirements) which is just a read-only textbox. There are three text-buttons per "Line" and 13 "Lines".
I need to pull up a selection modal and load some data in depending on what was clicked, so that a new selection can be made.
Although the model on the controller is changed, I don't know what was changed by the time the selection modal pops up.
Model:
$scope.Lines = {
"eg1": { one: '', two: '', three: '' },
"eg2": { one: '', two: '', three: '' },
"eg3": { one: '', two: '', three: '' },
"eg4": { one: '', two: '', three: '' },
... 9 more ...
};
Directive:
.directive('textButton', function () {
return {
restrict: 'E',
require: 'ngModel',
link: function ($scope, elm, attrs, ctrl) {
elm.on('click', function () {
$scope.popModal(this); //<--I want the ng-model here!
});
},
template: '<input type="text" readonly="readonly" class="form-control" />'
};
});
View:
<ul>
<li> <text-button ng-model="Lines.eg1.one"></text-button> </li>
<li> <text-button ng-model="Lines.eg1.two"></text-button> </li>
<li> <text-button ng-model="Lines.eg1.three"></text-button> </li>
<ul>
<ul>
<li> <text-button ng-model="Lines.eg2.one"></text-button> </li>
<li> <text-button ng-model="Lines.eg2.two"></text-button> </li>
<li> <text-button ng-model="Lines.eg2.three"></text-button> </li>
<ul>
... 11 more ...
I've looked at $watch and $scope.watch, but nothing seems to be able to tell me what in a particular model has changed.
https://stackoverflow.com/a/15113029/1913371

In the end, you need to isolate the scope of your directive so you have a chance to get to the ngModel of each line:
scope: {
ngModel: '#',
popModal: '='
}
then you can use it in your callback:
elm.on('click', function () {
$scope.popModal($scope.ngModel); //<--you get the ng-model here!
});
However, this means you also lose access to popModal() which I guess is defined in the controller scope. To fix this, you need to hand it in as a second parameter (I named it pop-modal):
<text-button ng-model="Lines.eg1.one" pop-modal="popModal"></text-button>
Tying it all together, here's a JSBin using Angular 1.2 (although you really should get away from that).

If you are using AngularJs 1.3+, you can use 'controllerAs' in the directive definition object. Then create a controller to do popModal.

Related

Is it possible to use a directive again inside its own template?

Im trying to write a 'comments' directive in angular to load nested comments(from Json data) and reuse the same directive for child comments/replies.
The parent comments load just fine by themselves, however when I try to show child comments by using the 'comments' directive again inside its own template, the app just freezes and I have to close it down.
Below is some of my code:
app.html: ---
<ul ng-repeat="comment in View2.postItems | limitTo: 10">
<comments collection="comment"></comments>
</ul>
comments.html (directive):----
<li>
<span>
{{ collection.data.commentText }}
<ul ng-show="{{collection.data.replies}}"
ng-repeat="comment in collection.data.replies.data.children">
<!-**child comments: this line causes the app to freeze:**-->
<comments collection="comment"></comments>
</ul>
</span>
</li>
comments.js:---
var comments = function(){
return {
templateUrl : 'modules/comments/comments.html',
restrict:'E',
scope:{
collection: '='
},
link: function(scope, element, attrs){
}
};
};
You can build recursive directives with the $compile service to conditionally append the child directive. Example: (http://plnkr.co/edit/WpNp20DSjJhO412j3cSw?p=preview)
function comment($compile) {
return {
template: '<span ng-bind="comment.text"></span>',
link: function(scope, element) {
if (angular.isArray(scope.comment.collection)) {
element.append($compile('<comments collection="comment.collection"></comments>')(scope));
}
}
}
}
function comments(){
return {
template : '<ul><li ng-repeat="comment in collection"><comment></comment></li></ul>',
scope:{
collection: '='
}
};
}

Appending to an array that's tied to an Angular.JS directive

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

Angular, adding scope is breaking my directive

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.

Calling a parent scope function from deeply nested directives

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.

AngularJS converting object to string in directive

I need some help with getting AngularJS to maintain my non-string values in directive attributes.
I was looking for a way to render a tree structure in HTML from a piece of JSON, and I found this code: http://jsfiddle.net/n8dPm/
I've been trying to adapt that for my project, as shown in the code below.
My controller/directive is shown here:
cxpControllers.controller("ProductTocCtrl", ["$scope", "$http", "$routeParams",
function ProductTocController($scope, $http, $routeParams) {
$scope.typeOf = typeOf;
//test value
$scope.contents = {
"id": 1,
"name": "Test",
subsections: [
{
id: 2,
name: "Test1.1",
link: "test11.xml",
test: 34
},
{
id: 3,
name: "Test1.2",
link: "test12.xml",
test: 95
}
]
}
}]);
cxpControllers.directive('tree', function($compile) {
return {
restrict: 'E',
scope: {key: "=", content: "="},
templateUrl: "tree_renderer.html",
compile: function(tElement, tAttr) {
var contents = tElement.contents().remove();
var compiledContents;
return function(scope, iElement, iAttr) {
if(!compiledContents) {
compiledContents = $compile(contents);
}
compiledContents(scope, function(clone, scope) {
iElement.append(clone);
});
};
}
};
});
And then this is my template:
<script type="text/ng-template" id="tree_renderer.html">
{{key}}:
<ul ng-if="typeOf(content) == 'object' && content != null">
<li ng-repeat="(key, content) in content">
<tree key="key" content="content"></tree>
</li>
</ul>
<span ng-if="typeOf(content) != 'object'">
"{{content}}"
</span>
</script>
<ul>
<li ng-repeat="(key, content) in contents">
<tree key="key" content="content"></tree>
</li>
</ul>
This would work, except for one problem. Angular is turning the value of "content" into a string, preventing the recursion from working because it can't iterate over a string.
I have seen other questions like this, for example here, but their problem is that they used "#" in the directive scope, which converts to a string. But since I'm using "=", it should maintain the type.
Here's the output I'm seeing with the test data shown in the code above:
I would appreciate any help you can give. If you need more information I'll be happy to supply it.
The problem is with the typeOf function in your template. The compiled template doesn't find this function so it is never equal to 'object'. Add a controller to your directive to define it.
I took the plunkr and added this:
controller: function($scope) {
$scope.typeOf = function(val) {
return typeof val;
};
},
It does recognize it as an object. Check out the updated plunkr here.

Categories

Resources