Angular2 多态嵌套组件在单独文件中时中断

Angular2 polymorphic nested components break when in separate files

我收到错误:"Unexpected directive value 'undefined' on the View of component" 当我将多态组件放在单独的文件中时,但当它们都在同一个文件中时没有错误。 我已经研究了关于此错误的所有 SO 问题,但无济于事,但由于我有一个工作和非工作场景,希望有一些 ng2/typescript 专家出来那里可以帮助我解决这个问题:

我已将问题归结为一个玩具场景,其中我有复合天体(星系、太阳系、恒星)。任何天体都可以包含任何其他天体。我知道——有些人可能会说一颗恒星不能包含一个星系,但他们可能从未看过神秘博士。 :-)

我的主要组件设置描述符并创建顶级组件,在本例中是一个星系:

import {Component} from 'angular2/core';
import {CelestialObject} from './CelestialObject';


@Component({
  selector: 'preview',
  directives: [CelestialObject],
  template: `
    <celestial-object [descriptor]="descriptor"></celestial-object>
  `
})

export class MainComponent {
  private descriptor;

  constructor() {
    this.descriptor = {
      type: 'galaxy',
      children: [
        { type: 'solarSystem', children: [{type: 'star', children: []}] },
        { type: 'star', children: []}
      ]
    };
  }
}

CelestialObjects.ts:

import {Component, Input, DynamicComponentLoader, ElementRef, AfterViewInit} from 'angular2/core';

@Component({
  selector: 'celestial-object',
  template: `
    <span #loadSpecificCelestialObjectHere></span>
  `
})

export class CelestialObject implements AfterViewInit {
  @Input() descriptor: any;

  constructor(private dcl: DynamicComponentLoader, private elementRef: ElementRef) { /* */
  }

  ngAfterViewInit() {
    let objectType: any = null;

    switch (this.descriptor.type) {
      case 'solarSystem':
        objectType = SolarSystem;
        break;
      case 'galaxy':
        objectType = Galaxy;
        break;
      case 'star':
        objectType = Star;
        break;
    }

    this.dcl.loadIntoLocation(objectType, this.elementRef, 'loadSpecificCelestialObjectHere').then((comp) => {
      comp.instance.descriptor = this.descriptor;
    });
  }
}

//======================================
// Galaxy
//======================================
@Component({
  selector: 'galaxy',
  directives: [CelestialObject],
  template: `
    <p>Galaxy</p>
    <celestial-object [descriptor]="obj" *ngFor="#obj of descriptor.children"></celestial-object>
  `
})

export class Galaxy {
  @Input() descriptor: any;
}

//======================================
// SolarSystem
//======================================
@Component({
  selector: 'solar-system',
  directives: [CelestialObject],
  template: `
    <p>Solar system</p>
    <celestial-object [descriptor]="obj" *ngFor="#obj of descriptor.children"></celestial-object>
  `
})

export class SolarSystem {
  @Input() descriptor: any;
}

//======================================
// Star
//======================================
@Component({
  selector: 'star',
  directives: [CelestialObject],
  template: `
    <p>Star</p>
    <celestial-object [descriptor]="obj" *ngFor="#obj of descriptor.children"></celestial-object>
  `
})

export class Star {
  @Input() descriptor: any;
}

CelestialObjectsMinusGalaxy.ts:

import {Component, Input, DynamicComponentLoader, ElementRef, AfterViewInit} from 'angular2/core';
import {Galaxy} from './Galaxy';

@Component({
  selector: 'celestial-object',
  template: `
    <span #loadSpecificCelestialObjectHere></span>
  `
})

export class CelestialObject implements AfterViewInit {
  @Input() descriptor: any;

  constructor(private dcl: DynamicComponentLoader, private elementRef: ElementRef) { /* */
  }

  ngAfterViewInit() {
    let objectType: any = null;

    switch (this.descriptor.type) {
      case 'solarSystem':
        objectType = SolarSystem;
        break;
      case 'galaxy':
        objectType = Galaxy;
        break;
      case 'star':
        objectType = Star;
        break;
    }

    this.dcl.loadIntoLocation(objectType, this.elementRef, 'loadSpecificCelestialObjectHere').then((comp) => {
      comp.instance.descriptor = this.descriptor;
    });
  }
}

