使用Angular自定义Web组件会引发错误:选择器“ app-root”与任何元素均不匹配

问题描述

我有一个带有延迟加载模块和路由的普通Angular 10应用程序。但是,我有一个特殊的要求要满足。

在大多数页面上,我想通过路由等方式初始化完整的应用程序,方法是将<app-root>元素嵌入到index.html中,即AppComponent。另一方面,在某些页面上,不应初始化完整的应用程序,而应仅初始化我使用@ angular / elements注册一个特定<header-search>组件(Web组件)。这也意味着在这种情况下,不应该进行路由选择,也不应初始化<header-search>以外的其他任何组件(如果它不是由<header-search>组件本身嵌入的话)。

仅用于您了解用例背景的旁注:在我正在构建的项目中,并非所有零件都与Angular分离。有些页面是使用Twig / PHP在后端显示的。但是我也需要在Angular生成的标头中提供搜索功能,才能在这页面上使用。这意味着我不会同时拥有完整的应用程序,在这种情况下,只有HeaderSearchComponent。但是,在其他页面上,整个应用程序将被初始化,包括HeaderSearchComponet,因此无需使用Web组件进行单独的嵌入-在这种情况下, <app-root>元素就足够了。

我的想法是如何使用像这样的有角元素将HeaderSearchComponent注册<header-search>自定义Web组件:

@NgModule({
  imports: [browserModule,FormsModule,SharedModule],declarations: [AppComponent],bootstrap: [AppComponent],entryComponents: [HeaderSearchComponent]
})
export class AppModule implements dobootstrap {
  constructor(injector: Injector) {
    const webComponent = createCustomElement(HeaderSearchComponent,{
      injector
    });
    customElements.define("header-search",webComponent);
  }

  public ngdobootstrap(): void {}
}

这样,我应该可以使用HeaderSearchComponent来渲染<header-search>或使用<app-root>来渲染整个应用。但是,一旦我只嵌入<header-search>而没有同时<app-root>可用,Angular就会抛出错误

错误:选择器“ app-root”与任何元素都不匹配

通过在<app-root></app-root>文件中将<header-search></header-search>替换为index.html,可以在下面的最小Stackblitz示例中尝试此操作,而无需复杂的应用程序逻辑或路由。

https://stackblitz.com/edit/angular-ivy-a4ulcc?file=src/index.html

如上所述,我需要的是工作组件<header-search>,其中没有<app-root>元素。而且,对于整个应用程序,应该有一个<app-root>元素,而没有出现<header-search>组件。因此,无论是标头搜索还是应用程序根目录,两者都应该起作用。

如何使用自定义元素组件(在这种情况下为<header-search>)作为入口点,并且仍然可以使用<app-root>元素初始化完整的Angular应用程序?

解决方法

对我来说,您似乎忘记了在HeaderSearchComponent的{​​{1}}部分添加declarations

大概这样尝试:

app.module.ts

还要确保您的@NgModule({ declarations: [ AppComponent,HeaderSearchComponent ],imports: [ BrowserModule,BrowserAnimationsModule,HttpClientModule,GraphQLModule,SharedModule,AppRoutingModule,],providers: [],bootstrap: [AppComponent],entryComponents: [HeaderSearchComponent],}) export class AppModule { constructor(private injector: Injector) { const webComponent = createCustomElement(HeaderSearchComponent,{injector}); customElements.define('header-search',webComponent); } } 带有以下注释:

app.component.ts
,

Angular Web组件已经可以使用angular元素。到目前为止,我遇到的问题是我不能同时只有一个没有<app-root>元素的Web组件。但是,请完全删除应用模块的boostrap属性,而添加

entryComponents: [AppComponent],

将使错误消失。但是,由于我们已将其从<app-root>部分中删除,因此不会再初始化boostrap。现在我们必须有条件地手动引导应用程序:

public ngDoBootstrap(appRef: ApplicationRef): void {
  if (document.querySelector('app-root')) {
    appRef.bootstrap(AppComponent);
  }
}

这可以解决问题。

,

问题

问题涉及到关于缺少元素的抱怨。因此,以下解决方案将检查元素是否存在以及是否存在,请在加载应用程序之前创建该元素

解决方案

在我们的 main.ts 文件中,我们将检查元素app-root是否存在,如果找不到,则创建一个元素并将其附加到所需的位置。对于这种情况,我们在header-search

之后追加

我们还将在window变量上声明APP_ELEMENT,以跟踪元素最初是否存在

window["APP_ELEMENT"] = { root: true,header: true };
let headerSearchTag = document.querySelector("header-search");
const appRootTag = document.querySelector("app-root");
if (!headerSearchTag) {
  document
    .querySelector("body")
    .prepend(document.createElement("header-search"));
  headerSearchTag = document.querySelector("header-search");
  window["APP_ELEMENT"] = { ...window["APP_ELEMENT"],header: false };
}
if (!appRootTag) {
  headerSearchTag.parentNode.insertBefore(
    document.createElement("app-root"),headerSearchTag.nextSibling
  );
  window["APP_ELEMENT"] = { ...window["APP_ELEMENT"],root: false };
}

app.component.ts

appElement = window["APP_ELEMENT"].root;

app.component.html

<ng-container *ngIf="appElement">
  <h1>BODY</h1>
  <p>
    APP CONTENTS
  </p>
</ng-container>

我们还可以将相同的技术应用于标头组件 header-search.component.ts

appElement = window["APP_ELEMENT"].header;

header-search.component.html

<ng-container *ngIf="appElement">
  <h1>Header</h1>
  <p>
    Some nice header
  </p>
</ng-container>

最后,我们可以将HeaderComponent添加到 app.module.ts

中的引导数组中
@NgModule({
  imports:      [ BrowserModule,FormsModule ],declarations: [ AppComponent,HeaderSearchComponent ],bootstrap: [ AppComponent,HeaderSearchComponent ]
})
export class AppModule { }

查看此Demo on Stackblitz

,

首先,您必须从NgModule声明中的引导程序数组中删除AppComponent。您不想以这种方式引导您的组件。 此外,您需要添加适当的库。

我为您准备了解决方案。

https://angular-ivy-hsyuvz.stackblitz.io

https://stackblitz.com/edit/angular-ivy-hsyuvz

我所做的:

  • 将@ angular / elements添加到package.json
  • 将@ webcomponents / custom-elements添加到package.json
  • 将@ webcomponents / webcomponentsjs添加到package.json(出于与es5的兼容性
  • polyfills.ts' added line 中导入“ @ webcomponents / webcomponentsjs / custom-elements-es5-adapter.js”; `
  • 因为index.html是主文件,所以我用简单的<header-search></header-search>
  • 替换了内容
  • 在app.module.ts中,我已从引导数组中删除了AppComponent,并更改了创建WC的方式

现在,它可以按预期工作:)

此外,我前一段时间已经写过关于该主题https://itnext.io/how-to-run-separate-angular-apps-in-one-spa-shell-5250e0fc6155的文章,并准备了带有更多示例的存储库:https://github.com/marcinmilewicz/microfrontendly/tree/master/microfrontend-example/foo-app