ChatGPT解决这个技术问题 Extra ChatGPT

具有用户单击选择的组件的动态选项卡

我正在尝试设置一个标签系统,允许组件注册自己(带有标题)。第一个选项卡就像一个收件箱,有很多动作/链接项可供用户选择,每次点击都应该能够在点击时实例化一个新组件。动作/链接来自 JSON。

然后实例化的组件将自己注册为一个新选项卡。

我不确定这是否是“最佳”方法?到目前为止,我看到的唯一指南是针对静态选项卡的,这无济于事。

到目前为止,我只有在 main 中引导以在整个应用程序中持续存在的 tabs 服务。它看起来像这样:

export interface ITab { title: string; }

@Injectable()
export class TabsService {
    private tabs = new Set<ITab>();

    addTab(title: string): ITab {
        let tab: ITab = { title };
        this.tabs.add(tab);
        return tab;
    }

    removeTab(tab: ITab) {
        this.tabs.delete(tab);
    }
}

问题:

如何在收件箱中有一个动态列表来创建新的(不同的)标签?我有点猜测 DynamicComponentBuilder 会被使用吗?如何从收件箱创建组件(单击时)将自己注册为选项卡并显示?我猜是 ng-content,但我找不到太多关于如何使用它的信息

编辑:试图澄清。

将收件箱视为邮件收件箱。项目以 JSON 格式获取,并显示多个项目。单击其中一个项目后,将使用该项目操作“类型”创建一个新选项卡。那么类型就是一个组件。

编辑 2: Image

如果选项卡中显示的组件在构建时未知,那么 DCL 是正确的方法。
我不明白你的要求,所以很难在没有工作代码/plunker的情况下告诉你任何事情。如果它可以在某个地方plnkr.co/edit/Ud1x10xee7BmtUaSAA2R?p=preview对您有所帮助,请看这个(我不知道它是否相关)
@micronyks 我认为您的链接错误
你好!我正在尝试做你所要求的。到目前为止,我设法创建了具有动态内容的选项卡,但是我没有找到一种令人满意的方法来在选项卡更改时保持组件状态(加载的组件可能非常不同)。你是怎么做到的?

G
Günter Zöchbauer

更新

Angular 5 StackBlitz example

更新

ngComponentOutlet 已添加到 4.0.0-beta.3

更新

NgComponentOutlet 正在进行类似的工作https://github.com/angular/angular/pull/11235

RC.7

Plunker example RC.7

// Helper component to add dynamic components
@Component({
  selector: 'dcl-wrapper',
  template: `<div #target></div>`
})
export class DclWrapper {
  @ViewChild('target', {read: ViewContainerRef}) target: ViewContainerRef;
  @Input() type: Type<Component>;
  cmpRef: ComponentRef<Component>;
  private isViewInitialized:boolean = false;

  constructor(private componentFactoryResolver: ComponentFactoryResolver, private compiler: Compiler) {}

  updateComponent() {
    if(!this.isViewInitialized) {
      return;
    }
    if(this.cmpRef) {
      // when the `type` input changes we destroy a previously 
      // created component before creating the new one
      this.cmpRef.destroy();
    }

    let factory = this.componentFactoryResolver.resolveComponentFactory(this.type);
    this.cmpRef = this.target.createComponent(factory)
    // to access the created instance use
    // this.compRef.instance.someProperty = 'someValue';
    // this.compRef.instance.someOutput.subscribe(val => doSomething());
  }

  ngOnChanges() {
    this.updateComponent();
  }

  ngAfterViewInit() {
    this.isViewInitialized = true;
    this.updateComponent();  
  }

  ngOnDestroy() {
    if(this.cmpRef) {
      this.cmpRef.destroy();
    }    
  }
}

使用示例