//======================================
// SolarSystem
//======================================
@Component({
  selector: 'solar-system',
  directives: [CelestialObject],
  template: `
    <p>Solar system</p>
    <celestial-object [descriptor]="obj" *ngFor="#obj of descriptor.children"></celestial-object>
  `
})

export class SolarSystem {
  @Input() descriptor: any;
}

//======================================
// Star
//======================================
@Component({
  selector: 'star',
  directives: [CelestialObject],
  template: `
    <p>Star</p>
    <celestial-object [descriptor]="obj" *ngFor="#obj of descriptor.children"></celestial-object>
  `
})

export class Star {
  @Input() descriptor: any;
}

和Galaxy.ts。与之前相同的代码,只是拆分成一个单独的文件并导入 CelestialObject:

import {Component, Input} from 'angular2/core';
import {CelestialObject} from './CelestialObject';

//======================================
// Galaxy
//======================================
@Component({
  selector: 'galaxy',
  directives: [CelestialObject],
  template: `
    <p>Galaxy</p>
    <celestial-object [descriptor]="obj" *ngFor="#obj of descriptor.children"></celestial-object>
  `
})

export class Galaxy {
  @Input() descriptor: any;
}

现在我得到了错误。我意识到这最终会成为一个循环引用,但是没有办法将这些组件保存在它们自己的文件中而不会出现 "Unexpected directive value 'undefined' on the View of component" 错误吗?

非常感谢任何帮助。这是一个难以解开的问题。

您可以尝试删除类型开关并将其替换为事件发射器 在你的 CelestialObject:

export class CelestialObject implements AfterViewInit {
  @Input() descriptor: any;

  constructor(private dcl: DynamicComponentLoader, 
              private emitter: SharedEmitterService,
              private elementRef: ElementRef) { /* */
    this.emitter.subscribe(objectType => load(objectType));
  }

  load(objectType) {
    this.dcl.loadIntoLocation(objectType, this.elementRef, 'loadSpecificCelestialObjectHere').then((comp) => {
      comp.instance.descriptor = this.descriptor;
    });
  }
}

然后在您的另一个 类 中您可以启动加载:

export class Galaxy {
  @Input() descriptor: any;
  constructor(private emitter: SharedEmitterService) { /* */ }
  ngOnInit() {
    this.emitter.emit(Galaxy);
  }
}

问题是 Galaxy 导入了 CelestialObject,而 CelestialObject 又导入了 Galaxy。 Sasxa 建议去掉 switch 语句,从而使 CelestialObject 摆脱对 Galaxy 的依赖。

这导致了用实际类型而不是表示类型的字符串填充描述符的解决方案:

import {Component} from 'angular2/core';
import {CelestialObject} from './CelestialObject';
import {Galaxy} from './Galaxy';
import {SolarSystem} from './SolarSystem';
import {Star} from './Star';


@Component({
  selector: 'preview',
  directives: [CelestialObject],
  template: `
    <celestial-object [descriptor]="descriptor"></celestial-object>
  `
})

export class MainComponent {
  private descriptor;

  constructor() {
    this.descriptor = {
      type: Galaxy,
      children: [
        { type: SolarSystem, children: [{type: Star, children: []}] },
        { type: Star, children: []}
      ]
    };
  }
}

那么CelestialObject就变成了下面这个,与具体的天体没有任何依赖关系:

import {Component, Input, DynamicComponentLoader, ElementRef, AfterViewInit} from 'angular2/core';

@Component({
  selector: 'celestial-object',
  template: `
    <span #loadSpecificCelestialObjectHere></span>
  `
})

export class CelestialObject implements AfterViewInit {
  @Input() descriptor: any;

  constructor(private dcl: DynamicComponentLoader, private elementRef: ElementRef) { /* */ }

  ngAfterViewInit() {
    this.dcl.loadIntoLocation(descriptor.type, this.elementRef, 'loadSpecificCelestialObjectHere').then((comp) => {
      comp.instance.descriptor = this.descriptor;
    });
  }
}

感谢 Sasxa 提供关键拼图。