4

I am trying out the new AngularElements feature. (https://angular.io/guide/elements)

First tests were successfull but as soon as I integrate @ContentChildren it stops working. This seems logic to me, as the newly created Component as the CustomElement cannot get the reference via Angular-Context as it lives outside of the app.

My aim was to make a minimal Tab-Wrapper / Tab - Component like structure as they use in angular.io

<custom-tab-wrapper>
  <anb-tab [title]="'Test'">
    Content
  </anb-tab>
</custom-tab-wrapper>

I also created a Stackblitz but this seems to be even worse as HMR defines the component multiple times which results in an error. But this should give you a better idea on what I am trying to achieve.

https://stackblitz.com/edit/angular-byvpdz?file=src%2Fapp%2Ftab-wrapper%2Ftab-wrapper.component.ts

Here are the main files:

index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>AngularBlog</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<anb-root></anb-root>
<custom-tab-wrapper>
  <anb-tab [title]="'Test'">
    Content
  </anb-tab>
</custom-tab-wrapper>
</body>
</html>

tab-wrapper.component.ts

import {AfterContentInit, Component, ContentChildren, OnInit, QueryList, ViewEncapsulation} from '@angular/core';
import {TabComponent} from '../tab/tab.component';

   import {AfterContentInit, Component, ContentChildren, OnInit, QueryList, ViewEncapsulation} from '@angular/core';
import {TabComponent} from '../tab/tab.component';

@Component({
  selector: 'anb-tab-wrapper',
  template: `
    <div class="tab-selection-wrapper">
      <h1>Test</h1>
      <div class="tab-selection" (click)="enableTab(tab)" *ngFor="let tab of tabs.toArray()">
        {{tab.title}}
      </div>
    </div>
    <div class="tab-content">
      <ng-content></ng-content>
    </div>
  `,
  encapsulation: ViewEncapsulation.Native
})
export class TabWrapperComponent implements OnInit, AfterContentInit {
  @ContentChildren(TabComponent) tabs: QueryList<TabComponent>;

  constructor() {
    setInterval(() => {
      console.log(this.tabs);
    }, 2000);
  }

  public enableTab(tab: TabComponent) {
    tab.active = true;
    this.tabs.toArray().forEach(tabToCheck => {
      if (tab !== tabToCheck) {
        tabToCheck.active = false;
      }
    });
  }

  ngAfterContentInit(): void {
  }


  ngOnInit() {
  }

}

And then I would have a Tab-Component. Which gets rendered correctly if I use it within the angular-root-element but not as a WebComponent.

app.module.ts

import {BrowserModule} from '@angular/platform-browser';
import {Injector, NgModule} from '@angular/core';

import {AppComponent} from './app.component';
import {TabWrapperComponent} from './tab-wrapper/tab-wrapper.component';
import {TabComponent} from './tab/tab.component';
import {createCustomElement} from '@angular/elements';

@NgModule({
  declarations: [
    AppComponent,
    TabWrapperComponent,
    TabComponent
  ],
  entryComponents: [
    TabWrapperComponent,
    TabComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {

  constructor(private injector: Injector) {
    const tabWrapper = createCustomElement(TabWrapperComponent, {injector: injector});
    customElements.define('custom-tab-wrapper', tabWrapper);
    const tabComponent = createCustomElement(TabComponent, {injector: injector});
    customElements.define('anb-tab', tabComponent);
  }
}

I am aware that I can solve this without @ContentChildren but I would like to use this feature in CustomComponent. So my question is: Is it possible to use @ContentChildren / ViewChildren. If no, what are the alternatives?

Thanks for helping

Daniel

DaniS
  • 3,429
  • 1
  • 12
  • 18

1 Answers1

4

Rob Worlmald answered my question which I sent him as a Twitter-Message. I would like to share the results with the community:

He answers the question if Directives and ContentChild / ContentChildren works as following:

Nope, they do not - in the current view engine queries are static. In general, because you’re likely to be projecting either vanilla DOM nodes or other (Angular) Custom Elements, you can just use querySelector(All)

Which basically means we can select the children within an angular-livecyclce or outside with document.querySelector(). In my personal opinion fetching it outside Angular seems to be the cleaner approach as the few additional lines wouldn't be used but still executed within an angular-application. I will do some research.

Further discussions on this topic and a possible workaround can be found here: https://twitter.com/robwormald/status/1005613409486848000

He continues:

Which you can combine with https://developer.mozilla.org/en-US/docs/Web/Events/slotchange … to approximate the same behaviors

And ends with a possibly cleaner solution in the future:

In ivy (v7 or shortly thereafter) we’ll likely make the query system more flexible and dynamic

Will update my solution with querySelectorAll in the near future.

DaniS
  • 3,429
  • 1
  • 12
  • 18
  • Has Ivy cleaned this up yet? – light24bulbs Sep 07 '18 at 15:48
  • 1
    It might be solved: https://jaxenter.com/road-to-angular-v7-145326.html https://github.com/angular/angular/pull/24861 Will have a look and update my answer accordingly. – DaniS Sep 10 '18 at 21:59
  • @DaniS - This was really helpful in 2020. What happens to the forwardRef API- which ideally uses a closure for component which will be defined in later point in the app. constructor(@Inject(forwardRef(() => Bus)) bus) { this.bus = bus as Bus; } How this will be evaluated while converting to web components using @angular/elements – CharanbabuKarnam May 05 '20 at 12:30
  • 1
    Well we're in 2021 and it seems we can't access the content projection items yet ( https://github.com/angular/angular/issues/24509 ) ... – DARKGuy Jul 26 '21 at 02:50