在已经使用 angular 的页面上在扩展中使用 Angular [英] Using Angular in extension on a page that already uses angular

查看:26
本文介绍了在已经使用 angular 的页面上在扩展中使用 Angular的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在为 此处).github.com/kentcdodds/genie">我的图书馆,它使用 angular 作为其 UI.它在不使用 angular 的页面上效果很好,但它会导致具有 angular 的页面出现问题.例如,在 Angular 文档页面上:

未捕获的错误:[$injector:modulerr] 无法实例化模块 docsApp 由于:错误:[$injector:nomod] 模块docsApp"不可用!您拼错了模块名称或忘记加载它.如果注册模块,请确保将依赖项指定为第二个参数.http://errors.angularjs.org/1.2.7/$injector/nomod?p0=docsApp在 chrome-extension://cfgbockhpgdlmcdlcbfmflckllmdiljo/angular.js:78:14在 chrome-extension://cfgbockhpgdlmcdlcbfmflckllmdiljo/angular.js:1528:19在确保(chrome-extension://cfgbockhpgdlmcdlcbfmflckllmdiljo/angular.js:1453:40)在模块 (chrome-extension://cfgbockhpgdlmcdlcbfmflckllmdiljo/angular.js:1526:16)在 chrome-extension://cfgbockhpgdlmcdlcbfmflckllmdiljo/angular.js:3616:24在 Array.forEach(本机)在 forEach (chrome-extension://cfgbockhpgdlmcdlcbfmflckllmdiljo/angular.js:302:13)在 loadModules (chrome-extension://cfgbockhpgdlmcdlcbfmflckllmdiljo/angular.js:3610:7)在 createInjector (chrome-extension://cfgbockhpgdlmcdlcbfmflckllmdiljo/angular.js:3550:13)在 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:8angular.js%3A227)

奇怪的是,无论我的扩​​展是否使用 angular,似乎都会发生这种情况.我需要重现这个问题,就是将 angular 作为 content_script 包含在我的 manifest.json 中,然后抛出这个错误.任何关于如何在不弄乱角度站点的情况下完成这项工作的想法将不胜感激.

就像我说的,我是否真的使用 angular 并不重要,但这就是我使用它所做的一切:

makeWishForAnchors();//这只是加载全局精灵对象.我不相信这有关系.var lamp = '<div class="genie-extension"><div ux-lamp lamp-visible="genieVisible" rub-class="visible" local-storage="true"></div>

';$('body').append(灯);angular.module('genie-extension', ['uxGenie']);angular.bootstrap($('.genie-extension')[0], ['genie-extension']);

谢谢!

解决方案

问题

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

解决办法

您需要防止您的 Angular 实例(您的"我指的是由您的扩展程序作为内容脚本注入的实例)自动引导.通常,您只需省略 ng-app 指令就可以了,但是由于您无法控制原始 DOM(也不想破坏页面的功能),这不是一个选项.

可以使用手动引导 结合 延迟引导(防止您的 Angular 尝试自动引导页面的 Angular 应用程序).

同时,您需要从页面的 Angular 实例中保护"(即隐藏)您应用的根元素.为此,您可以使用 ngNonBindable 指令,因此页面的 Angular 实例将不理会它.

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

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

    window.name = 'NG_DEFER_BOOTSTRAP!'+ 窗口名称;

  2. 使用属性 ng-non-bindable 将应用的根元素包装在父元素中:

    var wrapper = ...//

    wrapper.appendChild(lamp);//或者任何你的根元素document.body.appendChild(wrapper);

  3. 在您应用的主脚本中,手动引导您的 Angular 应用:

    var appRoot = document.querySelector('#

<打击>细则:我自己还没有测试过,但我保证会尽快测试!

<小时>

更新

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

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

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

ma​​nifest.json:

<代码>{清单版本":2,"name": "测试扩展",版本":0.0",背景": {持久":假,脚本":[background.js"]},浏览器动作":{"default_title": "测试扩展"//"default_icon": {//"19": "img/icon19.png",//"38": "img/icon38.png"//},},权限":[activeTab"]}

background.js:

//注入我们的 Angular 应用程序,注意//不干扰页面的 Angular(如果有)函数注入角(tabId){//防止立即自动引导chrome.tabs.executeScript(tabId, {代码:'window.name = "NG_DEFER_BOOTSTRAP!"+ window.name;'}, 功能 () {//注入 AngularJSchrome.tabs.executeScript(tabId, {文件:'angular-1.2.7.min.js'}, 功能 () {//注入我们应用程序的脚本chrome.tabs.executeScript(tabId, {file: 'content.js'});});});}//当用户点击浏览器操作按钮时调用`injectAngular()`chrome.browserAction.onClicked.addListener(function (tab) {注入角(tab.id);});

content.js:

//为根元素创建一个不可绑定的包装器//保持页面的 Angular 实例远离var div = document.createElement('div');div.dataset.ngNonBindable = '';div.style.cssText = ['背景:rgb(250, 150, 50);','底部:0px;','字体粗细:粗体;','位置:固定;','文本对齐:居中;','宽度:100%;',''].join('\n');//创建应用程序的根元素(其他所有内容都应该放在此处)var appRoot = document.createElement('div');appRoot.dataset.ngController = 'MyCtrl as ctrl';appRoot.innerHTML = 'Angular 说:{{ctrl.message}}';//向 DOM 中插入元素document.body.appendChild(div);div.appendChild(appRoot);//Angular 特定的代码放在这里(即定义和配置//模块、指令、服务、过滤器等)棱角分明.模块('myApp',[]).控制器('MyCtrl',函数MyCtrl(){this.message = '你好,孤立的世界!';});/* 手动引导 Angular 应用程序 */window.name = '';//允许 `bootstrap()` 正常继续angular.bootstrap(appRoot, ['myApp']);console.log('启动并加载!');

<子>精美印刷:
我已经进行了一些初步测试(使用 Angular 和非 Angular 网页),一切似乎都按预期工作.然而,我从来没有彻底测试过这种方法!

<小时>

如果有人感兴趣,这就是角度化"精灵的灯.

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:

  1. Prepend window.name with NG_DEFER_BOOTSTRAP! prior to injecting your Angular.
    E.g. inject a tiny script (before angluar.js) containing just one line:

    window.name = 'NG_DEFER_BOOTSTRAP!' + window.name;
    

  2. Wrap your app's root element in a parent element with the attribute ng-non-bindable:

    var wrapper = ...    // <div ng-non-bindable></div>
    wrapper.appendChild(lamp);   // or whatever your root element is
    document.body.appendChild(wrapper);
    

  3. In your app's main script, manually bootstrap your Angular app:

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

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:

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

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.

这篇关于在已经使用 angular 的页面上在扩展中使用 Angular的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