Динамічні вкладки з обраними компонентами клацніть користувачем


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, але не можу знайти багато інформації про те, як ним користуватися

EDIT: Спроба уточнити.

Подумайте про вхідні як про поштову скриньку. Елементи дістаються як JSON, і він відображає кілька елементів. Після натискання одного з елементів створюється нова вкладка з типом дії "елементи". Тип - це потім компонент.

EDIT 2: Зображення .


Якщо компоненти, показані на вкладках, невідомі під час збирання, то DCL - це правильний підхід.
Günter Zöchbauer

7
Я не розумію вашої вимоги чітко, тому важко сказати вам що-небудь без робочого коду / плункера. Подивіться це, якщо це може допомогти вам десь plnkr.co/edit/Ud1x10xee7BmtUaSAA2R?p=preview (не знаю, чи це стосується чи ні)
micronyks

@micronyks Я думаю, що ви отримали неправильне посилання
Cuel

Привіт! Я намагаюся зробити те, про що ви просили. Поки мені вдалося створити вкладку з динамічним вмістом, але я не знайшов задовольняючого способу зберегти стан компонента при зміні вкладки (завантажені компоненти можуть бути дуже різними). Як вам це вдалося?
гіпінані

Відповіді:


267

оновлення

Приклад кутового 5 StackBlitz

оновлення

ngComponentOutlet додавали до 4.0.0-beta.3

оновлення

Зараз триває NgComponentOutletробота, яка робить щось подібне https://github.com/angular/angular/pull/11235

RC.7

Приклад Plunker 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 ДІНАМІЧНИЙ НАВІДНИК КОМПОНЕНТУ

старіші версії xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Це знову змінилося в 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();
    }    
  }
}

Приклад Plunker
RC.4 Приклад Plunker 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 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 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


1
Звичайно, вам просто потрібно додати тип компонентів DclWrapperдо, щоб він створив фактичний екземпляр.
Гюнтер Зехбауер

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.cmpRef.changeDetectorRef.detectChanges ();
Rajee

4
Я отримав помилку, коли динамічний компонент мав інший компонент dynaimc при використанні ngAfterViewInit. Натомість змінили на 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);
    });
  }

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.