如何使用动态模板创建组件? (使用内联模板嵌入组件)

How to create a Component with a dynamic template? (Component transclude with inline template)

我正在尝试创建一个内部具有动态模板字符串的组件,该字符串可以访问模板上的局部变量。我尝试过的每一种方法都以 "dynamic template string" 没有被 $compile 结束(angular 1 术语,请原谅)。

下面是组件的代码。在你看到评论的地方我想插入一个模板字符串,它可以在 ngFor.

中引用 item
@Component({
  selector: 'ion-alpha-scroll',
  template: `
    <ion-scroll [ngStyle]="calculateScrollHeight()" scrollX="false" scrollY="true">
      <ion-list class="ion-alpha-list-outer">
        <div *ngFor="let items of sortedItems | mapToIterable;">
          <ion-item-divider id="scroll-letter-{{items.key}}">{{items.key}}</ion-item-divider>
          <ion-item *ngFor="let item of items.value">
            <!-- how can I pass a dynamic template here that can reference item ? -->
          </ion-item>
        </div>
      </ion-list>
    </ion-scroll>
    <ul class="ion-alpha-sidebar" [ngStyle]="calculateDimensionsForSidebar()">
      <li (click)="alphaScrollGoToList(letter)" *ngFor="let letter of alphabet">
        <div class="letter">{{letter}}</div>
      </li>
    </ul>
  `,
  pipes: [MapToIterable]
})
export class IonAlphaScroll {
  @Input() listData: any;
  @Input() key: string;
  @Input() template: string;
  ....
}

理想情况下,我希望 ion-alpha-scroll 的嵌入内容引用 ngFor 中的 item。我尝试在组件的必要 ngFor 中使用 ng-content 但没有成功 -

<ion-alpha-scroll *ngIf="breeds" [listData]="breeds" key="$t">
  {{item.$t}}
</ion-alpha-scroll>

我试过的一件事是这样的 -

<ion-alpha-scroll *ngIf="breeds" [listData]="breeds" key="$t" [template]="alphaScrollTemplate">
</ion-alpha-scroll>

alphaScrollTemplate只是一个包含{{item.$t}}的字符串。然后我尝试在评论提出问题的组件中引用它,但它不起作用 -

...
<ion-item *ngFor="let item of items.value">
  {{template}}
  <!-- this just outputs {{item.$t}} literally -->
</ion-item>
...

我真的很好奇 angular 2 是否有可能做到这一点。我刚找到 。任何帮助或建议将不胜感激,谢谢。

这是我用于 angular 2.0.0-rc.3

的解决方案

此解决方案创建一个动态组件并使用 ViewContainerRefComponentFactory 加载它。 Here is the ionic 2 component on GitHub.

export function createComponentFactory(resolver: ComponentResolver, metadata: ComponentMetadata): Promise<ComponentFactory<any>> {
  const cmpClass = class DynamicComponent {};
  const decoratedCmp = Component(metadata)(cmpClass);
  return resolver.resolveComponent(decoratedCmp);
}

@Directive({
  selector: 'dynamic-html-outlet',
})
export class DynamicHTMLOutlet {
  @Input() src: string;
  @Input() ionAlphaScrollRef: any;
  @Input() currentPageClass: any;

  constructor(private vcRef: ViewContainerRef, private resolver: ComponentResolver) {
  }

  ngOnChanges() {
    if (!this.src) return;

    const metadata = new ComponentMetadata({
      selector: 'dynamic-html',
      template: this.src,
      pipes: [MapToIterable]
    });
    createComponentFactory(this.resolver, metadata).then(factory => {
      const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
      let component = this.vcRef.createComponent(factory, 0, injector, []);
      component.instance.ionAlphaScrollRef = this.ionAlphaScrollRef;
      component.instance.currentPageClass = this.currentPageClass;
    });
  }
}

@Component({
  selector: 'ion-alpha-scroll',
  template: `
    <dynamic-html-outlet
      [src]="alphaScrollTemplate"
      [ionAlphaScrollRef]="ionAlphaScrollRef"
      [currentPageClass]="currentPageClass">
    </dynamic-html-outlet>
  `,
  pipes: [MapToIterable],
  directives: [DynamicHTMLOutlet]
})
export class IonAlphaScroll {
  @Input() listData: any;
  @Input() key: string;
  @Input() itemTemplate: string;
  @Input() currentPageClass: any;
  @Input() triggerChange: any;
  private _scrollEle: HTMLElement;
  sortedItems: any = {};
  alphabet: any = [];
  alphaScrollTemplate: string;
  ionAlphaScrollRef = this;

  constructor(
    @Host() private _content: Content,
    private _elementRef: ElementRef,
    private vcRef: ViewContainerRef,
    private resolver: ComponentResolver
  ) {

  }

  ngOnInit() {
    this.alphaScrollTemplate = `
      <style>
        .ion-alpha-sidebar {
          position: fixed;
          right: 0;
          display: flex;
          flex-flow: column;
          z-index: 50000;
        }

        .ion-alpha-sidebar li {
          flex: 1 1 auto;
          list-style: none;
          width: 15px;
          text-align: center;
        }
      </style>

      <ion-scroll class="ion-alpha-scroll" [ngStyle]="ionAlphaScrollRef.calculateScrollDimensions()" scrollX="false" scrollY="true">
        <ion-list class="ion-alpha-list-outer">
          <div *ngFor="let items of ionAlphaScrollRef.sortedItems | mapToIterable; trackBy:ionAlphaScrollRef.trackBySortedItems">
            <ion-item-divider id="scroll-letter-{{items.key}}">{{items.key}}</ion-item-divider>
            <ion-item *ngFor="let item of items.value">
              ${this.itemTemplate}
            </ion-item>
          </div>
        </ion-list>
      </ion-scroll>
      <ul class="ion-alpha-sidebar" [ngStyle]="ionAlphaScrollRef.calculateDimensionsForSidebar()">
        <li *ngFor="let letter of ionAlphaScrollRef.alphabet" tappable (click)="ionAlphaScrollRef.alphaScrollGoToList(letter)">
          <a>{{letter}}</a>
        </li>
      </ul>
    `;

    setTimeout(() => {
      this._scrollEle = this._elementRef.nativeElement.querySelector('scroll-content');
      this.setupHammerHandlers();
    });
  }

  ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
    let tmp = {};
    for (let i = 0; i < this.listData.length; i++) {
      let letter = this.listData[i][this.key].toUpperCase().charAt(0);
      if (typeof tmp[letter] === 'undefined') {
        tmp[letter] = [];
      }
      tmp[letter].push(this.listData[i]);
    }

    this.alphabet = this.iterateAlphabet(tmp);
    this.sortedItems = tmp;
  }

  // ....
}