How and why is <body ng-app> used? How can we assign controllers, directives etc to this nameless module. Also explain how this is related to manually bootstraping the Angular App.
Fiddle
This is actually three separate questions, but I'm happy to tackle each one.
How and why is used?
Angular will not and cannot properly bootstrap the application unless there is an entrance point to that application. According to the documentation, if a parameter is not passed that names the app instance, angular will attempt to auto-bootstrap the application for you by crawling the DOM and using the first ngApp directive instance that it is encountered.
We typically want to place our entrance point on the <body> element to encompass all the potential DOM we need without cluttering it with <head> elements, such as loading scripts and css. That said, if you are auto-bootstrapping your application, the recommended placement is on the HTML element.
How can we assign controllers, directives etc to this nameless module?
Modules, controllers, etc MUST be attached to something in order for Angular to pick them up and interopt with them correctly.
Once the application is bootstrapped, Angular will begin parsing the DOM, looking for directives. If you have an application instance (and you do), your controllers will be automatically be added to that instance. If you look at the bootstrap documentation -> Automatic Initialization, you'll find the following:
Angular initializes automatically upon DOMContentLoaded event or when
the angular.js script is evaluated if at that time document.readyState
is set to 'complete'. At this point Angular looks for the ng-app
directive which designates your application root. If the ng-app
directive is found then Angular will:
load the module associated with the directive.
create the application
injector compile the DOM treating the ng-app directive as the root of
the compilation. This allows you to tell it to treat only a portion of
the DOM as an Angular application.
How the heck is the fiddle working?
This one is actually smoke and mirrors that really shouldn't count :). If you look at the network traffic for JSFiddle, you'll find that AngularJS is actually being loaded. As a result, your interpolation is actually getting automagically bound to the JSFiddle Angular instance, not one that you provide yourself (or in this case didn't), parsed, and subsequently rendered into the DOM as 2.
Per angularjs.org:
"The ngApp directive designates the root element of the application and is typically placed near the root element of the page - e.g. on the or tags."
In other words, ng-app is what makes your html become an Angular application, thus being able to use the {{ }} in your code. These brackets are able to carry out operations, which is why you're able to calcuate 1 + 1. If you were to use variables, however, you would need to attach an ng-controller to a container div and initialize a Controller.
It's the equivalent of ng-app="". However, you need a namespace to attach controllers, directives. etc too. Angular can interpolate {{ 1 + 1 }} without a namespace, but you won't be able to bind anything to the views scope. Interestingly enough ng-app=" " is a namespace you can bind to.
https://jsfiddle.net/n3hygcnd/3/
Related
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?
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.
The problem I am having is I need the root element as an anchor tag, then need a div under the anchor tag. They will both be using the same angular controller which belongs to the same app. The databind on vm.Open works find inside the anchor tag, but it is not working inside the div tag. How can I have the div tag also bootstrap as 'app' with the controller 'ordercontrol'?
Right now I have :
My HTML
<a data-ng-app="app" data-ng-controller="ordercontrol as vm" href="#" data-ng-click="vm.Open = !vm.Open">{{vm.Open}}</a>
<div data-ng-app="app" data-ng-controller="ordercontrol as vm" id="QuickOrderDiv">
<div class="row">
{{vm.Open}} //showing as '{{vm.Open}}' inside page
</div>
</div>
Per AngularJS documentation, only one application can be bootstrapped per HTML document. There is probably not a good reason for you to try and declare and use multiple apps in one document. Depending on your intention, there are a few ways to proceed.
First, remember: assigning a controller to an element (using the ng-controller directive) creates a new scope that inherits from the parent. All elements, directives, and additional controllers used within that scope can use that controller to share functionality. So in essence, a controller is used to centralize models and application logic that is specific to an application and usually to a view (think of any functional task in your application, such as an order form or log in page; those are serviced by controllers).
If you want to reuse a behavior multiple times throughout your application, and that behavior is not specific to a particular view in your application (think of a component, like a date picker, or perhaps a shopping cart status icon/link) you may wish to encapsulate your logic in a directive.
Now, there is some overlap (for example, directives can have a scope or controller of their own), and it may be confusing when to use one or the other. As I mentioned above, controllers are primarily intended to contain business logic for a view in your application. Directives are more orthogonal, encapsulated templates and logic that a) you can easily reuse across the views in your application, and b) extend existing HTML elements with richer mark-up and programmed behaviors. You can use a service (which is a single-instance object) to coordinate data between controllers and directives.
Another common issue new developers struggle with is maintaining or inheriting different states independently, considering that you can only have one ng-view element per document. In that case, consider ui-router.
My wild guess for your case, you may want some sort of QuickOrder directive that binds to a value on OrderController to determine whether or not it should display, and contains additional template mark-up for displaying the order or whatever and the logic to manage it.
I have a directive that takes "previewHTML" as a $scope variable input. I simply want to insert this HTML into a div in my directive template.
I've been working on this problem for a week trying methods from using ng-bind-html, {{}}, $sce, $compile and everything I could think of inbetween; I'm at a loss of why this is so painfully difficult.
The closest I've gotten is to create a scope using $rootScope.$new(true), then attach the variables I need "newScope.value = 'myvalue'", then use $compile to compile the preview HTML and in the cloneAttachFn (which I assume is a callback for when it's finished compiling) I set the previewHTML scope variable, which is included as '< ... ng-bind-html="previewHTML">'
The html without the scope applied comes out fine, but the bindings aren't set. The weird thing is that in the object they are set, but in the outputted HTML they are not; meaning the element has been created, but the bindings haven't been set yet. Unfortunately, Angular won't take a jquery HTML object in ng-bind-html, even though it returns a jquery object in order to maintain the bindings in the HTML.
I'm going to have to use a timeout for now... but does anyone know how to do this very rudimentary thing of including html, that has binding, in a directive template (it has to come from a $scope variable, or at least be generated outside of the directive by the user of the directive)? [is it even async? The documentation is frustratingly unclear]
(Honestly, I'm tens of thousands of lines and a year in and the more I use Angular the more I'd rather use plain JS)
I'm working on a project with AngularJS where I have an issue with 2 scopes conflicting:
- I have controllers defining scopes in different places of my DOM (it works great)
- I just added 3 directives to implement a "help" tooltip function just as chardin.js plugin in jQuery (http://heelhook.github.io/chardin.js/) : one main directive to be able to trigger them all at once, one 'child' directive on the trigger button, and one 'child' directive used to mark each DOM element on which I want the tooltip.
When I have only those 3 directives, it works great, but when I implement my controllers and my directives at the same time, I cannot use my controllers anymore (the scope seems to be completly wrong...)
The structure I have is something like this :
html -> main controller
4 sections -> 4 controllers
and I added 1 directive on the body (the main one), 1 directive to a button somewhere, and 1 directive on various elements of the 4 sections...
I'm aware my explanations are confused, but I really did my best for my first message here!
If you want to see at the code I have written for my directives, here it is:http://plnkr.co/edit/GrwgkH?p=preview
Thanks a lot for your help
S.
Try wrapping your controller's scope models in some object. Primitive values (e.g., number, string, boolean) are hidden in child scopes by its own property with the same name.
Read more in Understanding Scopes on AngularJS github wiki.
I ended up having issues with Primitive values as well. I ended up using sugar.js and Object.extended() to create a $scope.context object that I stored my model data on. This allowed for easy caching of the data as well.