I have directive with this template that includes a repeater:
template: '<div class="btn-group">' +
'{{option.label}}' +
'</div>'
Inside my link function, the following returns an empty array unless I put it inside a $timeout:
var tmp = element.find('.btn');
Link to fiddle: http://jsfiddle.net/dkrotts/XF3RY/1/
Shouldn't these elements be available at link time? If not, is there a cleaner way to handle this situation?
Apparently the ng-repeat happens later, after your link function is run: https://stackoverflow.com/a/13771751/215945
You may want to try using two directives, similar to the tabs and pane directives on the Angular home page example. The radio-buttons directive could define a method (using this) on its controller that the radio-button directive could call when clicked.
For details on how the tabs and pane directives communicate, see https://stackoverflow.com/a/14168699/215945
Related
I have a one-page site that I am building out and this is my first time using Angular on a site. Building it on top of Laravel too for the backend but that is beyond the scope of this question.
I need to be able to open a modal on a main page view which will add a new resource (e.g. a new client) or edit a resource. I want to somehow get the form's html inside the modal body when the $uibModal.open()'s controller is called and set the $scope.modalBody equal to the injected items.modalBody (the only way this works is if I use:
$scope.modalBody = $sce.trustAsHtml(items.modalBody);
The only problem now is that anything inside the HTML body, Angular will not use it's magic and do any data-binding. It is still in the raw form of
{{ object.property }} or since I'm using Laravel and avoiding conflict with the Blade template engine:
<% object.property %>
See screenshot:
screenshot
I have been banging my head against the wall on this one...I have tried putting $scope.$apply() in my directive and my controller, neither of which worked. I have a feeling that is the source of my problem though. I have also tried making the html just a <new-client></new-client> directive and using templateUrl: 'views/clients/add.php' which would be ideal, but the template is not being included inside the <new-client></new-client>.
I'm using ui-bootstrap 0.14.3 and Angular 1.4.8.
Could this be a bug? Or am I doing something wrong? Anyone have a better way of getting a form into my modal? Let me know what code you want to see so I don't clutter this post with unnecessary code blocks.
I have come across a similar issue with using jQuery's AJAX to receive template strings and append it to a server.
So when HTML is added via jQuery, bound html string, etc., angular doesn't know it needs to automagically compile this data.
What you need to do is use the $compile service, to $compile your html and then attach the correct $scope to it:
`$compile('jQuerySelectorReturningHtmlOrAnHTMLStringThatNeedsToBeCompiled')($scope);`
There are multiple examples in Angulars Documentation for $compile that can give you an idea of what is happening. I think by what you have described the same thing is happening here in your situation.
The key is to call this $compile service function after the html has been bound to the page.
EDIT:
There are a few other options based on some comments, that will serve as a viable solution to rendering this content on your view. For example a directive that takes a string attribute representing the HTML string of your desired view.
1. Modify your directive template in the compile step:
You have the ability to modify your template before the directive compiles and binds any attributes to it, to that directives scope:
app.directive('myAwesomeCompileStepDirective', [myAwesomeCompileStepDirectivef]);
function myAwesomeCompileStepDirectiveFn() {
return {
restrict: 'EA',
compile: function compileFn(tAttrs, tElement) {
//Here you can access the attrs that are passed into your directive (aka html string)
tElement.html(tAttrs['stringThatYouWantToReplaceElementWith']);
return function linkFn(scope, element, attrs, controller, transcludeFn) {
//if all you want to do is update the template you really don't have to do anything
//here but I leave it defined anyways.
}
}
}
}
You can view a file I wrote for a npm component which uses this method to modify my directive template before it is compiled on the page & you can also view the codepen for the complete component to see it in action.
2. Use $compile service to call $compile in link function using directive attrs.
In the same way as the aforementioned method, you can instead inject the $compile service, and call the function mentioned above. This provides a bit more work, for you but more flexibility to listen to events and perform scope based functions which is not available in the compile function in option 1.
Here's a plunker example you can see: http://plnkr.co/edit/NQT8oUv9iunz2hD2pf8H
I have a directive that I would like to turn into a web component. I've thought of several ways as to how I can achieve that with AngularJS but am having difficulty with a piece of it. I'm hoping someone can explain my misstep rather than tell me a different way to do it.
Imagine you have a directive component that sets up some shell with css classes maybe some sub components, etc.. but lets the user define the main content of the component. Something like the following:
<my-list items="ctrl.stuff">
<div>List Item: {{ item.name }}</div>
</my-list>
The HTML for the list directive could be something like the following (with OOCSS):
<ul class="mas pam bas border--color-2">
<li ng-repeat="items in item track by item.id" ng-transclude></li>
</ul>
Normally this can be solved in the link function by linking the directives scope to the new content. And it does work for other components. However introducing the ng-repeat seems to break that portion of the control. From what I can tell, the appropriate place might be the compile function but the documentation says the transcludeFn parameter will be deprecated so I'm not sure how to proceed.
I should also note that when using the beta AngularJS, there is either a bug or a new paradigm coming, because this is no longer a problem. It seems like the transcluded content always gets access to the directives scope as well as the outer controllers scope.
I really appreciate any enlightenment on this.
It's by design that content added via ng-transclude will bind with an outer controller scope, not a scope of the current element that ng-transclude is on.
You could solve the problem by copy the ng-transclude's code and modify it a bit to give a correct scope:
.directive('myTransclude', function () {
return {
restrict: 'EAC',
link: function(scope, element, attrs, controllers, transcludeFn) {
transcludeFn(scope, function(nodes) {
element.empty();
element.append(nodes);
});
}
};
});
And replace the ng-transclude with my-transclude in your directive template.
Example Plunker: http://plnkr.co/edit/i7ohGeRiO3m5kfxOehC1?p=preview
I'm creating a fall back image directive that looks like this http://plnkr.co/edit/wxy4Sp2K02iXoQNsvkah
angular.module('directives').directive('myDirective', function() {
return {
restrict: 'C',
link: function(scope, element, attrs) {
console.log('linking');
}
}
});
My directive doesn't work for elements that are added to the DOM by the typeahead.js plugin (https://github.com/twitter/typeahead.js).
<div class='tt-suggestion'>
<div><span class="my-directive">bla</span></div>
</div>
I guess it's because Angular is not informed about the elements that are added by jQuery and hence it doesn't invoke the directive. How do I notify Angular of these changes?
You can use the Angular compile service to do this: http://docs.angularjs.org/api/ng/service/$compile
Basicly it works like this:
document.getElementById("test").innerHTML = $compile("")($scope);
ideally you shouldnt be mixing jquery and angular because they both are based on different philosophy.
jquery-- is event driven i.e. have event listeners which cause changes to model and then the programmer has to code numerous lines to change the view i.e. changing css,text etc
angular-- woo hoo! just change the model which is binded to $scope and :) your view is automatically updated
to automatically react on changing of such events angular has a compiler which studies entire html code before the app is loaded so even if there is a template which you might use later you must enclose it in so that angular compiles this so that all the special angular directive and controller perform as expected even when you remove or add templates to the dom.
here you are using typehead.js and jquery to manually manipulate the view which is against angular philosophy because when you do such maipulation angular compiler wouldnt be aware of it as it runs only when the app is initialized. Thats why before appending you should use $compile to make the angular compiler aware of this template .
in your case i would suggest the typehead present on this url
http://angular-ui.github.io/bootstrap/
I wrote a simple custom directive. The template in this directive includes other directives (e.g. ui-sortable). Because it doesn't always use ui-sortable, I add it in the link phase. Yet it doesn't seem to apply:
link: function ($scope,$element,attrs) {
attrs.$observe('admin', function(value) {
if ($scope.admin) {
$element.find("span").html("true");
$element.find("ul").attr("ui:sortable","sortableOptions");
}
});
}
Full fiddle example is here: http://jsfiddle.net/VjfEf/4/
There are two lists. The first uses ui-sortable directly and drag/drop/sort works, the second uses my custom members directive. The directive does work, it renders, but the addition of ui-sortable in the exact same way as the first has no impact and drag/drop/sort does not.
I am assuming I am not understanding something about the processing phases of custom directives, and either need to add something to my custom directive?
You need to compile the newly added HTML.
$compile($element.contents())($scope);
Fiddle
I've just begun creating Angular directives (I'm new to the framework, as well), but am running into issues wherein a nested directive seems to be ignored. The basis for my directives' code is UI Bootstrap's "tabs" and "pane" directives.
The gist is that I want to be able to compile a list of "components" inside a "layout". Ultimately, there should also be an attribute on each "component" tag that will instruct the layout to render content from some known template location. For now, however, I can't even get the "link" function inside the component directive to fire, even though I've got two components in my template.
Here's a plunk of my situation:
http://plnkr.co/edit/K4n2Mx3kZyvVYGDyJ7t9
You're mis-using ngTransclude by placing it inside of ngRepeat. It's sort of a chicken/egg situation where since there's nothing to repeat over, nothing gets transcluded.
Also, since you're specifying components in HTML, you don't even need ngRepeat in your template.
http://plnkr.co/edit/aYjdd4skbKC3FEM3lCfY?p=preview
template:
'<section class="layout">' +
'<h4>Before all components</h4>' +
'<div ng-transclude></div>' +
'<h4>After all components</h4>' +
'</section>'
When you use ng-repeat, it creates a new scope and it makes you ng-transclude not in the right scope for injecting the transclusion.
So when you remove the ng-repeat, you get the rendered components.
Now, in order to control the layout, you can either add the elements to the controllers like you do with their scope, and then layout them accordingly in the controller:
// inside the controller
this.addComponentElement = function (componentElement) {
componentElements.push(componentElement);
};
// watch for array changes and handle layout
Or, you can use the transclude function in the compile + link combination to get a reference to the transcluded dom and manipulate its layout:
compile:function(telement, tAttrs, transcludeFn){
return function(scope, element, attrs){
transcludeFn(scope, function(transcludedDom){
// layout the transcludedDom
})
}