사용자가 선택한 구성 요소를 포함하는 동적 탭


224

구성 요소가 제목을 사용하여 자신을 등록 할 수있는 탭 시스템을 설정하려고합니다. 첫 번째 탭은받은 편지함과 유사하며 사용자를 위해 선택할 수있는 액션 / 링크 항목이 많으며 이러한 각 클릭은 클릭시 새 구성 요소를 인스턴스화 할 수 있어야합니다. 작업 / 링크는 JSON에서 제공됩니다.

그런 다음 인스턴스화 된 구성 요소가 새 탭으로 등록됩니다.

이것이 '최상의'접근법인지 확실하지 않습니까? 지금까지 내가 본 유일한 가이드는 정적 탭에 대한 것이므로 도움이되지 않습니다.

지금까지는 앱 전체에서 지속되도록 메인에 부트 스트랩 된 탭 서비스 만 가지고 있습니다. 다음과 같이 보입니다 :

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

질문 :

  1. 받은 편지함에 새 (다른) 탭을 만드는 동적 목록을 만들려면 어떻게해야합니까? 나는 DynamicComponentBuilder사용될 것이라고 추측하고 있습니까?
  2. 받은 편지함에서 구성 요소를 작성 (클릭시)하여 탭으로 등록하고 표시하는 방법은 무엇입니까? 나는 추측 ng-content하지만 그것을 사용하는 방법에 대한 많은 정보를 찾을 수 없다

편집 : 명확히하려고합니다.

받은 편지함을 메일받은 편지함이라고 생각하십시오. 항목은 JSON으로 가져오고 여러 항목을 표시합니다. 항목 중 하나를 클릭하면 해당 항목 조치 'type'을 사용하여 새 탭이 작성됩니다. 그런 다음 유형이 구성 요소입니다.

편집 2 : 이미지 .


탭에 표시된 구성 요소를 빌드 타임에 알 수없는 경우 DCL이 올바른 방법입니다.
Günter Zöchbauer

7
귀하의 요구 사항을 명확하게 이해하지 못하므로 코드 / 플런 커가 작동하지 않으면 아무것도 말할 수 없습니다. 당신이 어딘가에 도울 수 있다면이 봐 plnkr.co/edit/Ud1x10xee7BmtUaSAA2R?p=preview을 (그 관련 아닌지 모르겠다)
micronyks

@micronyks 나는 당신이 잘못된 연결을 가지고 있다고 생각합니다
Cuel

안녕하세요! 나는 당신이 요청한 것을하려고합니다. 지금까지 동적 내용으로 탭을 만들었지 만 탭을 변경할 때 구성 요소 상태를 유지하는 만족스러운 방법을 찾지 못했습니다 (로드 된 구성 요소는 매우 다를 수 있음). 어떻게 관리 했습니까?
gipinani

답변:


267

최신 정보

Angular 5 StackBlitz 예제

최신 정보

ngComponentOutlet 4.0.0-beta.3에 추가되었습니다

최신 정보

NgComponentOutlet비슷한 작업을 수행 하는 작업이 진행 중입니다 https://github.com/angular/angular/pull/11235

RC.7

플 런커 예 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 로더

이전 버전 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

이것은 Angular2 RC.5에서 다시 변경되었습니다.

아래 예를 업데이트하지만 휴가 전날입니다.

Plunker 예제 는 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();
    }    
  }
}

플런저 예제 RC.4
플런저 예제 베타 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();
    }    
  }
}

플 런커 예제 베타 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 예제 베타 15 ( Punker 기반이 아님)

동적으로 생성 된 구성 요소에 전달할 수있는 데이터를 전달하는 방법도 있습니다 ( 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을 참조 하십시오.


1
물론 DclWrapper실제 인스턴스를 만들려면 구성 요소 유형을 가져와야합니다.
Günter Zöchbauer

1
@Joseph ViewContainerRef을 사용 하는 대신 주입 할 수 있으며 ViewChild<dcl-wrapper>자체가 대상이됩니다. 요소는 대상의 형제로 추가되므로이 <dcl-wrapper>방식을 벗어나게 됩니다.
Günter Zöchbauer

1
교체는 지원되지 않습니다. 템플릿을 ''(빈 문자열)로 변경하고 생성자를로 변경하면 constructor(private target:ViewContainerRef) {}동적으로 추가 된 구성 요소가 동급이됩니다.<dcl-wrapper>
Günter Zöchbauer

1
RC4를 사용하고 있으며 예제가 매우 유용했습니다. 내가 언급하고 싶었던 유일한 것은 this this.cmpRef.changeDetectorRef.detectChanges ();
Rajee

4
ngAfterViewInit를 사용할 때 동적 구성 요소에 다른 dynaimc 구성 요소가 있으면 오류가 발생했습니다. 대신 ngAfterContentInit로 변경되었으며 이제는 중첩 된 동적 구성 요소를 사용하고 있습니다.
Abris

20

나는 의견이 충분하지 않다. 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


16

구성 요소를 스텝 컨테이너에 주입하고 모든 것을 함께 연결하는 서비스 (데이터 동기화) 를 사용 하는 구성 요소 를 사용할 준비가되었습니다 (rc5 호환) ng2 단계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);
    });
  }

}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.