I'm currently trying to integrate some angular into our MVC application. This is requiring some slightly more in-depth knowledge of how angular compiles the DOM, but it doesn't seem unachievable.
Here's a link to the CodePen
Essentially, I have a bunch of code (that I can't touch) which controls the page being loaded into DOM. This uses JQuery.
What I have is an ng-include that loads in a template, which gives me my 'angularised' DOM. Because this element is loaded in via AJAX, I'm having to manually $compile it when its inserted.
This is works okay until I switch to a different view, and then back again. The controller is instantiated again (as expected), but the previous one is still responding to the event.
I think I need to $destroy the old controller and all its child scopes, but how do I obtain them?
What you were missing is destroying the event listener $scope.$on(notifyRefreshEvent, ... and you do that by doing something like this. Here's your EventService snippet which solves this issue:
app.service('EventService', function($rootScope){
var notifyRefreshEvent = "contact::refresh";
var eventListenerDestroy;
return {
...
}
...
function onContactRefresh($scope, handler) {
eventListenerDestroy = $scope.$on(notifyRefreshEvent, function (e, data) {
eventListenerDestroy(); // this guy destroys it
handler(data);
});
}
});
Also, here's the forked codepen solution
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.
Using AngularJS and UI Bootstrap, I want to dynamically add alerts to DOM. But if I dynamically add an <alert> element to DOM, it's not compiled automatically. I tried to use $compile but it doesn't seem to understand tag names not present in core AngularJS. How can I achieve this? Is it even the right way to "manually" add elements to DOM in services?
See Plunker. The alert in #hardcodedalert is compiled and shown correctly but the contents of #dynamicalert are not being compiled.
Edit:
I'd later want to have alerts shown on different context and locations on my web page and that's why I created a constructor function for the alerts, to have a new instance in every controller which needs alerts. And just for curiosity's sake, I was wondering if it's possible to add the <alert> tags dynamically instead of including them in html.
I've updated your plunker to do what you're trying to do the "angular way".
There are a few problems with what you were trying to do. The biggest of which was DOM manipulation from within you controller. I see you were trying to offset that by handling part of it in the service, but you were still referencing the DOM in your controller when you were using JQuery to select that element.
All in all, your directives weren't compiling because you're still developing in a very JQuery-centric fashion. As a rule of thumb you should let directives handle the adding and removing of DOM elements for you. This handles all of the directive compiling and processing for you. If you add things manually the way you were trying, you will have to use the $compile provider to compile them and run them against a scope... it will also be a testing and maintenance nightmare.
Another note: I'm not sure if you meant to have a service that returned an object with a constructor on it, so I made it just an object. Something to note is that services are created and managed in a singleton fashion, so every instance of that $alertService you pass in to any controller will be the same. It's an interesting way to share data, although $rootScope is recommended for that in most cases.
Here is the code:
app.factory('alertservice', [function() {
function Alert() {
this.alerts = [];
this.addAlert = function(alert) {
this.alerts.push(alert);
};
}
return {
Alert: Alert
};
}]);
app.controller('MainCtrl', function($scope, alertservice) {
var myAlert = new alertservice.Alert();
$scope.alerts = myAlert.alerts;
$scope.add = function() {
myAlert.addAlert({"text": "bar"});
};
});
Here are the important parts of the updated markup:
<body ng-controller="MainCtrl">
<div id="dynamicalert">
<alert ng-repeat="alert in alerts">{{alert.text}}</alert>
</div>
<button ng-click="add()">Add more alerts...</button>
</body>
EDIT: updated to reflect your request
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.
I've been using directives in AngularJS which build a HTML element with data fetched from the $scope of the controller. I have my controller set a $scope.ready=true variable when it has fetched it's JSON data from the server. This way the directive won't have to build the page over and over each time data is fetched.
Here is the order of events that occur:
The controller page loads a route and fires the controller function.
The page scans the directives and this particular directive is fired.
The directive builds the element and evaluates its expressions and goes forward, but when the directive link function is fired, it waits for the controller to be "ready".
When ready, an inner function is fired which then continues building the partial.
This works, but the code is messy. My question is that is there an easier way to do this? Can I abstract my code so that it gets fired after my controller fires an event? Instead of having to make this onReady inner method.
Here's what it looks like (its works, but it's messy hard to test):
angular.module('App', []).directive('someDirective',function() {
return {
link : function($scope, element, attrs) {
var onReady = function() {
//now lets do the normal stuff
};
var readyKey = 'ready';
if($scope[readyKey] != true) {
$scope.$watch(readyKey, function() {
if($scope[readyKey] == true) {
onReady();
}
});
}
else {
onReady();
}
}
};
});
You could use $scope.$emit in your controller and $rootScope.on("bradcastEventName",...); in your directive. The good point is that directive is decoupled and you can pull it out from project any time. You can reuse same pattern for all directives and other "running" components of your app to respond to this event.
There are two issues that I have discovered:
Having any XHR requests fire in the background will not prevent the template from loading.
There is a difference between having the data be applied to the $scope variable and actually having that data be applied to the bindings of the page (when the $scope is digested). So if you set your data to the scope and then fire an event to inform the partial that the scope is ready then this won't ensure that the data binding for that partial is ready.
So to get around this, then the best solution is to:
Use this plugin to manage the event handling between the controller and any directives below:
https://github.com/yearofmoo/AngularJS-Scope.onReady
Do not put any data into your directive template HTML that you expect the JavaScript function to pickup and use. So if for example you have a link that looks like this:
<a data-user-id="{{ user_id }}" href="/path/to/:user_id/page">My Page</a>
Then the problem is that the directive will have to prepare the :user_id value from the data-user-id attribute, get the href value and replace the data. This means that the directive will have to continuously check the data-user-id attribute to see if it's there (by checking the attrs hash every few moments).
Instead, place a different scope variable directly into the URL
My Page
And then place this in your directive:
$scope.whenReady(function() {
$scope.directive_user_id = $scope.user_id;
});