I am a rookie in angularJS learning about directives (and struggling a lot :)).
I am trying to understand a piece of angularJS code in the plunker
by user tasseKATT for the stack overflow question regarding angular-ui-bootstrap.
I was hoping if anyone can explain this code fragment in more detail.
Specifically
How parsing and compilation happens in directives
How angular knows when to recompile directives ($watch - perhaps, if so how).
I checked the documentation for $parse but dont see any explanation on the service taking a function. What piece of information am I missing.
Also what is the
(value || '').toString();
used for.
What are the properties compileHTML. Where can I see the documentation for the compile function explained in more detail than the one provided by AJS.
What is $$addBindingClass(tElement) and $$addBindingInfo.
Explain the function ngBindHtmlWatchAction
what is $sce.
The fragment from the directive is below
app.directive('compileHtml', ['$sce', '$parse', '$compile',
function($sce, $parse, $compile) {
return {
restrict: 'A',
compile: function ngBindHtmlCompile(tElement, tAttrs) {
var ngBindHtmlGetter = $parse(tAttrs.compileHtml);
var ngBindHtmlWatch = $parse(tAttrs.compileHtml, function getStringValue(value) {
return (value || '').toString();
});
$compile.$$addBindingClass(tElement);
return function ngBindHtmlLink(scope, element, attr) {
$compile.$$addBindingInfo(element, attr.compileHtml);
scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
element.html($sce.trustAsHtml(ngBindHtmlGetter(scope)) || '');
$compile(element.contents())(scope);
});
};
}
};
}
]);
Disclaimer : First thing buddy, you are a rookie in angular and you are trying to understand directives using a very complicated directive. Maybe its better if you start with something a little less complicated :)
I will try to answer your many questions:
How parsing and compilation happens in directives:
Angular knows which all directives are present, so when you write a name which matches one of the directive it compiles the html and link the directive.(I am trying to find a good blog for you to read on these cycles of directive, maybe this post will help)
How angular knows when to recompile directives ($watch - perhaps, if so how): angular doesnot recompile directives, a directive is only compiled once when angular encounters it for the first time.
$watch is basically put on a variable, so whenever angularjs is applying the changes of the scope, it looks for that variable if it is changed.
$parse
It is a way of adding angular to html. So if you directly append html to an element, it does not have angular like features. So we get the html, compile/parse it and then append to the element. Parsing is basically getting all the variables in the html and applying two way binding on those, plus replacing them with their values in controller.
Read this post.
Remember that compile/parse are always done against a scope variable, because variables defined inside the html are properties of the scope variables.
What is this (value || '').toString();
This is a common coding practce of js developers.
This basically amounts to :
(value ? value : '').toString();
Why dont we use value directly like this
value.toString();
Because if the value is undefined or null, it does not have a toString function and it will throw an error.
So the coder is trying to check if the value is there, then convert the value to string otherwise, just put empty string(converting to string results empty string).
what is $sce?
sce is a service, that comes in file angular-sanitize.js which is a part of angular bundle. When somebody tries to modify html to insert his link(malicious activity we call it), angular does not allow and treats his html as simple text.
But what if you want to add html, you pass your html to $sce service(injected in controller/directive etc) and the output is the html which you can insert into the view(this will come as html).
$$addBindingClass
This is just to add "ng-binding" class to the element, so that you can see which element has a model created by angularjs.
$$addBindingInfo
This is to add information to the element for debugging purpose. You can select the element in the inspector and run the following statement in console to get the scope information.
angular.element($0).scope(); //$0 means selected element
Here is a link that explains this thing better.
I'm trying to create a popover and load content into it directly from a controller.
I can succesfully bind flag into the tooltip using a directive from this answer, but the popover keeps showing the initial value of flag, even if I change flag's value with the second button.
The point is that I wish the content of the popover to change
dinamically along with the variable in the controller.
How can I make the trick?
Here's the directive:
var app = angular.module('plunker', []);
app.directive('popover', function($compile, $timeout){
return {
restrict: 'A',
link:function(scope, el, attrs){
var content = attrs.content;
var settings = scope.$eval(attrs.popover);
var elm = angular.element('<div />');
elm.append(attrs.content);
$compile(elm)(scope);
$timeout(function() {
el.removeAttr('popover').attr('data-content',elm.html());
el.popover(settings);
});
}
}
Here comes the plunker
2ND STEP
I wish I can set the container of the popover to be the <body> using that directive, so I can make the popover width 1/3 of the page.
Regarding your first problem with updating the body value - you are not binding to the scope variable, but are reading the value assigned to the element attribute in var content = attrs.content;
Since you are already using bootstrap popover, take a look at angular-ui bootstrap, who have implemented a popover directive. They support custom templates using the popover-template attribute.
The problem is that the final html of the popover is not the one you compiled, just a copy of it.
You can instead set the content option to the compiled element itself:
// remove the attribute so the popover will look at the content option value
el.removeAttr('data-content');
settings.content = elm;
el.popover(settings);
See this plunker.
The real problem here is that the popover-template directive using a template which route is stored as a string in a $scope variable (as suggested by #kpentchev in the comments of the other answer) is available only with the angular-bootstrap version 0.13.0.
That version is not available in npm yet, so after I manually updated it using bower I was able to use correctly my custom popover.
I have an iframe nested in my app that has an angular project in it.
How can I get access to the scope inside that?
The magic is in <iframe>.contentWindow
When going from the parent to the child iframe (which is what you do when debugging), you'll want get the child window object, and use its definition of angular.
var w = $('iframe')[0].contentWindow;
w.angular.element(<selector>).scope();
Note that this does work with chrome's development shortcuts, so you can do
w.angular.element($0).scope()
to get the scope of the currently selected element.
EDIT:
You can also directly assign the parent window's angular object to the child's definition (though I wouldn't recommend it if the parent is using angular itself)
window.angular = w.angular
This makes the parent effectively transparent, so you can use angular as normal
angular.element($0).scope()
and it has the advantage of fixing angular console tools like Batarang
Note: the iframe must be from the same origin in order for this to work. For instance, trying this in plunker won't work as the iframe is generated dynamically, and has a source of "about:blank".
Select iframe from debug options and do something like below:
jQuery('#my_element_id').scope();
To do this with javascript, I have setup a plunker link: http://plnkr.co/edit/whVo7wanhmcUfC7Nem9J?p=preview
Below js method access the scope of a element and passes to the parent method:
var passScopeToParent = function() {
var scope = angular.element(jQuery("#myListItem")).scope();
parent.change(scope);
}
parent widnow method accesses scope and changes its expressions:
var change = function(scope) {
scope.$apply(function() {
scope.department = 'Superhero';
});
}
You can tweak this link to access the scope an element within iframe from parent window and print it in the console window.
Late to the party. What I do is put a button in the directive and attach it to a function that prints the scope.
<button ng-click="echoScope()">Echo Scope</button>
$scope.echoScope = function() { console.debug($scope); };
I want to modify web pages' behavior using angularjs and greasemonkey. I want to know, what's the best way to do it? Should I use jquery to inject attributes like "ng-*" to DOM elements before I can write some angular code? Or can I solely stick to angularjs?
Thanks.
There's a general answer about dynamically modifying AngularJS content in the DOM from JavaScript code here:
AngularJS + JQuery : How to get dynamic content working in angularjs
To sum up, when you put ng-* attributes into the DOM from JavaScript code, they won't automatically get hooked up; but AngularJS provides the $compile function for hooking up new HTML content with AngularJS attributes from JavaScript.
So what does this mean when it comes to Greasemonkey/Userscript?
For the purposes of this I'm assuming that your Greasemonkey script is modifying an existing page that already uses AngularJS, and the AngularJS content you want to add uses some of the variables or functions in AngularJS scopes already on that page.
For those purposes:
Get a reference to $compile from AngularJS' dynamic injection system
Get a reference to the AngularJS scope that you want your HTML code to be connected to
Put your HTML code with ng-* attributes in a string and call $compile on it and the scope.
Take the result of that and put it into the page using the usual jQuery-style ways.
To illustrate, here's a little script for CERN's Particle Clicker game, which adds a stat under the 'workers' section.
$(function () { // Once the page is done loading...
// Using jQuery, get the parts of the page we want to add the AngularJS content to
var mediaList = $('ul.media-list');
var medias = $('li.media', mediaList);
// A string with a fragment of HTML with AngularJS attributes that we want to add.
// w is an existing object in the AngularJS scope of the
// <li class="media"> tags that has properties rate and cost.
var content = '<p>dps/MJTN = <span ng-bind="w.rate / w.cost * 1000000 | number:2"></span></p>';
// Invoke a function through the injector so it gets access to $compile **
angular.element(document).injector().invoke(function($compile) {
angular.forEach(medias, function(media) {
// Get the AngularJS scope we want our fragment to see
var scope = angular.element(media).scope();
// Pass our fragment content to $compile,
// and call the function that $compile returns with the scope.
var compiledContent = $compile(content)(scope);
// Put the output of the compilation in to the page using jQuery
$('p', media).after(compiledContent);
});
});
});
** NB: Like any AngularJS function that uses its dependency injection,
.invoke uses the parameter names of the function you pass to it
determine what to inject, and this will break if you're using a minifier that changes the parameter names.
To avoid this you can replace
.invoke(function($compile) { ... });
with the form
.invoke(['$compile', function($compile) { ... }]);
which won't break if the minifier changes the parameter name to something other than $compile.
I would like to access my $scope variable in Chrome's JavaScript console. How do I do that?
I can neither see $scope nor the name of my module myapp in the console as variables.
Pick an element in the HTML panel of the developer tools and type this in the console:
angular.element($0).scope()
In WebKit and Firefox, $0 is a reference to the selected DOM node in the elements tab, so by doing this you get the selected DOM node scope printed out in the console.
You can also target the scope by element ID, like so:
angular.element(document.getElementById('yourElementId')).scope()
Addons/Extensions
There are some very useful Chrome extensions that you might want to check out:
Batarang. This has been around for a while.
ng-inspector. This is the newest one, and as the name suggests, it allows you to inspect your application's scopes.
Playing with jsFiddle
When working with jsfiddle you can open the fiddle in show mode by adding /show at the end of the URL. When running like this you have access to the angular global. You can try it here:
http://jsfiddle.net/jaimem/Yatbt/show
jQuery Lite
If you load jQuery before AngularJS, angular.element can be passed a jQuery selector. So you could inspect the scope of a controller with
angular.element('[ng-controller=ctrl]').scope()
Of a button
angular.element('button:eq(1)').scope()
... and so on.
You might actually want to use a global function to make it easier:
window.SC = function(selector){
return angular.element(selector).scope();
};
Now you could do this
SC('button:eq(10)')
SC('button:eq(10)').row // -> value of scope.row
Check here: http://jsfiddle.net/jaimem/DvRaR/1/show/
To improve on jm's answer...
// Access whole scope
angular.element(myDomElement).scope();
// Access and change variable in scope
angular.element(myDomElement).scope().myVar = 5;
angular.element(myDomElement).scope().myArray.push(newItem);
// Update page to reflect changed variables
angular.element(myDomElement).scope().$apply();
Or if you're using jQuery, this does the same thing...
$('#elementId').scope();
$('#elementId').scope().$apply();
Another easy way to access a DOM element from the console (as jm mentioned) is to click on it in the 'elements' tab, and it automatically gets stored as $0.
angular.element($0).scope();
If you have installed Batarang
Then you can just write:
$scope
when you have the element selected in the elements view in chrome.
Ref - https://github.com/angular/angularjs-batarang#console
This is a way of getting at scope without Batarang, you can do:
var scope = angular.element('#selectorId').scope();
Or if you want to find your scope by controller name, do this:
var scope = angular.element('[ng-controller=myController]').scope();
After you make changes to your model, you'll need to apply the changes to the DOM by calling:
scope.$apply();
Somewhere in your controller (often the last line is a good place), put
console.log($scope);
If you want to see an inner/implicit scope, say inside an ng-repeat, something like this will work.
<li ng-repeat="item in items">
...
<a ng-click="showScope($event)">show scope</a>
</li>
Then in your controller
function MyCtrl($scope) {
...
$scope.showScope = function(e) {
console.log(angular.element(e.srcElement).scope());
}
}
Note that above we define the showScope() function in the parent scope, but that's okay... the child/inner/implicit scope can access that function, which then prints out the scope based on the event, and hence the scope associated with the element that fired the event.
#jm-'s suggestion also works, but I don't think it works inside a jsFiddle. I get this error on jsFiddle inside Chrome:
> angular.element($0).scope()
ReferenceError: angular is not defined
One caveat to many of these answers: if you alias your controller your scope objects will be in an object within the returned object from scope().
For example, if your controller directive is created like so:
<div ng-controller="FormController as frm">
then to access a startDate property of your controller, you would call angular.element($0).scope().frm.startDate
To add and enhance the other answers, in the console, enter $($0) to get the element. If it's an Angularjs application, a jQuery lite version is loaded by default.
If you are not using jQuery, you can use angular.element($0) as in:
angular.element($0).scope()
To check if you have jQuery and the version, run this command in the console:
$.fn.jquery
If you have inspected an element, the currently selected element is available via the command line API reference $0. Both Firebug and Chrome have this reference.
However, the Chrome developer tools will make available the last five elements (or heap objects) selected through the properties named $0, $1, $2, $3, $4 using these references. The most recently selected element or object can be referenced as $0, the second most recent as $1 and so on.
Here is the Command Line API reference for Firebug that lists it's references.
$($0).scope() will return the scope associated with the element. You can see its properties right away.
Some other things that you can use are:
View an elements parent scope:
$($0).scope().$parent.
You can chain this too:
$($0).scope().$parent.$parent
You can look at the root scope:
$($0).scope().$root
If you highlighted a directive with isolate scope, you can look at it with:
$($0).isolateScope()
See Tips and Tricks for Debugging Unfamiliar Angularjs Code for more details and examples.
I agree the best is Batarang with it's $scope after selecting an object (it's the same as angular.element($0).scope() or even shorter with jQuery: $($0).scope() (my favorite))
Also, if like me you have you main scope on the body element, a $('body').scope() works fine.
Just assign $scope as a global variable. Problem solved.
app.controller('myCtrl', ['$scope', '$http', function($scope, $http) {
window.$scope = $scope;
}
We actually need $scope more often in development than in production.
Mentioned already by #JasonGoemaat but adding it as a suitable answer to this question.
Inspect the element, then use this in the console
s = $($0).scope()
// `s` is the scope object if it exists
You can first select an element from the DOM that's within the scope you want to inspect:
Then you can view the scope object by querying the following in the console:
angular.element($0).scope()
You can query any property on the scope, e.g.:
angular.element($0).scope().widgets
Or you can inspect the controller attached to the scope:
angular.element($0).scope().$myControllerName
(Another option that can work is to put a breakpoint in your code. If the $scope is currently in the current 'plain old JavaScript' scope, then you can inspect the value of $scope in the console.)
I've used angular.element($(".ng-scope")).scope(); in the past and it works great. Only good if you have only one app scope on the page, or you can do something like:
angular.element($("div[ng-controller=controllerName]")).scope(); or angular.element(document.getElementsByClassName("ng-scope")).scope();
I usually use jQuery data() function for that:
$($0).data().$scope
The $0 is currently selected item in chrome DOM inspector.
$1, $2 .. and so on are previously selected items.
Say you want to access the scope of the element like
<div ng-controller="hw"></div>
You could use the following in the console:
angular.element(document.querySelector('[ng-controller=hw]')).scope();
This will give you the scope at that element.
At the Chrome's console :
1. Select the **Elements** tab
2. Select the element of your angular's scope. For instance, click on an element <ui-view>, or <div>, or etc.
3. Type the command **angular.element($0).scope()** with following variable in the angular's scope
Example
angular.element($0).scope().a
angular.element($0).scope().b
Chrome's console
This requires jQuery to be installed as well, but works perfectly for a dev environment. It looks through each element to get the instances of the scopes then returns them labelled with there controller names. Its also removing any property start with a $ which is what angularjs generally uses for its configuration.
let controllers = (extensive = false) => {
let result = {};
$('*').each((i, e) => {
let scope = angular.element(e).scope();
if(Object.prototype.toString.call(scope) === '[object Object]' && e.hasAttribute('ng-controller')) {
let slimScope = {};
for(let key in scope) {
if(key.indexOf('$') !== 0 && key !== 'constructor' || extensive) {
slimScope[key] = scope[key];
}
}
result[$(e).attr('ng-controller')] = slimScope;
}
});
return result;
}
in angular we get jquery element by angular.element()....
lets c...
angular.element().scope();
example:
<div id=""></div>
For only debugging purposes I put this to the start of the controller.
window.scope = $scope;
$scope.today = new Date();
And this is how I use it.
then delete it when I am done debugging.
Also, we can access the scope by name of HTML element like this:
angular.element(document.getElementsByName('onboardingForm')[0]).scope()
Just define a JavaScript variable outside the scope and assign it to your scope in your controller:
var myScope;
...
app.controller('myController', function ($scope,log) {
myScope = $scope;
...
That's it! It should work in all browsers (tested at least in Chrome and Mozilla).
It is working, and I'm using this method.