You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将jQuery Web应用通过Web Components与Shadow DOM嵌入Angular应用时出现jQuery未定义错误的问题排查

Fixing "jquery is not defined" Error When Embedding jQuery App in Shadow DOM Web Component

Hey, let's break down why you're hitting that "jquery is not defined" error and fix it step by step. The core issues are tied to Shadow DOM's script execution behavior and asynchronous dependency loading, plus a small syntax bug in your Web Component code.

What's Going Wrong?

  1. Incorrect Shadow DOM Reference: In your connectedCallback, you're assigning the Shadow root to a global parent variable instead of the component's instance property. This could lead to unexpected behavior when trying to append content later.
  2. Script Loading Race Condition: When you inject the external HTML into Shadow DOM using createContextualFragment, the jQuery and faker scripts start loading asynchronously—but your inline business script runs immediately, before jQuery finishes loading. That's why you get the "not defined" error.

Fixed Solution

Here's the revised code for both your Web Component and the jQuery app to make everything work smoothly:

1. Updated Web Component Code

class Legacy2Wc extends HTMLElement {
  url = undefined;
  parent = undefined; // Instance variable for Shadow root

  constructor() {
    super();
  }

  connectedCallback() {
    this.parent = this.attachShadow({ mode: 'closed' }); // Bind to instance instead of global
  }

  static get observedAttributes() {
    return ['url'];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'url' && oldValue !== newValue) {
      this.url = newValue;
      this.render();
    }
  }

  render() {
    const context = this;
    const xhr = new XMLHttpRequest();
    xhr.addEventListener('readystatechange', function() {
      // Ensure the request succeeded before processing
      if (this.readyState === 4 && this.status === 200) {
        context.buildComponent(this.responseText, context.parent);
      }
    });
    xhr.open('GET', this.url);
    xhr.send();
  }

  async buildComponent(source, shadowRoot) {
    // Parse the external HTML to separate DOM and scripts
    const parser = new DOMParser();
    const doc = parser.parseFromString(source, 'text/html');
    
    // First, add all non-script DOM elements to Shadow DOM
    const bodyElements = Array.from(doc.body.children);
    bodyElements.forEach(el => {
      if (el.tagName !== 'SCRIPT') {
        shadowRoot.appendChild(el.cloneNode(true));
      }
    });

    // Load external scripts one by one, waiting for each to finish
    const externalScripts = Array.from(doc.body.querySelectorAll('script[src]'));
    for (const script of externalScripts) {
      await this.loadScript(script.src, shadowRoot);
    }

    // Run the inline business script after dependencies are ready
    const inlineScript = doc.body.querySelector('script:not([src])');
    if (inlineScript) {
      const scriptEl = document.createElement('script');
      scriptEl.textContent = inlineScript.textContent;
      shadowRoot.appendChild(scriptEl);
    }
  }

  // Helper to load a script and return a promise for async/await
  loadScript(src, container) {
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = src;
      script.onload = resolve;
      script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
      container.appendChild(script);
    });
  }
}

customElements.define('legacy-2-wc', Legacy2Wc);

2. Robustified jQuery App Script (Optional but Recommended)

To add an extra layer of safety, adjust your jQuery app's script to ensure it only runs after jQuery is fully loaded:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <table id="jquery-wc-table">
    <thead>
      <th>id</th>
      <th>name</th>
      <th>address</th>
      <th>date</th>
    </thead>
    <tbody>
    </tbody>
  </table>
  <script src="https://code.jquery.com/jquery-3.6.0.min.js" type="text/javascript"></script>
  <script src="https://rawgit.com/Marak/faker.js/master/examples/browser/js/faker.js" type="text/javascript"></script>
  <script type="text/javascript">
    function initLegacyApp() {
      faker.seed(1);
      const data = new Array(20).fill({}).map(() => ({
        id: faker.random.uuid(),
        name: faker.name.firstName(),
        address: faker.address.streetAddress(),
        date: faker.date.past()
      }));
      const urlParams = new URLSearchParams(window.location.search);
      
      $(document).ready(() => {
        const filterData = data.filter(dataItem => {
          return !urlParams.has('name') || dataItem.name.toLowerCase().trim().includes(urlParams.get('name').toLowerCase().trim());
        });
        const tableBody = $('#jquery-wc-table tbody');
        tableBody.empty(); // Clean up existing rows
        
        filterData.forEach(dataItem => {
          const row = $('<tr></tr>').appendTo(tableBody);
          row.append(`<td>${ dataItem.id }</td>`);
          row.append(`<td>${ dataItem.name }</td>`);
          row.append(`<td><address>${ dataItem.address }</address></td>`);
          row.append(`<td>${ dataItem.date.toISOString() }</td>`);
        });
      });
    }

    // Check if jQuery is already loaded, or wait for it
    if (typeof jQuery !== 'undefined') {
      initLegacyApp();
    } else {
      document.querySelector('script[src*="jquery"]').addEventListener('load', initLegacyApp);
    }
  </script>
</body>
</html>

Key Improvements

  • Fixed Shadow DOM Binding: Now the component uses this.parent to reference the Shadow root, avoiding global variable conflicts.
  • Controlled Script Execution: Using async/await, we load external dependencies first (jQuery, faker) and only run the business logic once they're ready—eliminating the race condition.
  • Cleaner DOM Handling: We separate the static DOM content from scripts, ensuring elements exist before the script tries to manipulate them.
  • Robust Initialization: The jQuery app now explicitly checks for jQuery's presence before running, adding redundancy against loading issues.

With these changes, your legacy jQuery app should embed perfectly into your Angular app via the Web Component.

内容的提问来源于stack exchange,提问作者Ernesto Alfonso

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 13:42:31