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



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

在大多数页面上,我希望通过在index.html中嵌入<app-root>元素(即AppComponent)来初始化具有路由等功能的完整应用程序。另一方面,在一些页面上,不应该初始化整个应用程序,而应该只初始化一个特定的<header-search>组件,我已经使用@angular/elements(web组件)注册了该组件。这也意味着在这种情况下,除了<header-search>之外,不应该进行任何路由,也不应该初始化任何其他组件(如果它不是由<header-search>组件本身嵌入的)。

旁注只是为了让你了解用例的背景:在我正在构建的项目中,并不是所有部分都与Angular解耦。一些页面是使用Twig/PHP在后端呈现的。但我需要Angular构建的标题中的搜索功能也能在这些页面上使用。这意味着我不会同时拥有完整的应用程序,在这种情况下只有HeaderSearchComponent。然而,在其他页面上,完整的应用程序将被初始化,包括HeaderSearchComponet,因此不需要使用web组件进行单独的嵌入——在这种情况下CCD_ 8元素就足够了

我的想法是,在哪里使用角度元素将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 {}
}

有了这一点,我应该能够使用<header-search>渲染HeaderSearchComponent或使用<app-root>渲染完整的应用程序。然而,一旦我只嵌入<header-search>,而没有<app-root>可用,Angular就会抛出一个错误:

错误:选择器"应用程序根";与的任何元素都不匹配

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

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

如前所述,我需要的是一个不存在<app-root>元素的工作组件<header-search>。但是,也应该可以在不存在<header-search>组件的情况下具有用于整个应用的<app-root>元素。所以它要么是头搜索,要么是应用程序根,两者都应该有效。

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

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

这就行了。

对我来说,您似乎忘记在app.module.tsdeclarations部分添加HeaderSearchComponent

也许可以这样试试:

@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具有以下注释:

@Component({
selector: 'app-root',
...
})
export class AppComponent { ... }

问题

这个问题涉及到缺少元素的角度抱怨。因此,下面的解决方案将检查元素是否存在,如果没有,则在加载应用之前创建该元素

解决方案

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

我们还将在窗口变量上声明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 };
}

应用程序组件.ts

appElement = window["APP_ELEMENT"].root;

app.component.html

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

我们也可以将相同的技术应用于标头组件标头搜索.组件.ts

appElement = window["APP_ELEMENT"].header;

标题搜索.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 { }

在Stacklitz 上查看此演示

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

我已经为你准备好了解决方案。

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

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

我所做的:

  • 在package.json中添加了@angular/elements
  • 向package.json添加了@webcomponents/custom元素
  • 在package.json中添加了@webcomponents/webcomponentsjs(用于es5兼容性
  • polyfills.ts' added line中导入'@webcomponents/webcomponentsjs/customereelements-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

相关内容

  • 没有找到相关文章

最新更新