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



当我将多态组件放在单独的文件中时,我收到错误:"组件视图上意外的指令值'未定义'",但当它们都在同一个文件中时没有错误。我已经研究了有关此错误的所有SO问题无济于事,但是由于我有一个工作和非工作场景,希望有一些ng2/打字稿大师可以帮助我解决这个问题:

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

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

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

还有银河网。与之前相同的代码,只需拆分成一个单独的文件并导入 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;
}

现在我收到错误。我意识到这最终是一个循环引用,但是有没有办法将这些组件保存在它们自己的文件中而不会收到"组件视图上的意外指令值'未定义'"错误?

任何帮助非常感谢。这个是一只需要解开的熊。

您可以尝试删除类型开关并将其替换为事件发射器 在您的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: []}
      ]
    };
  }
}

然后,天体变为以下内容,不依赖于特定的天体:

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提供关键的拼图。

最新更新