I'm writing a Chrome Extension (here) for my library that uses angular for its UI. It works great on pages that don't use angular, but it causes issues with pages that do have angular. For example, on the Angular docs page:
Uncaught Error: [$injector:modulerr] Failed to instantiate module docsApp due to:
Error: [$injector:nomod] Module 'docsApp' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
http://errors.angularjs.org/1.2.7/$injector/nomod?p0=docsApp
at chrome-extension://cfgbockhpgdlmcdlcbfmflckllmdiljo/angular.js:78:14
at chrome-extension://cfgbockhpgdlmcdlcbfmflckllmdiljo/angular.js:1528:19
at ensure (chrome-extension://cfgbockhpgdlmcdlcbfmflckllmdiljo/angular.js:1453:40)
at module (chrome-extension://cfgbockhpgdlmcdlcbfmflckllmdiljo/angular.js:1526:16)
at chrome-extension://cfgbockhpgdlmcdlcbfmflckllmdiljo/angular.js:3616:24
at Array.forEach (native)
at forEach (chrome-extension://cfgbockhpgdlmcdlcbfmflckllmdiljo/angular.js:302:13)
at loadModules (chrome-extension://cfgbockhpgdlmcdlcbfmflckllmdiljo/angular.js:3610:7)
at createInjector (chrome-extension://cfgbockhpgdlmcdlcbfmflckllmdiljo/angular.js:3550:13)
at doBootstrap (chrome-extension://cfgbockhpgdlmcdlcbfmflckllmdiljo/angular.js:1298:22)
http://errors.angularjs.org/1.2.7/$injector/modulerr?p0=docsApp&p1=Error%3A…xtension%3A%2F%2Fcfgbockhpgdlmcdlcbfmflckllmdiljo%2Fangular.js%3A1298%3A22) angular.js:78
The weird thing is that it seems to happen whether my extension actually uses angular or not. All I need to reproduce the issue is to include angular in my manifest.json as a content_script
and this error is thrown. Any ideas of how I could make this work without messing up an angular site would be greatly appreciated.
Like I said, it doesn't matter whether I actually use angular or not, but this is all I'm doing to use it:
makeWishForAnchors(); // This just loads the global genie object. I don't believe it's related.
var lamp = '<div class="genie-extension"><div ux-lamp lamp-visible="genieVisible" rub-class="visible" local-storage="true"></div></div>';
$('body').append(lamp);
angular.module('genie-extension', ['uxGenie']);
angular.bootstrap($('.genie-extension')[0], ['genie-extension']);
Thanks!
The problem
As soon as Angular is injected, it parses the DOM looking for an element with the
ng-app
directive. If one is found Angular will bootstrap automatically. This becomes a problem when a page uses Angular itself, because (although they have separate JS execution contexts) the page and the content script share the same DOM.The solution
You need to prevent your Angular instance (by "your" I mean the one injected by your extension as a content script) from automatically bootstrapping. Normally you would just omit the
ng-app
directive and you would be good to go, but since you do not have control over the original DOM (nor do you want to break the page's functionality) this is not an option.What you can do it use manual bootstrapping for your Angular app in conjunction with deferred bootstrapping (to prevent your Angular from trying to automatically bootstrap the page's Angular app).
At the same time, you need to "protect" (i.e. hide) your app's root element from the page's Angular instance. To achieve this, you can wrap your root element in a parent element with the ngNonBindable directive, so the page's Angular instance will leave it alone.
Summarizing the steps from the above docs, you need to do the following:
Prepend
window.name
withNG_DEFER_BOOTSTRAP!
prior to injecting your Angular.E.g. inject a tiny script (before
angluar.js
) containing just one line:Wrap your app's root element in a parent element with the attribute
ng-non-bindable
:In your app's main script, manually bootstrap your Angular app:
Fine print: I haven't tested it myself, but I promise to do so soon !UPDATE
The code below is intended as a proof of concept for the approach described above. Basically, it is a demo extension that loads an Angular-powered content-script into any
http:
/https:
page whenever the browser-action button is clicked.The extension takes all necessary precautions in order not to interfere with (or get broken by) the page's own Angular instance (if any).
Finally, I had to add a third requirement (see the updated solution description above) to protect/hide the content-script's Angular app from the page's Angular instance.
(I.e. I wrapped the root element in a parent element with the ngNonBindable directive.)
manifest.json:
background.js:
content.js:
Fine print:
I have conducted some preliminary tests (with both Angular and non-Angular webpages) and everything seems to work as expected. Yet, I have by no means tested this approach thoroughly !
Should anyone be interested, this is what it took to "Angularize" Genie's lamp.
Answer of @ExpertSystem is pretty cool. But there on more case with same error. If you define angular in
manifest.json
in content-script section, like below:You may faced this error (like I am)
@ExpertSystem 's answer is good, but it doesn't work for me, I think it may not a solution for each scenarios, so in the end I decide to edit the source file of angular.js.
I found angular.js will listen a
ready
event at the end of the file (in 1.4.6):Delete these lines, and everything clear.
If you use Bower, you can add a simple hook to do that when
bower install
orbower update
.In
.bowerrc
file:In
strip.js
: