I have an Angular app with a custom element directive (<uber-table>). <uber-table> is supposed to take a collection of objects, render them into a <table> and add some functionality (click row to toggle the underlying object as selected, search box for live filtering, action links on each row with customized click callback(s) for each object). I created a Plunker with the relevant code. Please note that Plunker is giving an error about map (Object [object Object] has no method 'map'), but locally I am not getting any errors.
The post-link function's element parameter is not the <uber-table> element as I expected. Instead it is the template's <div class="uber-table"> element. This is preventing me from extracting data from <uber-table>. What am I doing wrong? Any help will be much appreciated.
Here's a rundown on some of the issues.
First main issue is you want existing content already within the uber-table markup to exist, as well as a new template. Unless told otherwise the existing content ( columns) in this case will be overwritten. In order to include existing content in a directive that has a template, you need to set transclude:true then identify within template where this existing content needs to be placed using ng-transclude on element that will be parent of the content.
Here's demo with transclude fixed
New problems arise now where you are trying to use jQuery to loop over columns and return angular attrs => column.attrs . This throws undefined error.
I haven't tried to unravel this enough to sort out the columns issues yet. They should likely be handled by directive of their own
UPDATE: here's slightly revised error free version using jQuery to parse column count. I'm afraid am still confused a bit by structure of this. I don't see need to use jQuery to parse colunms, this could be converted to directive or pass column definitions into main directive from controller scope
After trying several things and looking at the documentation again, I finally got it working. The solution was to put the post-link function inside the compile function. Also I had to update my isolated scope to use =, set replace to true and set transclude to 'element'.
I updated Plunker if anybody wants to see the changes. The Plunker version isn't working, but since it is working locally, I'm not going to spend too much time on it.
Related
I am new to angular world.I am trying to create an app with reusable nested directives.
Here is a link to my code.
http://plnkr.co/edit/T2CNKQkLEoxjb3TGdp67?p=preview
I have created two containers and bound them to two set of data.Now if I click the buttons it should show me the data bound to parent scope.But it's showing only one set of data.When i debug it seems that both the buttons are bound to one single scope.How do it separate the scope of this two buttons.
I got it working with some help.
I needed to use ng-click instead of onclick()
If anyone is facing the same problem here is the updated code.Working Code
Thanks
Since there is so much commented code in the plunkr, it is a bit confusing at what you are asking. However one big problem is the line:
<container data="{{pie2}}"></container>
The curly braces {{ tell Angular to write out your variable as a string, so you lose any reference to the variable itself. Instead you would want:
<container data="pie2"></container>
And the scope of the directive would become:
scope :{
data:"="
},
That will help you get the reference you are expecting. Let me know if this gets you on the right path. Or if you can simplify/clarify your quesiton further.
I'm trying to create a new directive that selects among several children using an ng-switch. This example is not much more than creating an ng-switch inside a directive, but eventually the directive will have more display sugar and some automatic functions so it seemed that creating a directive was the right solution.
My progress so far is in this plunker:
http://plnkr.co/edit/yeCiIOCQswYJHyTozQUZ
The $compile I'm doing seems to be evaluating the switch, and determining that the value doesn't match any of the when clauses which shouldn't be true. You can see that by inspecting the elements in the rendered picker.
I'm also concerned that calling $compile at this stage seems to have thrown away the item list, so it seems like I'm barking up the wrong tree.
How do I get the transcluded content to re-evaluate within the current state?
Update
I think I was barking up the wrong tree. Mathew's answer got me started in the right direction, so it was a big help.
As far as I can tell trying to construct a directive (ng-switch) inside a directive is a bad idea. In the previous plunker when the compile happened the template was changed permanently. That means if I changed the which parameter it wouldn't update. That's what was smelling funny to me in the first place.
Here is a revised plunker:
http://plnkr.co/edit/WUVgdXjwedxO4356321s
In this case, there's a watch on the 'which' value that refires the transclusion. That function removes the previous entry (if any) and adds the new one. There's a couple added benefits.
First I removed the 'item' directive. There's no reason for it to exist, since I'm just looking at the class. Second, I used $animator to do the list manipulation. That means you can add ng-animate to the picker and get animation effects.
I hope that helps someone else looking at this question.
There were two problems with your code:
1) Your template had which being evaluated so your on was becoming the number 1, instead of the variable you wanted which is "which":
template: '<div><div ng-switch on="which"></div></div>',
2) When you used compile, you needed to pass in the $scope like so:
$compile(sel)($scope);
Here's an updated plnkr for you: http://plnkr.co/edit/Q6ViJBvkLwQRgUKYMfS9?p=preview
I have a simple form with two input boxes of number type. When i try to add max attribute via directive, it seems to ignore it and doesn't validates although attribute is added on the dom element. When i add it inline it works.I referred this question AngularJs can't read dynamically set attributes but that did'nt seemed to help me.New in angularJS and completely clueless.
Any help appreciated.
Edit: Updated fiddle
Demo http://jsfiddle.net/tNUNh/6/
Didn't look furthuer, but one solution you have is to recompile the element everytime you change its max value.
Just change inject $compile (module.directive('type', ['$compile', function ($compile) {) and after attr.$set('max', '100') call $compile(element)(scope).
This doesn't seem right, but I'd need to look further into input directive code to see if it exposes a way to change it without recompiling.
(Note that you don't need to know about datatables for this one..)
I'm creating a directive to handle DataTables. What I'd like to do is have an actions column with two icons - edit and delete. Those icons should response to an ng-click.
Datatables allow you do this by providing a callback for a given column defintion (mRender). This callback returns an html string for that cell value, which is inserted into the DOM instead of the actual cell value.
Checkout this plunker. The two important functions are highlighted:
renderActionIcon - my implementation of the callback mentioned above. It generates the HTML string that I'd like in the cell.
registerNewHtmlWithAngular - function where I ostensibly let angular know about the ng-clicks that I need to register for that column.
What should go in registerNewHtmlWithAngular?
If $compile the html, angular adds the appropriate click event listeners and gives me back an element, but since the Datatables function expects HTML, those registered elements will not be added to the DOM.
Any ideas? Thanks folks!
After a day of keyboard banging:
There does not seem to be a way to $compile into html and have it be usable in the DOM. It has to be an element that's inserted into the DOM, so that event listeners are not lost.
The way I solved the problem in this particular scenario is to just insert the HTML, and run $compile on the new HTML later.
Datatables makes this easy with the fnCreatedRow callback, which, after a row is rendered, passes back a row's Element and lets you do whatever you want with it. In this case I ran $compile over the row and returned it back to Datatables. See the rowCompiler function in the updated plunker.
Because datatables uses innerHTML = mRender(...) to insert an html (so it anyway will convert anything to string and you will lose the results of $compile function. I guess there is no way to do it with mRender without hacks - for instance it is possible to slightly change datatables so it will use $.append instead of innerHTML. But this is not a good practice so in our project we use usual jquery bindings instead of ng-click.
I have the following line in my html:
<div ng-bind-html-unsafe="departmentConfig" class="control-group"></div>
and I use a $resource get to retrieve the HTML, assign the HTML to $scope.departmentConfig, and then the view is updated perfectly. The HTML that is assigned to $scope.departmentConfig contains form elements, with ng-model attributes in it, but when I change the values in these input elements, they don't update the $scope model at all.
This is what I have tried, based on a lot of other internet posts, and it isn't working:
$resource('resources/sources/departments/:mappedName', {
mappedName:departmentKey
}).get(function(departmentConfig) {
// This will call another function that will build a chunk of HTML
$scope.departmentConfig = $scope.buildDepartmentConfigHtml(departmentConfig);
// This is my feeble attempt to access the element, and bootstrap it to include the items in the $scope model.
var $departmentConfigContainer = $('#departmentConfig');
angular.bootstrap($departmentConfigContainer, ['sourcemanager']);
I have even seen some jsFiddle examples where this appears to be working, but mine isn't. Am I calling bootstrap too soon? I also tried putting a $watch on $scope.departmentConfig like this:
$scope.$watch('departmentConfig', function() {
var $departmentConfigContainer = $('#departmentConfig');
angular.bootstrap($departmentConfigContainer);
});
but it didn't work either. I bet there is an easy explanation to this, I just can't seem to get the input elements with ng-model that are loaded after page compile to get bound to the model. Any help is appreciated, this is the last piece of functionality I need to get working on my page. Let me know if you need more information about my configuration as well.
So, simply put, how can I force a section of the DOM to recompile after I know it has been loaded?
UPDATE
Here is a jsfiddle outlining what I would like to do: http://jsfiddle.net/j_snyder/ctyfg/. You will notice that property two and three don't update the model, and I am calling bootstrap on the outer div, hoping that will include those in the model binding. This is the first time I have posted to jsfiddle, please let me know if you can't see the example.
ng-bind-html is made for regular HTML, not compiling new angular elements.
You will have use the $compile service.
Here is how you would edit your current example to work: http://jsfiddle.net/andytjoslin/ctyfg/21/. But this way ends up being bad, because you have to do DOM manipulation in your controller.
You just need to create a directive that will basically do what you wanted ng-bind-html to do: http://jsfiddle.net/andytjoslin/ctyfg/22/