SVG with Angular directive : bad performances - javascript

Its the first time I'm asking something here but i'm totally stuck.
I'm creating an interactive svg map with Angular. It works fine with D3 but I would like to use Angular, with its directives and templates.
Currently, I managed to have the map show up on screen and manipulate it, but the performances are awful.
I know that I must filter my datas but i don't know where : is it on the controller, or the directive's link, or the API ? I don't know.
Note that I'm still a noob with Angular (hardly a month), even with code since I started to learn it 3 months ago. So, you will certainly read some silly stuff here.
Anyway, my question is : what I am supposed to do to replace my complex ng-hide by a simple ng-if (i want to remove path depending on the year input).
So, I'm retrieving a JSON list of path from my API.
myjson :
[ { "land_name" : "landa", "path" : "...", "born" : 5000, end : 5152}
{ "land_name" : "landi", "path" : "...", "born" : 1200, end : 7100}
{ "land_name" : "lando", "path" : "...", "born" : 100, end : 4000} ]
my api (node + mongoose):
// get all the lands (accessed at GET http://localhost:8080/api/landapi)
.get(function(req, res) {
landapi.find(function(err, docs) {
if (err)
res.send(err);
res.json(docs);
});
});
angular :
my factory :
.factory('landDataFactory', ['$http', function($http) {
return $http.get('http://localhost:3000/api/landapi')
.success(function(data) {
return data;
})
.error(function(err) {
return err;
});
my controller :
.controller('testCtrl', function($scope, landDataFactory) {
var vm = this;
vm.paysList = [];
vm.year = ''; (==> ng-model in the template)
landDataFactory.success(function(data) {
vm.land = data;
for (var i = 0; i < vm.land.length; i++) {
vm.LandList.push(vm.land[i]);
}
});
my directive :
.directive('dynamicMap', function() {
return {
restrict: 'E',
replace : true,
scope: {
datapath : '=',
year : '='
},
templateUrl : './modules/dynamicmap.tpl.html',
link : function(scope, element, attrs) {
}
};
my template :
<div>
<div class="year">
<input ng-model="year" type="number" step="50" class="inputDate col-xs-2 form-control" placeholder="date""> <br/>
</div>
<svg class="Map">
<g class="drawLand" ng-repeat="data in datapath">
<path ng-hide="year == NULL || data.born > year || data.end < year" ng-attr-d="{{ data.path }}" id="{{ data.id_land }}"></path>
</g>
</svg>
</div>
my directive when i'm calling it :
<dynamic-map datapath="vm.paysList" year="vm.year"></dynamic-map>
Thanks in advance

I am barely qualified to comment since I only played with Angular for about 2 days, but in that brief period, I found that it is extremely easy to run into performance issues if you have even medium sized data sets. From what I understand, you should try very hard to limit how many two way data bindings you create. Since your code has an ng-repeat, you should, if possible, look at making the bindings inside of the repeat one way by replacing {data:path} with {::data.path}.
My real advise though is to not use angular. From what I can tell, Angular 1.0 is a dead end since it will soon be eclipsed with Angular 2.0. In addition the entire philosophy of Angular seems built around the idea of "Look how nice this syntax is! HTML and javascript have never been easier!", to "Oh, you want to make a real app? Well here's some ugly hacks to make it perform, and by the way, don't you understand the difference between a directive and a factory-directive and a directive-factory? And how could you not know that every tag has different scoping rules...".
In short, wait for Anuglar 2.0 or look for something else. I went with react.js and so far find it to lighter weight and more intuitive and a better for long run productivity.

Related

angularjs ng-click not working on dynamic html elements

For some reason when using this function('testclickfn') as ng-click on dynamic elements, it doesn't invoke the function. Here is the angularjs file:
app.controller('testctrl',function($scope){
testfn($scope);
$scope.showelements = function(){
displayTestRows();
}
});
function testfn($scope){
$scope.testclickfn = function(){
alert('testing click fn');
};
}
function displayTestRows(){
for(var i=0; i < 5; i++){
$("#testdiv").append('<p ng-click="testclickfn()">click me</p><br>');
}
}
HTML page that calls angularjs controller 'testctrl':
<div id="testdiv" ng-controller="testctrl">
<button ng-click="showelements()">Show dynamic elements</button><br>
</div>
I'm assuming since the 'click me' tags are being generated after angular has loaded the page, it doesn't know of anything after page is generated so ng-click="testclickfn()" doesn't get registered with angularjs.
How do I get around this situation?
You're creating elements in a way angular has no idea about (pretty bad practice), but not to worry, you can let angular know!
Change the controller signature to
controller('testctrl', function($scope, $compile) {
Then run compile the new elements manually to get the ng-click directive activated
$scope.showelements = function(){
displayTestRows();
$compile($("#testdiv").contents())($scope);
}
If you cant tell, having to use jquery selectors inside your controller is bad, you should be using a directive and the link function to attach the element to the scope (ie, what if you have multiple testctrl elements?), but this'll get you running
As promised
The general rules are that no JS should be outside the angular functions, and that DOM manipulation, where appropriate should be handled by angular also.
Example 1: powerful
Have a look
<div ng-controller="ctrl">
<button ng-click="show('#here')">
create
</button>
<div id="here">
I'll create the clickables here.
</div>
</div>
use controllers for things that share stuff between a lot of different things
.controller('ctrl', ['$scope', '$compile', function($scope, $compile) {
$scope.sharedVariable = 'I am #';
$scope.show = function(where) {
where = $(where).html('');
//lets create a new directive, and even pass it a parameter!
for (var index = 0; index < 5; ++index)
$('<div>', {'test':index}).appendTo(where);
$compile(where.contents())($scope);
};
}])
use directives for non-unique elements that each have their own states
.directive('test', function() {
return {
//these too have their own controllers in case there are things they need to share with different things -inside them-
controller : ['$scope', function($scope) {
$scope.test = function() {
//see, no selectors, the scope already knows the element!
$scope.element.text(
//remember that parent controller? Just because we're in another one doesnt mean we lost the first!
$scope.$parent.sharedVariable +
$scope.index
);
}
}],
//no need to do things by hand, specify what each of these look like
template : '<p>click me</p>',
//the whole "angular way" thing. Basically no code should be outside angular functions.
//"how do I reference anything in the DOM, then?"; that's what the `link` is for: give the controller access using `scope`!
link : function(scope, element, attributes) {
//you can assign "ng-click" here, instead of putting it in the template
//not everything in angular has to be HTML
scope.element = $(element).click(scope.test);
//did you know you can accept parameters?
scope.index = Number.parseInt(attributes.test) + 1;
},
//just some set up, I'll let you look them up
replace : true,
restrict : 'A',
scope : {}
};
})
Example 2: Simple
But that is just a very generic and powerful way of doing things. It all depends on what you need to do. If this very simple example was indeed all you needed to do you can make a very simple, almost-all-html version:
<div ng-controller="ctrl">
<button ng-click="items = [1, 2, 3, 4, 5]">
create
</button>
<p ng-repeat="item in items" ng-click="test($event)">
<span>click me</span>
<span style="display:none">I am #{{item}}</span>
</p>
</div>
.controller('ctrl', ['$scope', function($scope) {
$scope.test = function($event) {
$($event.currentTarget).children().toggle();
};
}])
That's it, works the same almost

AppGyver Steroids Supersonic Views

I'm trying to get my head around switching views / passing views to another view.
I have an app that is calling in a kimono API, that's all setup with the supersonic background and looks fine. I have 1 string and 2 objects in the API. I have a page that is calling in the full list of events using a page called event:
{{ event.eventdescription }}
The Event#Index controller is:
angular
.module('event')
.controller("IndexController", function ($scope, Event, supersonic) {
$scope.events = null;
$scope.showSpinner = true;
Event.all().whenChanged( function (events) {
$scope.$apply( function () {
$scope.events = events;
$scope.showSpinner = false;
});
});
});
And all of that displays properly. The issue is when I click on one of those items shown which should go to the specific event I get nothing. And I'm sure I'm doing this wrong or don't understand enough about switching views. I've read many examples, but I'm not getting how it all goes together.
here is my event#show page. Very generic just trying to load any information at this point.
<div ng-controller="ShowController">
<super-navbar>
<super-navbar-title>
Show
</super-navbar-title>
</super-navbar>
<div class="padding">
{{ event.eventdescription }}
</div>
</div>
And the showcontroller:
angular
.module('event')
.controller("ShowController", function ($scope, Event, supersonic) {
$scope.events = null;
Event.all().whenChanged( function (events) {
$scope.$apply( function () {
});
});
});
And this always returns a blank page. When i check the log it says Undefined.undefined which i'm not sure what that means.
Any insight on this is greatly appreciated. In the appgyver docs I saw something called.
var view = new supersonic.ui.View("bananas#show");
supersonic.ui.layers.push(view);
But I'm not sure how to use this?
ANY insight is appreciated.
So, UPDATED I have:
here's the event#index i'm working with.
<div ng-controller="IndexController">
<super-navbar>
<super-navbar-title>
Event Index
</super-navbar-title>
</super-navbar>
<ul class="list" ng-hide="events.length == 0">
<super-navigate view-id="event#show" data-params-id="{{event.id}}" ng-repeat="event in events">
<li class="item item-icon-right">
<h2 ng-bind="event.EventTitles['text']"></h2>
<img ng-src="{{ event.HeadlineImages.src }}" width="100px" height="100px">
<p> {{ event.eventdescription }} </p>
<i class="icon super-ios7-arrow-right"></i>
</li>
</super-navigate>
</ul>
</div>
And the Index Controller
angular
.module('event')
.controller("IndexController", function ($scope, Event, supersonic) {
$scope.events = null;
Event.all().whenChanged( function (events) {
$scope.$apply( function () {
$scope.events = events;
});
});
});
The show html page.
<div ng-controller="ShowController">
<super-navbar>
<super-navbar-title>
Show
</super-navbar-title>
</super-navbar>
<div class="padding">
<p>
{{event.eventdescription}}
</p>
</div>
</div>
The ShowController
angular
.module('event')
.controller("ShowController", function ($scope, Event, supersonic) {
supersonic.ui.views.current.params.onValue( function (Event) {
$scope.events = event.id;
});
Event.find($scope.events).then( function (Event) {
$scope.$apply( function () {
$scope.event = Event;
});
});
});
And I also updated the structure.coffee as so
rootView:
location: "event#index"
preloads: [
{
id: "event#show"
}
{
id: "using-the-scanner"
location: "example#using-the-scanner"
}
]
Any help is appreciated.
It doesn't look like the data is being set in the your ShowController. I commented about this before. I think you need to pass the id of the event using <super-navigate> with a location property and a data-params-id or whatever you want the parameter name to be. Then in your ShowController you can access it with:
supersonic.ui.views.current.params.onValue( function (values) {
// values.nameOfPropertyPassedInCouldBeEventId
$scope.id = values.id;
});
Then you might be able to do something like this to access the Event by id:
Event.find($scope.id).then( function (theEvent) {
$scope.$apply( function () {
$scope.event = theEvent;
});
});
Now in your view where you have {{ event.eventdescription }} there should be some data.
And another piece for when the view is visible meaning every time you see that view page this will fire:
supersonic.ui.views.current.whenVisible( function () {
// your code for watching events
});
Ok, after a couple weeks of trying to get this working and although, I still haven't been able to get this to work yet.. I think I'm getting somewhere with this FINALLY... It seems the biggest problem here is using Kimono and AppGyver. The JSON file has been updated in Kimono using:
function transform(data) {
data.results.collection1 = data.results.collection1.map(function(o) {
o.eventdescription = {
text: o.eventdescription
}
return o;
});
return data;
}
This cleans up the JSON file exported/ coming in as API to App Gyver so that all parts are objects. ( I know, maybe not a big deal, but I just wanted to make this as clean as possible). To give you an idea of the before and after of using this script in the Kimono Modify Results box -->
BEFORE:
"EventTitles": {
"href": "http://",
"src": "http://.jpg",
"text": "Lorem Ipsum"
},
"HeadlineImages": {
"href": "http://",
"src": "http://.jpg",
"text": "Lorem Ipsum"
},
"eventdescription":"Lorem Ipsum"
},
which leaves eventdescription as a string rather than object and then the AFTER:
"EventTitles": {
"href": "http://",
"src": "http://.jpg",
"text": "TEXT"
},
"HeadlineImages": {
"href": "http://",
"src": "http://.jpg",
"text": "TEXT"
},
"eventdescription": {
"text": "TEXT"
},
So, after running this into Kimono as you can see all entries are "objects". And you'd use &kimmodify=1 AFTER the apikey in the link thusly:
https://www.kimonolabs.com/api/{indentifier}{apikey}&kimmodify=1
NEXT, as I was explained to by the AppGyver community one would pretty much need an "id" of sorts for each item in the JSON / API that's being created to be able to use the ShowController to create a reasonable/ feasible url string on the show.html.
Which should create something like /app/tier/showid=123456789 when going from the index to a specific entry view.
(You find the URLs by using the debug mode in AppGyver either via Safari Web Inspector on Mac with the IOS Emulator. or a browser using http://localhost:[some port number]/location/of/app when using the Android Emulator (the recommended Genymotion).
So, to do this, in Kimono use the API Hash addition &kimhash=1 to the end of your url AFTER the APIKEY but BEFORE the modify such as this:
https://www.kimonolabs.com/api/{indentifier}{apikey}&kimhash=1&kimmodify=1
. See: Kimono API Docs- Re:Hash.
This creates something like
"EventTitles": {
"href": "http://",
"src": "http://.jpg",
"text": "TEXT"
},
"HeadlineImages": {
"href": "http://",
"src": "http://.jpg",
"text": "TEXT"
},
"eventdescription": {
"text": "TEXT"
},
"hash":"1a2b3c4d5e6f7g8h9z"},
a random 'indentifier' is created for each entry.
Now, that's where I'm stuck now. ...because the API URL needing to come in is:
https://www.kimonolabs.com/api/{indentifier}{apikey}&kimhash=1&kimmodify=1
and when you go to configure your API on the backend there is no area I see to enter this new &kimhash=1&kimmodify=1 that needs to be at the end of the URL to call in the correctly formatted and id'd API and as far as I can see there is no reference for doing this.
http://docs.appgyver.com/supersonic/guides/data/other-data-providers/kimono-labs/
I feel like this is the next to last step in figuring this all out and finally being able to get this up and working. The last being to obviously revisit pulling in the id to the ShowController which I'm feeling somewhat confident about if I can somehow figure out this last part.
Any ideas??

Getting an error with context in jQuery when using ng-repeat and limit-to and a tooltip from tether.js

First off, I know that's a heck of a title.
Ive recently taken over angular-tooltip and am attempting to build a custom tooltip for my main work project.
In my project, I have an ng-repeat directive that simply says
<div class="row-submenu-white" ng-repeat="merge in potentialMerges | limitTo: numPotentialMergesVisible" company-profile-tooltip="merge.preview"></div>
Using the instructions for the library, I defined a custom tooltip directive:
myApp.directive('companyProfileTooltip', ['$tooltip', ($tooltip) => {
return {
restrict: 'EA',
scope: { profile: '#companyProfileTooltip' },
link: (scope: ng.IScope, elem) => {
var tooltip = $tooltip({
target: elem,
scope: scope,
templateUrl: "/App/Private/Content/Common/company.profile.html",
tether: {
attachment: 'middle right',
targetAttachment: 'middle left',
offset: '0 10px'
}
});
$(elem).hover(() => {
tooltip.open();
}, () => {
tooltip.close();
});
}
};
}]);
Company.profile.html is simply:
<div>Hello, world!</div>
Now, if you notice, in the ng-repeat I have a limitTo filter. For each of those (inititally 3) merges work perfectly, where a <div>Hello, world!</div> tooltip is properly added.
Then I trigger the limitTo to limit to a greater number. Each repeated element after the initial 3 gives me the following error:
TypeError: context is undefined
if ( ( context.ownerDocument || context ) !== document ) {
The error is in jquery-2.1.1.js, which debugging appears to be hopelessly over my head.
What I can tell you is that the function being called for that line is
Sizzle.contains = function( context, elem ) {
// Set document vars if needed
if ( ( context.ownerDocument || context ) !== document ) {
setDocument( context );
}
return contains( context, elem );
};
With a call stack of
Sizzle</Sizzle.contains(context=Document 046f7364-fa8d-4e95-e131-fa26ae78d108, elem=div)jquery-2.1.1.js (line 1409)
.buildFragment(elems=["<div>\r\n Hello, world!\r\n</div>"], context=Document 046f7364-fa8d-4e95-e131-fa26ae78d108, scripts=false, selection=undefined)jquery-2.1.1.js (line 5123)
jQuery.parseHTML(data="<div>\r\n Hello, world!\r\n</div>", context=Document 046f7364-fa8d-4e95-e131-fa26ae78d108, keepScripts=true)jquery-2.1.1.js (line 8810)
jQuery.fn.init(selector="<div>\r\n Hello, world!\r\n</div>\r\n", context=undefined, rootjQuery=undefined)jquery-....2.1.js (line 221)
jQuery(selector="<div>\r\n Hello, world!\r\n</div>\r\n", context=undefined)jquery-2.1.1.js (line 76)
compile($compileNodes="<div>\r\n Hello, world!\r\n</div>\r\n", transcludeFn=undefined, maxPriority=undefined, ignoreDirective=undefined, previousCompileContext=undefined)angular.js (line 6812)
m()angular....min.js (line 2)
.link/<()app.js (line 262)
jQuery.event.special[orig].handle(event=Object { originalEvent=Event mouseover, type="mouseenter", timeStamp=0, more...})jquery-2.1.1.js (line 4739)
jQuery.event.dispatch(event=Object { originalEvent=Event mouseover, type="mouseenter", timeStamp=0, more...})jquery-2.1.1.js (line 4408)
jQuery.event.add/elemData.handle(e=mouseover clientX=980, clientY=403)jquery-2.1.1.js (line 4095)
App.js line 262 being the link function of the directive supplied above.
For the life of me, I cannot figure out what makes the context undefined in repeated elements that come after the initial limitTo is increased. What I can verify is that removing the limitTo filter causes the behavior to be fine in each element throughout. What I can also verify is that the initial 3 elements do not work if I set the initial limitTo value to 0 and increase it after.
Looking at the source code for limitTo leads me to believe that a new array is constructed each time the amount you're limiting to changes. As to my understanding, this should cause angular to remove all the DOM elements and then change them, but I cannot tell if that change would change affect this in any way.
I know that there's not much to work off of, but I am lost as to how to debug this and could appreciate any help, or if there's any behavior in ng-repeat that I'm not aware of that could explain this.
I would guess that elem isn't added to the dom "fast enough" when you update numPotentialMergesVisible.
Try the following:
myApp.directive('companyProfileTooltip', ['$tooltip', ($tooltip) => {
return {
restrict: 'EA',
scope: { profile: '#companyProfileTooltip' },
link: (scope: ng.IScope, elem) => {
var tooltip = $tooltip({
target: elem,
scope: scope,
templateUrl: "/App/Private/Content/Common/company.profile.html",
tether: {
attachment: 'middle right',
targetAttachment: 'middle left',
offset: '0 10px'
}
});
$timeout(()=> {
$(elem).hover(() => {
tooltip.open();
}, () => {
tooltip.close();
});
},1);
}
};
}]);
This way the hover setup method will be executed after the $scope variable value change has been handled.
Apparently, the issue was that the wrong data was being cached in the angular-tooltip library. The entire request for the template was being parsed, rather than just the content of it. The issue had nothing to do with ngRepeat; the first n-items before the limitTo would fire off a GET request because the template data had not yet populated the templateCache, but later on they would be trying to access the content of the entire request.

Angular Directive Template Update on Data Load

I have a directive whose data is being received via an api call. The directive itself works fine, the problem arises (I believe) because the directive is loaded before the api call finishes. This results in the whole shebang just not working. Instead of my expected output, I just get {{user}}.
My directive looks like this:
app.directive('myDirective', function() {
return {
restrict: 'A',
require: '^ngModel',
scope: {
ngModel: '=',
},
template: '<tbody style="background-color: red;" ng-bind-html="renderHtml(listing_html)"></tbody>',
controller: ['$scope', '$http', '$sce',
function($scope, $http, $sce) {
$scope.listing_html += "<td>{{user.name}}</td>"
$scope.renderHtml = function(html_code) {
return $sce.trustAsHtml(html_code);
};
}
],
link: function(scope, iElement, iAttrs, ctrl) {
scope.$watch('ngModel', function(newVal) {
// This *is* firing after the data arrives, but even then the
// {{user}} object is populated. And the `user in ngModel` doesn't
// run correctly either.
console.log(scope.ngModel);
scope.listing_html = "<tr ng-repeat='user in ngModel'><td>{{user}}</td></tr>"
})
}
};
});
And my html is simply
<table my-directive my-options='{"Name": "name", "Email": "email"}' ng-model='userData'></table>
I've created a plunker with a ton of comments to hopefully help explain the issue.
This question is very similar to this one, with the key distinction of that solution not working. Adding ng-cloak to mine just makes it not display.
It may also be worth noting that I've been using this as reference on the way to construct a directive.
I think you're making this a bit more complicated that it needs to be. If you're going to try to insert dynamic HTML with Angular expressions in them, you need to use the $compile service to compile them first (this hooks up the directives, etc, in that dynamic HTML to Angular). With that said, I don't think you need to do that for what you're trying to accomplish.
Take a look at this updated plunk: http://plnkr.co/edit/RWcwIhlv3dMbjln4dOyb?p=preview
You can use the template in the directive to produce the dynamic changes you need. In my example, I've used ng-repeat to repeat over the users provided to the directive, and also to the options provided to the directive. ng-repeat does the watching, so as soon as the data provided to the directive via ng-model is updated, the ng-repeats reflect those changes.
<tbody style="background-color: red;">
<tr><th ng-repeat="option in myOptions">{{option.name}}</th></tr>
<tr ng-repeat="user in ngModel">
<td ng-repeat="option in myOptions">{{user[option.value]}}</td>
</tr>
</tbody>
The options I defined in the main controller like this.
$scope.tableOptions = [
{"name": "Name", "value": "name"},
{"name": "Email", "value": "email"}
];
You could add other properties to this that are used by the directive, such as display order, etc. You could even remove an item from the options dynamically and that data would then be removed from the output table.
Let me know if this helps, or if I've misunderstood what you were trying to accomplish.
I am not 100% sure, but I believe that ngBindHtml will not help you in this case.
ngBindHtml is for displaying some "normal" HTML, but you want to display some Angular, magic HTML.
For that you need to $compile the HTML to something that is Angular-aware and link the compiled HTML to a scope.
I used the following approach (with apparently good results):
controller: function ($scope, $element, $compile) {
var html = createTmpl(angular.fromJson($scope.myOptions));
$scope.$watch('ngModel', function (newVal) {
var elem = angular.element(html); // Creating element
var linkingFn = $compile(elem); // Compiling element
linkingFn($scope); // Linking element
$element.html(''); // Removing previous content
$element.append(elem); // Inserting new content
// The above is purposedly explicit to highlight what is
// going on. It's moe concise equivalent would be:
//$element.html('').append($compile(html)($scope));
});
where createTmpl() is defined to take into account myOptions and return the appropriate template for creating a table with a header-row (based on the keys of myOptions) and data-rows with the properties defined as myOptions's values:
function createTmpl(options) {
// Construct the header-row
var html = '<tr>';
angular.forEach(options, function (value, key) {
html += '<th>' + key + '</th>';
});
html += '</tr>\n';
// Construct the data-rows
html += '<tr ng-repeat="user in ngModel">';
angular.forEach(options, function (value, key) {
html += '<td>{{user' + value + '}}</td>';
});
html += '</tr>\n';
// Return the template
return html;
}
See, also, this short demo.
Of course, this is for demonstration purposes only and does not handle everything a production-ready app should (e.g. accounting for errors, missing properties, changes in myOptions and whatnot).
UPDATE:
I had very strong competion, so I did a slight modification of the code above in order to support nested properties. E.g. given an object with the following structure:
user = {
name: 'ExpertSystem',
company: {
name: 'ExpertSystem S.A.',
ranking: 100
}
};
we can have the company name displayed in a column of our table, just by defining myOptions like this:
myOptions='{"Company name": "company.name"}

Data from directive not displaying within ng-repeat

I have broken this problem down into it's simplest form. Basically I have a directive that, for the demo, doesn't yet really do anything. I have a div with the directive as an attribute. The values within the div, which come from an object array, are not displayed. If I remove the directive from the div, they are displayed OK. I am clearly missing something really obvious here as I have done this before without any problems.
Here's the Plunk: http://plnkr.co/edit/ZUXD4qW5hXvB7y9RG6sB?p=preview
Script:
app.controller('MainCtrl', function($scope) {
$scope.tooltips = [{"id":1,"warn":true},{"id":2,"warn":false},{"id":3,"warn":true},{"id":4,"warn":true}];
});
app.directive("cmTooltip", function () {
return {
scope: {
cmTooltip: "="
}
};
});
HTML
<div ng-repeat="tip in tooltips" class="titlecell" cm-tooltip="true">
A div element: {{ tip.id }}
</div>
<br><br>
Just to prove it works without the directive:
<div ng-repeat="tip in tooltips" class="titlecell">
A div element: {{ tip.id }}
</div>
There is a hack to make it working in earlier versions of angular by making use of transclusion, like that:
app.directive("cmTooltip", function () {
return {
scope: {
cmTooltip: "="
},
transclude: true,
template : '<div ng-transclude></div>'
};
});
PLNKR
As by Beyers' comment above and below, the behaviour the question is about no longer exists in at least 1.2.5
To be clearer; this has nothing to do with ng-repeat, you can remove it and there still will be no tip ( or tooltips ).
See this question on what the = and other configs mean and what it is doing for you.
Basically for your situation when you use = the scope of the directive will be used in the underlying elements, you no longer have your controller's scope. What this means for you is that there is no {{ tip.id }} or not even tip. Because the directive doesn't supply one.
Here's a plunker that demonstrates what you can do with it.
Basically all i did was
app.directive("cmTooltip", function () {
return {
scope: {
cmTooltip: "="
},
link: function($scope){ // <<
$scope.tip = { id: 1 }; // <<
} // <<
};
});
This creates the tip object on the scope so it has an id.
For your situation you would probably just not use = and look at this question for your other options depending on what you want.
In my opinion this isn't the way to go.
I would use Objects.
JS code:
function tooltip(id,warn){
this.id = id;
this.warn = warn;
}
tooltip.prototype.toString = function toolToString(){
return "I'm a tooltip, my id = "+this.id+" and my warn value = "+this.warn;
}
$scope.tooltips = [new tooltip(1,true),new tooltip(2,false),new tooltip(3,true),new tooltip(4,true)];
HTML:
<div ng-repeat="tip in tooltips" class="titlecell">
A div element: {{ tip.toString() }}
</div>

Categories

Resources