Acessing directive's scope within trancluded ng-repeat - javascript

I'm writing a component and need to ng-repeat on directive's isolate scope property.
Consider this:
<div some-directive>
<h2>Let's repeat something</h2>
<p ng-repeat="item in contr.items">
Title: {{ item.title }}
Description: {{ item.desc }}
</p>
</div>
My idea is that directive provides a collection of items (a resolve in real code, but I will keep it simple here). Right now I have:
.directive('someDirective', function() {
return {
scope: {},
transclude: true,
template: '<div ng-transclude></div>',
controller: function($scope) {
$scope.items = this.items = [{
title: "Item 1 title",
desc: "Description"
}, {
title: "Item 2 title",
desc: "Another desc"
}, {
title: "Item 3 title",
desc: "Third desc"
}, {
title: "Item 4 title",
desc: "Third desc"
}];
},
controllerAs: 'contr'
};
});
See this Plunkr (and edit if you like).
Do you have any ideas?

It's by design that content added via ng-transclude will be binded with an outer (original) scope, not a scope of the current element that ng-transclude is on.
If you want the transcluded content to be binded with the isolate scope, you could use a modified version of ng-tranclude like this:
.directive('myTransclude', function () {
return {
restrict: 'EAC',
link: function(scope, element, attrs, controllers, transcludeFn) {
transcludeFn(scope, function(nodes) {
element.empty();
element.append(nodes);
});
}
};
});
and use it instead of ng-tranclude like this:
template: '<div my-transclude></div>',
Example Plunker: http://plnkr.co/edit/yDwuwCYtAzxyIhJRgZoJ?p=preview

Related

create an angularjs directive to wrap jquery tagcanvas plugin

I want to implement an animated word cloud. Since I did not found any angular or Ionic ready plugins to do what i wanted, I decided (and I found it interesting to learn) to create an angular wrapper for a jquery code.
The plugin is tagcanvas, and the usage example is this
What I have done so far:
I added the jquery js file to my index.html
I created a js file where I declared my directive, and added it to index.html:
tagcanvas.js
angular.module('tagCanvas', [])
.directive('tagCanvas', function() {
return {
restrict: 'E',
link: function (scope, element, attrs) {
element.tagcanvas({
outlineColour: attrs.outlineColour,
reverse: attrs.reverse,
depth: attrs.depth,
maxSpeed: attrs.maxSpeed,
textFont: attrs.textFont,
textColour: attrs.textColour,
weightMode: attrs.weightMode,
weight: attrs.weight
}, attrs.canvas);
}
};
});
As you can see in the codepen example, there are attributes that I need to call, and a html element id canvas, which I did not know how to call them in my directive.
My second problem is that I don't know how to create and call the words that I want to display in my tag cloud.
I looked over lots of tutorials, but I still don't know how to do it. Any help will be appreciated.
First, I would not name your Angular app and directive with the same name. Not only is it confusing, but it may not work properly. Second, although there may be many ways to approach this, here is how I would do it:
directive
(I have changed the directive to an attribute rather than an element and used an isolate scope for the attributes. The link function is wrapped in a $timeout to ensure the element exists in the DOM prior to trying to call tagcanvas.)
.directive("tagCanvas", function($timeout) {
return {
restrict: "A",
scope: {
canvasId: "#",
outlineColour: "#",
reverse: "#",
depth: "#",
maxSpeed: "#",
textFont: "#",
textColour: "#",
weightMode: "#",
weight: "#"
},
link: function (scope, element) {
$timeout(function() {
element.tagcanvas({
outlineColour: scope.outlineColour,
reverse: scope.reverse,
depth: scope.depth,
maxSpeed: scope.maxSpeed,
textFont: scope.textFont,
weightMode: scope.weightMode,
weight: scope.weight
}, scope.canvasId);
});
}
};
})
controller
(You would likely want to get your word list from a service or some other storage, but for illustration I have hardcoded an array in the controller)
.controller("ctrl", function ($scope) {
$scope.wordList = [
{
href: "#",
fontSize: "2.83ex",
text: "1000"
}, {
href: "#",
fontSize: "4.8ex",
text: "example"
}, {
href: "#",
fontSize: "8.77ex",
text: "gif"
}, {
href: "#",
fontSize: "2.83ex",
text: "svg"
}, {
href: "#",
fontSize: "8.77ex",
text: "jpg"
}, {
href: "#",
fontSize: "10.68ex",
text: "png"
}, {
href: "#",
fontSize: "2.83ex",
text: "bmp"
}, {
href: "#",
fontSize: "4.8ex",
text: "img"
}
];
})
html
<div>
<canvas width="300"
height="300"
id="myCanvas"
tag-canvas
canvas-id="tags"
outline-colour="#ff00ff"
text-font="Arial"
text-colour="#ff00ff"
reverse="true"
depth="0.8"
max-speed="0.05"
weight-mode="both"
weight="true"></canvas>
</div>
<div id="tags" style="font-size: 50%;">
<a ng-repeat="word in wordList" href="{{word.href}}" style="font-size: {{word.fontSize}}">{{word.text}}</a>
</div>
I have made an example plunker with a working example where you can dynamically add new tags:
http://plnkr.co/edit/zR4pxcZlqPxr8pnhsNRI?p=preview
It is not that easy to use tagcanvas inside of an AngularJS directive. Tagcanvas relies heavily on a certain DOM representation. This can sometimes be at odds with the way AngularJS works. Because of that I had to use the $timeout service for example.
It would be better to make a "component" out of you tagcanvas directive which itself is responsible for its HTML.
app.directive('tagCanvas', function($timeout) {
return {
scope: {
tagData: '='
},
controllerAs: 'vm',
template: '<canvas width=300 height=300" id="my-canvas"></canvas>' +
'<div id="tags"><a ng-repeat="tag in tagData" ng-bind="tag.name"></a></div>',
restrict: 'E',
link: function(scope, element, attrs) {
element.find('canvas').tagcanvas({
outlineColour: '#ff00ff',
reverse: true,
depth: 0.8,
maxSpeed: 0.05,
textFont: null,
textColour: null,
weightMode: 'both',
weight: true
}, 'tags');
scope.$watch('tagData', function(newValue) {
$timeout(function() {
element.find('canvas').tagcanvas('reload');
}, 0);
}, true);
}
};
});