// Use dcl-wrapper component
@Component({
  selector: 'my-tabs',
  template: `
  <h2>Tabs</h2>
  <div *ngFor="let tab of tabs">
    <dcl-wrapper [type]="tab"></dcl-wrapper>
  </div>
`
})
export class Tabs {
  @Input() tabs;
}
@Component({
  selector: 'my-app',
  template: `
  <h2>Hello {{name}}</h2>
  <my-tabs [tabs]="types"></my-tabs>
`
})
export class App {
  // The list of components to create tabs from
  types = [C3, C1, C2, C3, C3, C1, C1];
}
@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App, DclWrapper, Tabs, C1, C2, C3],
  entryComponents: [C1, C2, C3],
  bootstrap: [ App ]
})
export class AppModule {}

另请参阅angular.io DYNAMIC COMPONENT LOADER

旧版本 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

这在 Angular2 RC.5 中再次发生了变化

我将更新下面的示例,但这是假期前的最后一天。

Plunker example 演示了如何在 RC.5 中动态创建组件

更新 - 使用 ViewContainerRef.createComponent()

由于不推荐使用 DynamicComponentLoader,因此需要再次更新该方法。

@Component({
  selector: 'dcl-wrapper',
  template: `<div #target></div>`
})
export class DclWrapper {
  @ViewChild('target', {read: ViewContainerRef}) target;
  @Input() type;
  cmpRef:ComponentRef;
  private isViewInitialized:boolean = false;

  constructor(private resolver: ComponentResolver) {}

  updateComponent() {
    if(!this.isViewInitialized) {
      return;
    }
    if(this.cmpRef) {
      this.cmpRef.destroy();
    }
   this.resolver.resolveComponent(this.type).then((factory:ComponentFactory<any>) => {
      this.cmpRef = this.target.createComponent(factory)
      // to access the created instance use
      // this.compRef.instance.someProperty = 'someValue';
      // this.compRef.instance.someOutput.subscribe(val => doSomething());
    });
  }

  ngOnChanges() {
    this.updateComponent();
  }

  ngAfterViewInit() {
    this.isViewInitialized = true;
    this.updateComponent();  
  }

  ngOnDestroy() {
    if(this.cmpRef) {
      this.cmpRef.destroy();
    }    
  }
}

Plunker example RC.4
Plunker example beta.17

更新 - 使用 loadNextToLocation

export class DclWrapper {
  @ViewChild('target', {read: ViewContainerRef}) target;
  @Input() type;
  cmpRef:ComponentRef;
  private isViewInitialized:boolean = false;

  constructor(private dcl:DynamicComponentLoader) {}

  updateComponent() {
    // should be executed every time `type` changes but not before `ngAfterViewInit()` was called 
    // to have `target` initialized
    if(!this.isViewInitialized) {
      return;
    }
    if(this.cmpRef) {
      this.cmpRef.destroy();
    }
    this.dcl.loadNextToLocation(this.type, this.target).then((cmpRef) => {
      this.cmpRef = cmpRef;
    });
  }

  ngOnChanges() {
    this.updateComponent();
  }

  ngAfterViewInit() {
    this.isViewInitialized = true;
    this.updateComponent();  
  }

  ngOnDestroy() {
    if(this.cmpRef) {
      this.cmpRef.destroy();
    }    
  }
}

Plunker example beta.17

原来的

从您的问题中不能完全确定您的要求是什么,但我认为这应该符合您的要求。

Tabs 组件获取传递的类型数组,并为数组中的每个项目创建“选项卡”。

@Component({
  selector: 'dcl-wrapper',
  template: `<div #target></div>`
})
export class DclWrapper {
  constructor(private elRef:ElementRef, private dcl:DynamicComponentLoader) {}
  @Input() type;

  ngOnChanges() {
    if(this.cmpRef) {
      this.cmpRef.dispose();
    }
    this.dcl.loadIntoLocation(this.type, this.elRef, 'target').then((cmpRef) => {
      this.cmpRef = cmpRef;
    });
  }
}

@Component({
  selector: 'c1',
  template: `<h2>c1</h2>`

})
export class C1 {
}

