Bind an externally loaded DOM element to Angular's scope - angularjs

I'm using Google Maps within an Angular app. As per Google Maps's API, I pass a string to a google.maps function containing HTML, and the library displays it on the map as an InfoView.
I'm adding a button to this view, and I want this view to be bound to a controller in my Angular app, however, even if I pass <div ng-controller="MyController">...</div>, when the Maps library attaches it to the DOM, Angular is not notified.
How can I force the compilation of this element (over which I have very little control: just passing a string)?

For AngularJS to "notice" the directive, it'd need to compile the element, which is not done automatically except when bootstrapping the application. So either the API needs to allow you to pass DOM Element to it (and you need to $compile and link it before you pass it on), or you need to find the element after it is added to DOM and then $compile and link it. AFAIK, if you cannot do either of those, then what you're asking is impossible.
If you manage to get hold of the element, compiling it is as simple as calling
$compile(element)($scope);
where $scope is the scope you want to link it to (possibly even $rootScope).

Related

Angular directive DOM manipulation dependent on scope variables

I have read that DOM manipulation in angular should be done in the compile function of a directive, and not in the pre-link/post-link/controller. The compile function does not have access to the scope.
My problem is that I want to do DOM manipulation that is dependent on scope variables. For example, I have a list that I am passing into the directive. Within the directive, I am creating a custom select with the list items within it. Where is the right place to manipulate the DOM in this case?
Note that I am not using ng-repeat - I have found it very slow when the list becomes large.
I don't know where you have read "DOM manipulation in angular should be done in the compile function of a directive". That contradicts the advice of the AngularJS team.
Creating a Directive that Manipulates the DOM
Directives that want to modify the DOM typically use the link option to register DOM listeners as well as update the DOM.
-- AngularJS Developer Guide - Directives - DOM Manipulation
The built-in directives, ng-repeat, ng-if, ng-when, etc. all do their DOM manipulation in the link function.
compile
The compile function deals with transforming the template DOM. Since most directives do not do template transformation, it is not used often.
-- AngularJS $compile Service API Reference -- compile

How can an Angular directive have a 'native' parent state when it is compiled elsewhere?

I use ui-router to create routes with multiple HTML pages.
I have custom "target" directives within these pages that use $state.current in various ways.
But I also have other custom "lookup" directives in other states which load these HTML "templates", find these "target" directives, compile them and insert them into the DOM.
The problem I am having is that when the HTML content is compiled in these second directives, $state.current obviously refers to whatever state the application is in when it is compiled, whereas I would like the directive to compile as if it was in its "native" state.
Is there any (easy-ish!) way to get a reference to the target directive's "native" state? i.e. the state connected to the .HTML file which the directive is in? Is there a method (angular, jquery, native js or anything else) to get from the directive to the HTML template file? Then I could do a reverse lookup on the state objects. element.ownerDocument can get the URL of the current state, but not the HTML file of the template.
Alternatively, if the second directive had a reference to the "target" state, how should I modify the "target" directive so that it takes a state reference when it is compiled in both scenarios? Something in the compile function perhaps?? - but the docs don't seem to cover this kind of use case... I could do with a pointer in the right direction.
I hope that all makes sense. I have looked around for similar answers, but I guess this is an unusual use case? I'll knock up a Plunker shortly to help illustrate...
When you compile HTML using $compile, you pass in a scope object. Instead of using $state.current within the directive, could you attach the "state" you need to the scope object that you're passing in?

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.

Access Angular Controller Defined Element

Is there a way to access the div which is in the controller div or the controller defined div without defining them with and class or id JUST using the $scope.
<div ng-controller="gridController">
<div></div> // < -- I want to access this element
</div>
To be a bit more specific does angular saves and gives access to the element DOM info which the ng-controller was called ?
A controller has no concept of the DOM, and it should stay that way or you run the very likely risk of writing untestable code. This is a part of the separation of concerns in the angular framework. A controller can be bound to multiple different elements or even to the controller function of different directives and there would be know way to tell them apart.
If you are attempting to do anything to the DOM you should be using a directive.
Given more information about what you want to accomplish with the element in question more guidance to reach your goal could be given.
You can access the element the controller is defined on by injecting the angular variable $element into your controller
angular.module('myApp')
.controller('SomeCtrl',['$scope', '$element', function($scope, $element) {
$scope.buttonClick = function () {
$element.find('div').css('background-color', '#f00');
}
}]);
If jQuery is included in your project, $element will be a jQuery object of the element your controller is defined on (the outer div in your example). You can use standard jQuery directives to access its sub elements. Accessing an unnamed child div would require you to know its position, or use some other criteria to target it.
If jQuery is not installed, angular includes jqlite which is a subset of jQuery allowing you to use most jQuery selectors to target elements, but lacking most of the other manipulation features.
However, it is generally considered bad practice to access, and especially manipulate the DOM from within a controller. Because of the way jQuery binds variables to html, if you make changes to the DOM from certain functions, angular may not pick these changes up and overwrite them when it next does a binding cycle. To avoid this, you should do most of your DOM manipulation from inside an angular directive. Sometimes however, its just easier to do it from the controller... If you are going to do this, you should learn about $apply and $digest.

Is it possible to know the name of controller which responsible for some HTML fragment from browser?

i'm beginning angular developer and i have application with some controllers.
Is it possible to find out what is the Name the controller of some html if there is no "ng-controller" statement?
Like in case this is custom directive or the html loaded by ui-router?
p.s. i know about batarang but i dont see the name of the controller, only the scope
Thanks
If you can get hold of the DOM element you are interested in, you can run in the console:
angular.element(...).controller().constructor
This will print the constructor function (i.e. the function you have registered as controller) in the console; clicking on it (at least on Firebug) gets you to the source.
.controller() is an addon to the jQuery API from Angular. To get hold of the element in interest, the simplest thing is document.getElementById(). You can even use Firebug (or equivalent) to place an id in that element and then use getElementById().
Also for Firefox/Firebug let me propose the excellent angscope plugin.

Resources