Nested directives not inheriting parent scope properly

Having a problem with nested directives and inheriting the scope from a parent. It happens that the scope I want to inherit from is an isolated scope. I've the code below
test.html
<div ng-controller="testCtrl as test">
<special-select data-selected-item="test.selectedThing">
<special-select-selected-item></special-select-selected-item>
</special-select>
</div>
app.js
angular
.module('testApp', ['special-inputs'])
.controller('testCtrl', [
function() {
this.items = [
{ id: 1, name: 'alex 1', displayName:"alex 1 dn", imageUrl: 'http://placehold.it/505x100' }
];
this.selectedThing = this.items[0];
this.test = function() {
console.log('test fn');
}
}
]);
special-inputs.js
angular
.module('special-inputs', [])
.directive('specialSelect', [
function() {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div class="container" ng-transclude></div>',
scope: {
selectedItem: '='
},
link: {
pre: function(scope) {
console.log('parent link pre - ', scope.selectedItem);
},
post: function(scope) {
console.log('parent link post - ', scope.selectedItem);
}
}
}
}
])
.directive('specialSelectSelectedItem', [
function() {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div class="selected" ng-transclude></div>',
scope: true,
link: {
pre: function(scope) {
console.log('child link pre - ', scope.selectedItem);
},
post: function(scope) {
console.log('child link post - ', scope.selectedItem);
}
}
}
}
])
As you can see the nested directive is trying to create it's own isolate scope but grab the "selectedItem" from the isolate scope of the parent.
The docs say it should inherit and transclusion is semi prototypical, so I'm wondering why it's not working. Thanks. Any help would be appreciated
UPDATE
I've updated the code to show the problem I'm having now. It seems that the scope item is set in the pre + post link functions on the parent, but never get put down to the child. The console outputs
2015-12-29 15:13:44.258 special-select.js:35 parent link pre - Object {id: 1, name: "alex 1", displayName: "alex 1 dn", imageUrl: "http://placehold.it/505x100"}
2015-12-29 15:13:44.258 special-select.js:64 child link pre - undefined
2015-12-29 15:13:44.260 special-select.js:67 child link post - undefined
2015-12-29 15:13:44.260 special-select.js:38 parent link post - Object {id: 1, name: "alex 1", displayName: "alex 1 dn", imageUrl: "http://placehold.it/505x100"}
I've tried using scope.$watch and attrs.$observe to populate the valu

How to implement here hiding and displaying items in the list?

