在已使用angular的页面上使用Angular扩展

ken*_*dds 17 javascript google-chrome google-chrome-extension angularjs

我正在为我的库编写一个Chrome扩展程序(此处),它的UI使用angular .它在不使用角度的页面上运行良好,但它会导致具有角度的页面出现问题.例如,在Angular文档页面上:

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
Run Code Online (Sandbox Code Playgroud)

奇怪的是,似乎我的扩展实际上是否使用了角度.我需要重现的问题是在manifest.json中包含angular作为a content_script并抛出此错误.任何关于我如何能够在不弄乱角度站点的情况下完成这项工作的想法将不胜感激.

就像我说的那样,无论我是否实际使用角度都没关系,但这就是我正在使用它的全部内容:

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']);
Run Code Online (Sandbox Code Playgroud)

谢谢!

gka*_*pak 24

问题

注入Angular后,它会解析DOM以查找带有该ng-app指令的元素.如果找到一个Angular将自动引导.当页面使用Angular本身时,这会成为一个问题,因为(尽管它们具有单独的JS执行上下文),页面和内容脚本共享相同的DOM.

解决方案

您需要通过自动引导来阻止您的 Angular实例(通过"您的"我的意思是您的扩展注入的内容脚本).通常你会省略该ng-app指令,你会很高兴,但由于你无法控制原始的DOM(你也不想打破页面的功能),这不是一个选择.

可以做什么使用手动引导你的Angular应用程序与延迟引导(以防止你的Angular尝试自动引导页面的Angular应用程序).

同时,您需要从页面的Angular实例中"保护"(即隐藏)应用程序的根元素.要实现这一点,您可以使用ngNonBindable指令将根元素包装在父元素中,因此页面的Angular实例将使其保持不变.

总结上述文档中的步骤,您需要执行以下操作:

  1. 在注入Angular之前预先window.name添加NG_DEFER_BOOTSTRAP!.
    例如,注入一个angluar.js只包含一行的小脚本(之前):

    window.name = 'NG_DEFER_BOOTSTRAP!' + window.name;
    
    Run Code Online (Sandbox Code Playgroud)
  2. 使用以下属性将应用程序的根元素包装在父元素中ng-non-bindable:

    var wrapper = ...    // <div ng-non-bindable></div>
    wrapper.appendChild(lamp);   // or whatever your root element is
    document.body.appendChild(wrapper);
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在应用程序的主脚本中,手动引导您的Angular应用程序:

    var appRoot = document.querySelector('#<yourRootElemID');
    angular.bootstrap(appRoot, ['genie-extension']);
    
    Run Code Online (Sandbox Code Playgroud)

精细打印:我自己没有测试过,但我保证很快就会这样做!


UPDATE

以下代码旨在作为上述方法的概念证明.基本上,它是一个演示扩展,只要单击浏览器操作按钮,就会将Angular驱动的内容脚本加载到任何http:/ https:页面中.

扩展程序采取所有必要的预防措施,以免干扰(或破坏)页面自己的Angular实例(如果有的话).

最后,我必须添加第三个要求(请参阅上面更新的解决方案描述),以保护/隐藏内容脚本的Angular应用程序从页面的Angular实例.
(即我使用ngNonBindable指令将根元素包装在父元素中.)

manifest.json的:

{
  "manifest_version": 2,
  "name": "Test Extension",
  "version": "0.0",

  "background": {
    "persistent": false,
    "scripts": ["background.js"]
  },

  "browser_action": {
    "default_title": "Test Extension"
//    "default_icon": {
//      "19": "img/icon19.png",
//      "38": "img/icon38.png"
//    },
  },

  "permissions": ["activeTab"]
}
Run Code Online (Sandbox Code Playgroud)

background.js:

// Inject our Angular app, taking care
// not to interfere with page's Angular (if any)
function injectAngular(tabId) {
  // Prevent immediate automatic bootstrapping
  chrome.tabs.executeScript(tabId, {
    code: 'window.name = "NG_DEFER_BOOTSTRAP!" + window.name;'
  }, function () {
    // Inject AngularJS
    chrome.tabs.executeScript(tabId, {
      file: 'angular-1.2.7.min.js'
    }, function () {
      // Inject our app's script
      chrome.tabs.executeScript(tabId, {file: 'content.js'});
    });
  });
}

// Call `injectAngular()` when the user clicks the browser-action button
chrome.browserAction.onClicked.addListener(function (tab) {
  injectAngular(tab.id);
});
Run Code Online (Sandbox Code Playgroud)

content.js:

// Create a non-bindable wrapper for the root element
// to keep the page's Angular instance away
var div = document.createElement('div');
div.dataset.ngNonBindable = '';
div.style.cssText = [
  'background:  rgb(250, 150, 50);',
  'bottom:      0px;',
  'font-weight: bold;',
  'position:    fixed;',
  'text-align:  center;',
  'width:       100%;',
  ''].join('\n');

// Create the app's root element (everything else should go in here)
var appRoot = document.createElement('div');
appRoot.dataset.ngController = 'MyCtrl as ctrl';
appRoot.innerHTML = 'Angular says: {{ctrl.message}}';

// Insert elements into the DOM
document.body.appendChild(div);
div.appendChild(appRoot);

// Angular-specific code goes here (i.e. defining and configuring
// modules, directives, services, filters, etc.)
angular.
  module('myApp', []).
  controller('MyCtrl', function MyCtrl() {
    this.message = 'Hello, isolated world !';
  });

/* Manually bootstrap the Angular app */
window.name = '';   // To allow `bootstrap()` to continue normally
angular.bootstrap(appRoot, ['myApp']);
console.log('Boot and loaded !');
Run Code Online (Sandbox Code Playgroud)

精细打印:
我已经进行了一些初步测试(包括Angular和非Angular网页),一切似乎都按预期工作.然而,我绝不会彻底测试这种方法!


如果有人有兴趣,这就是 "Angularize"Genie的灯所花的.