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

时间:2014-01-10 16:04:12

标签: javascript angularjs google-chrome google-chrome-extension

我正在为here编写一个Chrome扩展程序(my library),该扩展使用its UI的角度。它在不使用角度的页面上运行良好,但它会导致具有角度的页面出现问题。例如,在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

奇怪的是,我的扩展实际上是否使用了角度似乎发生了。我需要重现的问题是将manifest.json中的angular包含为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']);

谢谢!

3 个答案:

答案 0 :(得分:24)

问题

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

解决方案

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

可以做什么,将 manual bootstrapping deferred bootstrapping 结合使用 ngNonBindable (以防止你的Angular试图自动引导页面的Angular应用程序。)

同时,您需要从页面的Angular实例中“保护”(即隐藏)应用的根元素。要实现这一点,您可以使用 ngNonBindable 指令将根元素包装在父元素中,这样页面的Angular实例就可以不用它了。

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

  1. 在注入Angular之前,先将window.nameNG_DEFER_BOOTSTRAP!前置 例如。注入一个只包含一行的小脚本(angluar.js之前):

    window.name = 'NG_DEFER_BOOTSTRAP!' + window.name;
    
  2. 将应用的根元素包含在具有属性ng-non-bindable的父元素中:

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

    var appRoot = document.querySelector('#<yourRootElemID');
    angular.bootstrap(appRoot, ['genie-extension']);
    
  4. <击> <子> 精美印刷:我自己没有测试过,但我保证很快就会这样做!


    更新

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

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

    最后,我必须添加第三个要求(请参阅上面更新的解决方案说明),以保护/隐藏内容脚本的Angular应用程序来自页面的Angular实例。
    (即,我使用 this is what it took 指令将根元素包装在父元素中。)

    <强>的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"]
    }
    

    <强> 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);
    });
    

    <强> 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 !');
    

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


    如果有人对此感兴趣, {{3}} 以“Angularize”Genie的灯。

答案 1 :(得分:0)

@ExpertSystem的

Answer非常酷。但是在更多情况下出现相同的错误。如果您在内容脚本部分的manifest.json中定义角度,如下所示:

"content_scripts": [
    {
      "css": ["app.css"],
      "js": [
        "libs/jquery/dist/jquery.min.js",
        "libs/angular/angular.min.js",
        "content.js"
      ],
      "matches": ["\u003Call_urls\u003E"]
    }
  ],

可能遇到此错误(就像我一样)

答案 2 :(得分:0)

@ExpertSystem的答案很好,但它对我不起作用,我认为它可能不是每个场景的解决方案,所以最后我决定编辑angular.js的源文件。

我发现angular.js会在文件末尾监听ready事件(在1.4.6中):

jqLite(document).ready(function() {
  angularInit(document, bootstrap);
});

删除这些行,一切都清楚。

如果您使用Bower,则可以在bower installbower update时添加一个简单的挂钩来执行此操作。

.bowerrc档案中:

{
  "scripts" : {
    "postinstall" : "node ./strip"
  }
}

strip.js

const fs  = require( 'fs' ) ,
filePath  = './bower_components/angular/angular.js' ,
angularJs = fs.readFileSync( filePath , 'utf8' ) ,
deleteStr = /jqLite\(document\)\.ready\(function\(\)[\s\S]+?\}\);/ ,
isDeleted = angularJs.search( deleteStr ) < 0;

if ( !isDeleted ) {
  fs.writeFileSync( filePath , angularJs.replace( deleteStr , '' ) );
  console.log( 'successful disabled auto-bootstrap' );
} else {
  console.log( 'auto-bootstrap already disabled.' );
}