Dynamiczne zakładki z kliknięciem przez użytkownika wybranych komponentów

Próbuję skonfigurować system tab, który pozwala komponentom na zarejestrowanie się (z tytułem). Pierwsza karta jest jak skrzynka odbiorcza, istnieje wiele działań/elementów linków do wyboru dla użytkowników, a każde z tych kliknięć powinno być w stanie utworzyć instancję nowego komponentu, po kliknięciu. Akcje / linki pochodzą z JSON.

Instancyjny komponent zarejestruje się jako nowa karta.

Nie jestem pewien, czy to jest najlepsze podejście? Do tej pory jedynym poradniki, które widziałem, są dla kart statycznych, co nie pomaga.

Jak na razie mam tylko usługę tabs, która jest bootstrapped w main, aby utrzymywać się w całej aplikacji. Wygląda to mniej więcej tak:

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

Pytania:

  1. Jak mogę mieć dynamiczną listę w skrzynce odbiorczej, która tworzy nowe (różne) karty? Zgaduję, że DynamicComponentBuilder zostanie użyty?
  2. W Jaki Sposób komponenty mogą być tworzone ze skrzynki odbiorczej (po kliknięciu) rejestrują się jako zakładki i mogą być wyświetlane? I ' m zgaduję ng-content, ale nie mogę znaleźć zbyt wiele informacji na temat tego, jak z niego korzystać

EDIT: próba wyjaśnienia.

Pomyśl o skrzynce odbiorczej jak o skrzynce odbiorczej. Elementy są pobierane jako JSON i wyświetla kilka elementów. Po kliknięciu jednego z elementów zostanie utworzona nowa karta z akcją "Typ". Typ jest wtedy składnikiem.

Edytuj 2: Obraz .

Author: Alexander Abakumov, 2016-03-31

3 answers

Update

Kątowa 5 StackBlitz przykład

Update

ngComponentOutlet został dodany do 4.0.0-beta.3

Update

Istnieje NgComponentOutlet praca w toku, która robi coś podobnego https://github.com/angular/angular/pull/11235

RC.7

Plunker przykład 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();
    }    
  }
}

Przykład użycia

// 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 {}

Zobacz angular.io DYNAMIC COMPONENT LOADER

Starsze wersje

To zmieniło się ponownie w Angular2 RC.5

Zaktualizuję poniższy przykład, ale jest to ostatni dzień przed wakacjami.

Ten przykład Plunkera pokazuje jak dynamicznie tworzyć komponenty w RC.5

Update-use ViewContainerRef .createComponent()

Ponieważ DynamicComponentLoader jest przestarzałe, podejście musi zostać zaktualizowane ponownie.

@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 przykład RC.4
Plunker przykład beta.17

Update-use 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 przykład beta.17

Oryginalny

Nie jestem do końca pewien z twojego pytania, Jakie są Twoje wymagania, ale myślę, że powinno to zrobić to, co chcesz.

Komponent Tabs otrzymuje tablicę typy przekazywane i tworzy "tabulatory"dla każdego elementu w tablicy.

@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 przykład beta.15 (nie na podstawie Twojego Plunkera)

Istnieje również sposób przekazywania danych, które mogą być przekazywane do dynamicznie tworzonego komponentu jak (someData musiałyby być przekazywane jak type)

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

Istnieje również wsparcie dla korzystania z iniekcji zależności z usługami współdzielonymi.

Po Więcej szczegółów zobacz https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html

 270
Author: Günter Zöchbauer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-26 07:16:52

Nie jestem wystarczająco fajny na komentarze. Naprawiłem plunker z zaakceptowanej odpowiedzi do pracy dla rc2. Nic wymyślnego, linki do CDN były po prostu zepsute to wszystko.

'@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

 20
Author: davimusprime,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-20 20:52:12

Istnieje komponent gotowy do użycia (kompatybilny z rc5) ng2-kroki który używa Compiler do wstrzykiwania komponentu do kontenera step i serwis do okablowania wszystkiego razem (synchronizacja danych)

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

}
 16
Author: neuronet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-10 15:58:54