I found a code that outputs a multi-level list into Angular here
http://jsfiddle.net/c4Kp8/
var items = [{
title: 'Something',
children: [
{ title: 'Hello World' },
{ title: 'Hello Overflow' },
{ title: 'John Doe', children: [
{ title: 'Amazing title' },
{ title: 'Google it' },
{ title: 'Im a child', children: [
{ title: 'Another ' },
{ title: 'He\'s my brother' },
{ title: 'She\'s my mother.', children: [
{title: 'You never know if im going to have children'}
]}
]}
]}
]
}];
var app = angular.module('app', []);
app.controller('test', function( $scope ) {
$scope.items = items;
});
app.directive('nestedItem', ['$compile', function($compile){
return {
restrict: 'A',
link: function(scope, element){
console.log(element);
if (scope.item.children){
var html = $compile('<ul><li nested-item ng-repeat="item in item.children">{{item.title}}</li></ul>')(scope);
element.append(html);
}
}
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="test">
<ul>
<li nested-item ng-repeat="item in items">{{item.title}}</li>
</ul>
</div>
How to implement here hiding and displaying items in the list? Only Angular not jquery....
Hope you know that, you can make nested ng-repeat without special directive for it :-) ?
You can use for example angular-ui.bootstrap.collapse, or some custom solutions.
Solutions:
Angular UI Bootrap
Googled custom solution
Use ng-show / ng-hide / ng-if
https://docs.angularjs.org/api/ng/directive/ngShow
https://docs.angularjs.org/api/ng/directive/ngHide
https://docs.angularjs.org/api/ng/directive/ngIf
Maybe a treeview is more adequate than multiples imbricated ng-repeat
http://ngmodules.org/modules/angular.treeview

Why doesn't directive work if I put name as an attribute rather than a tag?

In the example from the Angular documentation, a directive can be used by putting its name as an attribute in a <div>. The example given is
<div ng-controller="Controller">
<div my-customer></div>
</div>
with the js looking like
angular.module('docsSimpleDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
}])
.directive('myCustomer', function() {
return {
template: 'Name: {{customer.name}} Address: {{customer.address}}'
};
});
However, in a similar example, over here, if I change the html code from <tree> to <div tree>, the code no longer works.
Why not?
The code from JS Fiddle:
<div ng-app="myapp">
<div ng-controller="TreeCtrl">
<tree family="treeFamily">
<p>{{ family.name }}</p>
</tree>
</div>
</div>
var module = angular.module('myapp', []);
module.controller("TreeCtrl", function($scope) {
$scope.treeFamily = {
name : "Parent",
children: [{
name : "Child1",
children: [{
name : "Grandchild1",
children: []
},{
name : "Grandchild2",
children: []
},{
name : "Grandchild3",
children: []
}]
}, {
name: "Child2",
children: []
}]
};
});
module.directive("tree", function($compile) {
return {
restrict: "E",
transclude: true,
scope: {family: '='},
template:
'<ul>' +
'<li ng-transclude></li>' +
'<li ng-repeat="child in family.children">' +
'<tree family="child">{{family.name}}</tree>' +
'</li>' +
'</ul>',
compile: function(tElement, tAttr, transclude) {
var contents = tElement.contents().remove();
var compiledContents;
return function(scope, iElement, iAttr) {
if(!compiledContents) {
compiledContents = $compile(contents, transclude);
}
compiledContents(scope, function(clone, scope) {
iElement.append(clone);
});
};
}
};
});
tree {
margin-left: 20px;
display: block;
}
The restrict option is used to specify how a directive can be invoked on the page. There are four different ways to invoke a directive, so there are four valid options for restrict:
'A' - attribute - <span ng-sparkline></span>
'E' - element - <ng-sparkline></ng-sparkline>
'C' - class - <span class="ng-sparkline"></span>
'M' - comment - <!-- directive: ng-sparkline -->
In your case its not working because it is defined as restrict 'E' - element.
It's because of the restrict option in the directive.
Here it is set to e which means to match only the element name.
More https://docs.angularjs.org/guide/directive

AngularJS Directive with Scope data array

i've a array. Every item of the array holds data for an directive. The array is created inside a controller.
The Model
$scope.myDirectiveData =
[ { title: "First title", content: "First content" },
{ title: "Second title", content: "Second content" } ];
The Directive Template
<div class="MyDirective">
<h1>{{myDirectiveData.title}}</h1>
<p>{{myDirectiveData.content}}</p>
</div>
How should i implement the the directive to create a item for any item in the array ? Something like...
<MyDirective data-ng-repeat="item in myDirectiveData"></MyDirective>
Here is an example using a directive. It used ng-repeat to call your directive for each object in the array on your scope. In this fiddle is this what you are looking for.
http://jsfiddle.net/gLRxc/4/
angular.module('test', [])
.directive('myDirective', function () {
var template = '<div class="MyDirective">' +
'<h1>{{ data.title }}</h1>' +
'<p>{{ data.content }}</p>' +
'</div>';
return {
template: template,
scope: {
data: '='
},
link: function (scope, element, attrs) {
}
};
})
.controller('TestController', function ($scope) {
$scope.myDirectiveData = [
{ title: "First title", content: "First content" },
{ title: "Second title", content: "Second content" }
];
})
;
Html Edited: the ng-repeat is on the same line as the directive like your question states.
<div ng-app="test" ng-controller="TestController">
<div my-directive data="item" ng-repeat="item in myDirectiveData"></div>
</div>
<div ng-repeat="item in myDirectiveData">
<h1>{{item.title}}</h1>
<p>{{item.content}}</p>
</div>

Categories