I have a controller like this:
#VariantModalCtrl = ($scope) ->
$scope.upload_variant_image = ->
alert("test")
When I try to call upload_variant_image function using ng-click, it only works when binding to a static DOM (when the DOM loads), I have a link like this:
<%= link_to "test", "" , "ng-click" => "upload_variant_image()" %>
but this element is dynamically added after the DOM is loaded, so ng-click doesn't work.
Update
Just found part of my answer using $compile function:
AngularJS + JQuery : How to get dynamic content working in angularjs
BUT it doesn’t work when I update the DOM like this in Rails:
$(".modal-body").html($compile("<%= j render("/variants/form", :variant => #variant) %>")(scope));
I would warn you that you may not be fully embracing Angular philosophy if you're manipulating the DOM through Angular-external means. Adding links dynamically with AngularJS is as simple as anything else and will probably be much easier and more idiomatic than getting your external library to play nice. Here is a simple example based on your question:
<ul>
<li ng-repeat="item in items">
<a ng-click="upload_variant_image()">{{item.name}}</a>
</li>
</ul>
All you need to do is wire up the scope data appropriately and this will always "just work."
That said, if you are manipulating the DOM, you can cause AngularJS bindings to occur (to, say, ng-click as you desire) by using $compile service. Do consider the above better option, though. :)
Related
I am trying to print to the screen custom html using angular. I am using $sce.trustAsHtml in combination with ng-bind-html to accomplish this. The goal is not only to be able to print this custom html, but that it will retain directives such as ng-click and they will be usuable. Examples I have seen in articles such as follows are promising:
AngularJS render HTML within double curly brace notation
However in my implementation I find that although the html renders correctly including references to ng-click, the directive doesn't seem to work anymore when trying to click on the link I am using it on; here is some sample code:
$scope.htmlExpression = $sce.trustAsHtml("<a ng-click='test();'>Click Me</a>");
$scope.test = function() {
console.log('Hello World!');
}
<div>
<p ng-bind-html="htmlExpression"></p>
</div>
As everything renders fine and nothing appears lost in translation when analyzing the source; I am left feeling as if I have left something out. Any help is appreciated.
Use https://docs.angularjs.org/api/ngSanitize and bind the html. If this does not work, $digest to reboot the digest cycle.
I have a need to use the same directive within a directive, depending on a conditional param. However, when ever i try to do it, it seems to go to an endless loop. As i understand, it tries to pre-load the templates and that causes an endless recursion and at the end, it just throws me the following error:"RangeError: Maximum call stack size exceeded".
I have created an example in fiddle.. as you can see in the example, when the param's value is "1", it creates the error (even when the second level param is valued as "2" so it shouldn't have real recursion issues in the controller/app).
https://jsfiddle.net/qh9nh1gx/
"custom-directive"'s template:
<div>
<div ng-if='info==1'><div custom-directive info='2'></div></div>
<div ng-if='info==2'>DONE,single.</div>
</div>
Thanks
I have found 2 options to deal with the issue, the first one, is exactly what Jju described - creating a new "compiler" method (it can be grabbed from the url he sent).
The second option - always using an additional template for the "recursive" parts of the directive. For example, in my directive, i had a "ng-repeat" part that depending on the items value, it could request to display the directive again. while i used "ng-include" to have the other directive, it worked.
<div ng-repeat="item in items" ng-include="'inline-possibly-recursive-template"'></div>
in that template, you can call the directive again without any issues..
I hope that it will anyone else that will stumble into that issue.
You can look into https://stackoverflow.com/a/19065910/1680674 that describe a common approach to create directive that use himself inside
I'm making an in game UI using awesomium, at some points the game loads up and executes a chunk of javascript which is meant to create arbitrary new UI elements. e.g.
jQuery(document.body).append('<span class="game-status-alert">You Lose!</span>');
That works nicely, the problem comes when I want to create some slightly more advanced UI elements, specifically using angular. For example something like:
function ChatBoxControl($scope) { /* Stuff */ }
jQuery(document.body).append(
'<div ng-controller="ChatBoxControl"><div ng-repeat="line in chat"><span>{{line}}</span></div></div>'
);
Not surprisingly, this does not create a new angular view. It simply adds that html to the document and never binds to the ChatBoxControl.
How can I achieve what I'm trying to do here?
You should $compile dynamically added angular content.
Something like:
jQuery(document.body).append(
$compile(
'<div ng-controller="ChatBoxControl"><div ng-repeat="line in chat"><span>{{line}}</span></div></div>'
)(scope)
);
scope for any element you can get using something like:
var scope = angular.element('#dynamicContent').scope();
Also you should get $compile that can be injected in other controller.
See also: AngularJS + JQuery : How to get dynamic content working in angularjs
You might want to use ng-include combined with ng-repeat.
Here is an simple example: http://plunker.no.de/edit/IxB3wO?live=preview
<div ng-repeat="dom in domList" ng-include="dom"></div>
Parent $scope will keep the list of partials loaded into the view.
And ng-repeat + ng-include will iterate over and display partials according
to the list.
When it is the right timing, you can append the partial into the dom list. e.g.
$scope.domList.push("chatbox.html");
(BTW, putting DOM manipulation into controller is not the angular way.)
I'm new to AngularJS, and I'm dealing with a problem while implementing jQuery custom content scroller into my application.
I need the scroller to update, when I update the content with Angular, for this the scroller has an update method. My problem is, that I don't know where to call it. The markup for the content is the following:
<div class="scroll-list nice-scrollbars">
<ul class="gallery clearfix">
<li class="extra-alt" ng-repeat="item in relatedItems.data">
...
</li>
</ul>
</div>
I was trying to call the update method in success branch of Angular's $http.post:
$scope.relatedItems = $http.post($scope.url, {
'filterType': 'related', 'film': id
}).success(function() {
$(".nice-scrollbars").mCustomScrollbar('update');
});
This didn't work, I think it's because when the success method is called, the view content is not updated yet (I could see it using an alert function, the data appeared after closing the dialog box)
The only way I was able to make the scrollbars work was using the scroller's advanced property for watching the changes in the content (these are the options passed to the scrollbar):
var scrollbarOpts = {
scrollButtons:{
enable:true
},
advanced:{
updateOnContentResize: true
//#TODO: get rid of this, correctly find where to call update method
}
}
This is bad practice, as this options reduces the performance of the whole script.
I would like to know, where is the correct place to call jQuery methods needed to update DOM as needed, or how is such binding to view changes done correctly in AngularJS?
DOM manipulation should be done in a directive (not a controller). The directive should $watch() your model for changes, and the watch callback should perform the jQuery DOM manipulation. Sometimes $evalAsync is needed to run the jQuery code after Angular has updated/modified the DOM (but before the browser renders. Use $timeout if you want do perform some action after the browser renders). See this answer, where I provided a fiddle showing how to use a directive to $watch() a model property, and I used $evalAsync in a mock fetch() function.
For your particular case, I suggest you first try the following, in a directive:
scope.$watch("relatedItems.data", function() {
$(".nice-scrollbars").mCustomScrollbar('update');
});
and if that doesn't work, try this:
scope.$watch("relatedItems.data", function() {
scope.$evalAsync( // you might need to wrap the next line in a function, not sure
$(".nice-scrollbars").mCustomScrollbar('update')
);
});
I'm currently creating a footer with Save/Cancel/Delete, depending on where the user is. Now I'm trying to not show/render the Delete button when it's not required. How to achieve this using a variable from KnockoutJS (observable) as the operator in a ternary?
Current code doesn't work properly but below anyway.
<li>#(Global.ButtonCancel)</li>
<script>
var button = "<li>#(Global.ButtonDelete)</li>";
isEditingProduct ? button : false;
</script>
<li>#(Global.ButtonSave)</li>
The error i keep getting is that "isEditingProduct" is undefined. When i use it inline (outside the script), for a straight <li data-bind="isEditingProduct" ></li> with the other stuff inside it works. It hides the button, but leaves me with a gaping hole in the footer. Which is why I'm trying to get around it by not loading it in for rendering at all if it's not yet needed.
Any help would be appreciated.
Taking a look at your code, I'm confused.
No idea why you feel you need a ternary to hide/unhide elements.
Use the visible: binding.
<li data-bind="visible: isEditingProduct"></li>
isEditingProduct needs to be a property on your view model.
You could use visible or if binding:
<li>#(Global.ButtonCancel)</li>
<li>#(Global.ButtonDelete)</li>
<li>#(Global.ButtonSave)</li>
Read documentation about these bindings:
http://knockoutjs.com/documentation/if-binding.html
http://knockoutjs.com/documentation/visible-binding.html
Not sure where you "isEditingProduct" is defined but you can't simply reference a part of your View Model in JavaScript without fully qualifying it. Instead try:
myViewModel.isEditingProducts = true;
Also, the location of your script block is confusing. It shouldn't be in-lined between <li /> tags. The script will not necessarily execute at that time (as the browser is parsing your markup).