@Component({
  selector: 'c2',
  template: `<h2>c2</h2>`

})
export class C2 {
}

@Component({
  selector: 'c3',
  template: `<h2>c3</h2>`

})
export class C3 {
}

@Component({
  selector: 'my-tabs',
  directives: [DclWrapper],
  template: `
  <h2>Tabs</h2>
  <div *ngFor="let tab of tabs">
    <dcl-wrapper [type]="tab"></dcl-wrapper>
  </div>
`
})
export class Tabs {
  @Input() tabs;
}


@Component({
  selector: 'my-app',
  directives: [Tabs]
  template: `
  <h2>Hello {{name}}</h2>
  <my-tabs [tabs]="types"></my-tabs>
`
})
export class App {
  types = [C3, C1, C2, C3, C3, C1, C1];
}

Plunker example beta.15(不是基于您的 Plunker)

还有一种方法可以将数据传递给动态创建的组件,例如(someData 需要像 type 一样传递)

    this.dcl.loadIntoLocation(this.type, this.elRef, 'target').then((cmpRef) => {
  cmpRef.instance.someProperty = someData;
  this.cmpRef = cmpRef;
});

还有一些支持将依赖注入与共享服务一起使用。

有关详细信息,请参阅 https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html


当然,您只需将组件类型添加到 DclWrapper 以使其创建实际实例。
@Joseph 您可以注入 ViewContainerRef 而不是使用 ViewChild,然后 <dcl-wrapper> 本身成为目标。元素作为目标的兄弟元素添加,因此会以这种方式位于 <dcl-wrapper> 之外。
不支持更换。您可以将模板更改为 ''(空字符串)`并将构造函数更改为 constructor(private target:ViewContainerRef) {},然后动态添加的组件成为 <dcl-wrapper> 的兄弟
我正在使用 RC4,这个例子非常有用。我只想提到的是我必须添加下面的代码才能正常工作 this.cmpRef.changeDetectorRef.detectChanges();
使用 ngAfterViewInit 时,当动态组件有另一个 dynaimc 组件时出现错误。改为 ngAfterContentInit,现在它正在使用嵌套的动态组件
d
davimusprime

我对评论不够冷静。我从接受的答案中修复了 plunker,以便为 rc2 工作。没什么特别的,只是 CDN 的链接被破坏了。

'@angular/core': {
  main: 'bundles/core.umd.js',
  defaultExtension: 'js'
},
'@angular/compiler': {
  main: 'bundles/compiler.umd.js',
  defaultExtension: 'js'
},
'@angular/common': {
  main: 'bundles/common.umd.js',
  defaultExtension: 'js'
},
'@angular/platform-browser-dynamic': {
  main: 'bundles/platform-browser-dynamic.umd.js',
  defaultExtension: 'js'
},
'@angular/platform-browser': {
  main: 'bundles/platform-browser.umd.js',
  defaultExtension: 'js'
},

https://plnkr.co/edit/kVJvI1vkzrLZJeRFsZuv?p=preview


n
neuronet

有可以使用的组件(与 rc5 兼容)ng2-steps,它使用 Compiler 将组件注入步骤容器和服务以将所有内容连接在一起(数据同步)

    import { Directive , Input, OnInit, Compiler , ViewContainerRef } from '@angular/core';

import { StepsService } from './ng2-steps';

@Directive({
  selector:'[ng2-step]'
})
export class StepDirective implements OnInit{

  @Input('content') content:any;
  @Input('index') index:string;
  public instance;

  constructor(
    private compiler:Compiler,
    private viewContainerRef:ViewContainerRef,
    private sds:StepsService
  ){}

  ngOnInit(){
    //Magic!
    this.compiler.compileComponentAsync(this.content).then((cmpFactory)=>{
      const injector = this.viewContainerRef.injector;
      this.viewContainerRef.createComponent(cmpFactory, 0,  injector);
    });
  }

}

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