Controller not loading when using jQuery.append - angularjs

I'm currently using jQuery to display modal windows, the function basically builds some HTML and then does $('#myElement').append(modalHtml);
Inside that modal HTML I have <div ng-controller="MyController">...</div> however when the modal is displayed the controller doesn't seem to get initialized. Is this because it's being added to the DOM outside of angular's scope? If so is there anyway when I run that code I could notify angular to look out for changes?
The breakdown of how it is at the moment is Angular runs loadModal() through an ng-click on an element. loadModal() calls modal(), and modal() builds the html and adds it to the DOM. Modal is in a script of just standalone helper functions.

The modal controller won't be called because you're not parsing or compiling the appended DOM code with Angular. Hence, Angular will not 'notice' this change and as a result it will not run any controllers or directives in the added DOM. The best way to solve this kind of problems is by adding a custom directive.
As you're trying to make a modal work, you could also take a look at existing modal adaptations for Angular, such as the one in AngularUI.

Related

Angular Directive not executing on UI Bootstrap Modal open

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.

pass parameter to Angular directive template function post-post-link, using $scope, GET or whatever

I am trying to write a directive that will format content for modal display (using Bootstrap classes) if given a certain parameter, and as standard view if not. I have this working for a view loaded directly, toggling on a URL param (?modal) available to $routeParams and/or $location.
I want to use this toggle-able template as a "pipe" for other templates. However, the intended content URL will never be the visible URL when used as a modal. I can't get it working when loading the view with $modal.open or ngInclude, because $routeParams/$location has data for the including page, not the included one.
I put this in a Plunker, but because Plunker also doesn't provide the URL param, the modal view isn't available.
Does Angular provide a means to change the template or templateUrl much later in the process? For example, could I use $scope, either from a controller or on the directive, itself?
Clarification: The goal here is to have one template/partial for each component, with that template used either as a standalone or a modal, based on some switch. The _modal and _alone partials in the Plunker convert the component template into the desired state.
$modal.open takes a single object parameter one of the properties of this config parameter is templateUrl
http://angular-ui.github.io/bootstrap/
So you can create the config object and open the modal with any template you need.
Dan Wahlin uses this technique for a dialog service and then in this article goes on to demonstrate a full modal service
http://weblogs.asp.net/dwahlin/building-an-angularjs-modal-service
There were a couple of issues with your code:
First of all the use $routeParams if you don't use ngRoute's $routeProvider.
In your case, it might be easier to use $window.location.search (and compare it against ?modal).
That said, in order to properly display a Bootstrap modal, you need Bootstrap's JS file (which in turn requires jQuery) and you also need to create the modal (e.g. by calling $('.modal').modal()).
Take a look at this modified demo.
For better intergration with Angular's components, you might want to look into UI Bootstrap.

How to wait for controller is rendered in Angular.js

I'm using a custom jquery plugin which transforms table to a tree. And when i'm loading data from ng model i need to recall plugin's constructor. but i can not find that event, when i need to call that.
I've tried to $watch model variable - doen't work well
You should create a new directive instead of doing this in a controller.
By using a directive, you'll be sure that the code located in its link function is applyed after DOM complilation.
It is intended to do what you want.
see http://docs.angularjs.org/guide/directive

Run JavaScript when Angular controller has loaded DOM

I have an Angular template with an ng-repeat. That ng-repeat loads up textareas which I would like to autosize based on their contents. In order to do that, I need to initialize a plugin on each textarea element. The problem is the textareas need to exist in order to run the script on them, and unless I set a timeout I don't know when/where to run that init call. I need to run it once the controller has run and the ng-repeat has loaded up all the textareas into the DOM. I can't seem to find an event that fires when a controller is done doing it's thing.
This seems like such a common thing. What am I missing?
You should put this script in a directive and put it on the ng-repeat. Here is a link to a question that has a pretty good explanation on how you might get started.
<div class="MyTextArea" ng-repeat="text int texts" my-directive>{{text}}</div>

How do I test HTML that has to be rendered with testacular and jasmine

I want to test if a directive binds the correct jquery functions to the DOM and that they work.
For instance i want to test if an element is visible after it was slid up with $.slideUp() or i want to execute a click event on an html input.
It seems that i need to somehow attach the compiled directive to the DOM to make this happen. I've watched the testing directives video on youtube where he says that it is possible but he doesnt mention how you do it.
I'm kinda stuck.
Heres the link to the failing test: http://plnkr.co/edit/PojXf8?p=preview
Couple of things:
First, if you want to use jQuery with Angular, it needs to be loaded before Angular is, so move its script tag before Angular's.
Second, you're right about attaching the directive to the DOM. In a beforeEach call, you can use jQuery to create a host div and then remove that div in an afterEach call.
Here that is in a fork of your plunk.
That said, you only need your div in the DOM to check for visibility, which I'm not even sure you need to do in most cases. For example, you could use Sinon to spy on the slideUp call to make sure it happens and rely on jQuery's tests that it actually works.
And you can always trigger clicks directly just by calling elm.click() using jQuery.

Resources