Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Angular custom web component throws error: The selector "app-root" did not match any elements

I have a normal Angular 10 application with lazy loaded modules and routing. However, I have a special requirement I need to fulfill.

On most pages I want to initialize the full application with routing etc. by embedding the <app-root> element in the index.html – which is the AppComponent. On some pages on the other hand not the full app should be initialized, rather only one specific <header-search> component that I've registered using @angular/elements (web components). This also means that no routing should take place nor should any other component except the <header-search> be initialized (if it's not embedding by the <header-search> component itself) in this case.

Side note just for you to understand the background of the use case: In the project I'm building not all parts are decoupled with Angular. Some pages are rendered backend-side using Twig/PHP. But I need the search functionality in the header that was built with Angular to be available on these pages too. This means I won't have the full application available at the same time, only the HeaderSearchComponent in this case. On other pages, however, the full application will be initialized including the HeaderSearchComponet, so there's no need for a separate embed using web components – in this case the <app-root> element is enough.

My thoughts where do register the HeaderSearchComponent as <header-search> custom web component using angular elements like:

@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 {}
}

With that I should be able to render the HeaderSearchComponent using <header-search> or the full app using <app-root>. However, once I only embed <header-search> without having <app-root> available at the same time Angular throws an error:

Error: The selector "app-root" did not match any elements

You can try this out yourself in the following minimal Stackblitz example without complex application logic or routing, by replacing <app-root></app-root> with <header-search></header-search> in the index.html file.

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

As mentioned, what I need is a working component <header-search> without the <app-root> element being present. But also, it should be possible to have the <app-root> element for the full application without the <header-search> component being present. So it's either header-search or app-root, both should work.

How to have a custom element component (in this case <header-search>) as an entry point and still the possibility to initialize a full Angular application with the <app-root> element?

like image 464
dude Avatar asked Sep 30 '20 22:09

dude


2 Answers

Angular web components already work with angular elements. The problem that I've had so far is that I couldn't have only a web component without an <app-root> element at the same time. However, removing the boostrap property of the app module entirely and instead adding

entryComponents: [AppComponent],

will make the error disappear. However, this will not initialize <app-root> anymore, since we've removed it from the boostrap part. Now we have to conditionally manually bootstrap the app with:

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

This will do the trick.

like image 125
dude Avatar answered Oct 12 '22 06:10

dude


For me, it seems like you forgot to add HeaderSearchComponent in the declarations part of your app.module.ts.

Probably try it like this:

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

Also make sure your app.component.ts has the following annotation:

@Component({
  selector: 'app-root',
  ...
})
export class AppComponent { ... }
like image 2
nrausch Avatar answered Oct 12 '22 05:10

nrausch