Как добавить новые вкладки с компонентами, используя "имя/селектор компонента" программно, используя angular 2/4?

Я использую вкладки материалов:https://material.angular.io/components/tabs/overview

у меня есть страница с видом вкладки, которую я хочу использовать для заполнения одного пользовательского компонента на вкладку одним нажатием кнопки. Предположим, у меня есть два компонента с селекторами "customcomponent1" и "customcomponent2". Я хочу сделать так, чтобы при нажатии кнопки новый "объект" был добавлен в список следующим образом:

{'имя_компонента':'customcomponent1'}

и новая вкладка "Tab2" будет динамически создаваться с этим компонентом внутри него, если это было так:

  <mat-tab-group>
    <mat-tab label="Tab1">
<button (click)="createNewTabWithComponent('customcomponent1')"></button>
    </mat-tab>
    <mat-tab label="Tab2">
       <customcomponent1></customcomponent1>
    </mat-tab>
  </mat-tab-group>

Если я снова нажму на кнопку... Я получаю новую вкладку:

<mat-tab-group>
    <mat-tab label="Tab1">
<button (click)="createNewTabWithComponent('customcomponent1')"></button>
    </mat-tab>
    <mat-tab label="Tab2">
       <customcomponent1></customcomponent1>
    </mat-tab>
    <mat-tab label="Tab3">
       <customcomponent2></customcomponent2>
    </mat-tab>
  </mat-tab-group>

как это сделать с angular2 / 4? Обычно я бы использовал что-то вроде $compile, но я не думаю, что у angular2/4 есть один. Чего я хочу избежать, это создать кучу вкладок для каждого компонента (представьте, что у меня есть 10 компоненты, действительно не хотят создавать несколько заполнителей mat-tab для каждого отдельного компонента и устанавливать флаг show/hide на каждом из них) и "hardcoding" один компонент внутри него.

это на самом деле не проблема, я думаю, что это специфично для вкладок, если решение показывает, как добавить компонент "динамически", используя имя селектора (что пользователь вводит в текстовое поле и нажимает кнопку "Добавить в список", это тоже будет рассмотренный ответ.

образец я могу подумайте, есть ли текстовое поле, и кто-то может ввести любую строку. Если строка соответствует имени компонента, то этот компонент программно "динамически отображается" на экране, как если бы он был частью исходного шаблона.

псевдокод того, что я ищу, что я знаю, не существует (я думаю, но было бы неплохо, если бы это было):

<button (click)="addNameOfComponentToListOfCustomComponentStrings('text-from-a-textbox')">Add Component</button>

<div *ngFor="let string_name_of_component in listofcustomcomponentstrings">
    <{{string_name_of_component}}><</{{string_name_of_component}}>
</div>

получение их во вкладках было бы плюсом. Если это не выполнимо, пожалуйста, напишите. В противном случае, пожалуйста должность.

Если есть, возможно, обходной путь, который можно было бы сделать, используя некоторую "вложенную угловую маршрутизацию", где он вытаскивает компонент на основе имени компонента в ngFor, это тоже может сработать... я открыт для любых идей.

3 ответов


для вашей заявленной проблемы (не желая иметь 10 заполнителей-вкладок с *ngIf, чтобы скрыть материал), вы могли бы сделать гораздо лучше с маршрутизацией и ленивой загрузкой.

простая навигация: исправлены пути с ленивой загрузкой.

скажем, ваша страница с вкладками находится в /tabs и модуль с это MyTabsModule. Конфигурация маршрута для этого модуля выглядит следующим образом:

const routes = [{
  path: 'tabs',
  component: MyTabsComponent,
}];

предположим, у вас есть две вкладки,left и right. Теперь вам нужно добавить дочерние компоненты в ленивых модулях. Что-то простое, как:

const routes = [{
  path: 'tabs',
  component: MyTabsComponent,
  children: [
    {
      path: 'left',
      loadChildren: 'app/lazy/left.module#LazyLeftModule'
    },
    {
      path: 'right',
      loadChildren: 'app/lazy/right.module#LazyRightModule'
    }
  ]
}];

код MyTabsComponent нужно как-то это показать, как? Вот что!--26-->материал документы говорят (упрощенно):

<h2>My tabs component</h2>
<nav mat-tab-nav-bar>
  <a mat-tab-link routerLink="left">Left tab</a>
  <a mat-tab-link routerLink="right">Right tab</a>
</nav>

<router-outlet></router-outlet>

но что, если у вас есть несколько вкладок? Ну, врачи скажут, что этой, я упростил предыдущий пример:

<a mat-tab-link
  *ngFor="let link of navLinks"
  routerLinkActive #rla="routerLinkActive"
  [routerLink]="link.path"
  [active]="rla.isActive">
    {{link.label}}
</a>

теперь вы можете создать несколько ленивых компонентов и конфигурацию маршрута для этих ленивых маршрутов. На самом деле, вы могли бы даже создать сценарий, который генерирует1 ваша конфигурация маршрута и даже эти ленивые модули для вас как часть сборки.

1 - Угловые Схемы?

это при условии, что вы знаете, какие компоненты у вас есть. Что делать, если вы просто хотите, чтобы пользователь ввел в это поле твой? И выбрать любой компонент самостоятельно? Например, есть раскрывающийся список, и пусть пользователь решает все вкладки, которые они хотят, а затем, поверх этого, загрузите их лениво?

поле навигации: дети параметризация и компонент оболочки.

как это? Во-первых, ваши дети маршрута теперь немного другие:

{
  path: 'tabs',
  component: MyTabsComponent,
  children: [{
    path: ':tab',
    loadChildren: 'app/lazy/lazy.module#LazyWrapperModule',
  }
}

сейчас, ваш LazyWrapperModule экспорт a LazyWrapperComponent. Этот компонент имеет собственную локальную розетку маршрутизатора в шаблоне. Он также загружает компонент на основе url:

constructor(private route: ActivatedRoute, private router: Router) {}
ngOnInit() {
  this.activatedRoute.params.subscribe(params => {
    const tab = params.tab;
    if (!this.isRouteValid(tab)) {
      return this.router.navigate(['error'])
    }
    this.router.navigate([tab]);
  });
}

и LazyWrapperModule также имеет конфигурацию маршрутизатора:

@NgModule({
  imports: [
    RouterModule.forChild([{
      path: 'tab1',
      component: Tab1
    },
    {
      path: 'tab2',
      component: Tab2
    },
    ...
    {
      path: 'tab100',
      component: Tab100
    }]
  ],
  ...

теперь это выглядит лучше. Вы может, из вашего TabsModule перейти к чему угодно. Затем он загружает компонент по параметру. Ваш компонент может, например, показать вкладку ошибки, если пользователь вводит неправильную вкладку,или вы можете просто предоставить typeahead со списком" разрешенных " вкладок и т. д. Забавная штука!

но что, если вы не хотите ограничивать пользователя? Вы хотите, чтобы они набрали "мой самый динамичный компонент" в этом текстовом поле?

Dynamic all: динамическое создание компонентов

вы можете просто создайте компонент динамически. Снова есть компонент-оболочка, который создает и вводит компонент. Е. Г. шаблон:

<input [(ngModel)]="name" required>
<textrea [(ngModel)]="template">
<button (click)="createTemplate()">Create template</button>

<div #target></div>

тогда компонент может быть чем-то вроде:

class MyTabsComponent {
  @ViewChild('target') target;
  name = 'TempComponent';
  template: '<span class="red">Change me!</span>';
  styles: ['.red { border: 1px solid red; }']

  constructor(private compiler: Compiler,
              private injector: Injector,
              private moduleRef: NgModuleRef<any>) {
  }

  createTemplate() {
    const TempComponent = Component({ this.template, this.styles})(class {});
    const TempModule = NgModule({
      declarations: [TempComponent]
    })(class {});

  this.compiler.compileModuleAndAllComponentsAsync(TempModule)
    .then((factories) => {
      const f = factories.componentFactories[0];
      const cmpRef = f.create(this.injector, [], null, this.m);
      cmpRef.instance.name = this.name;
      this.target.insert(cmpRef.hostView);
    });
  }
... // other stuff
}

теперь, как конкретно использовать его, зависит от вашей конкретной потребности. Е. Г. для выполнения домашних заданий, вы можете попробовать динамически создать компонент, как это выше, и впрыскивать его в <mat-tab-group> еще выше. Или динамически создайте маршрут к существующему компоненту в виде лениво загруженной ссылки. Или... размеренности. Мы их любим.


этот образец для использования динамический компонент-погрузчик.

в вашем случае, вам нужно создать вкладку модуль, не использовать <mat-tab-group> insteand. С <mat-tab> составленному MatTabsModule, вы не можете встроить представление в <mat-tab-group>.

пример этот код не будет работать

<mat-tab-group>
   <template>
    <!-- dynamic -->
   </template>
</mat-tab-group>

вам нужно создать новый компонент для вашей собственной вкладки и использовать viewContainerRef.createComponent создать компонент.

если вы видите мой пример (динамический компонент-погрузчик), я ставлю шаблон так:

  <mat-card>
    <mat-card-content>
      <h2 class="example-h2">Tabs with text labels</h2>
      <mat-tab-group class="demo-tab-group">
        <mat-tab label="Tab 1">
        hallow
        </mat-tab>
      </mat-tab-group>
      <ng-template live-comp></ng-template>
    </mat-card-content>
  </mat-card>

я поставил <ng-template live-comp></ng-template> под <mat-card-content> не внутри <mat-tab-group>, потому что <mat-tab> составленному MatTabsModule.


Примечание это пример того, как я использую это, для более сложного приложения с динамическими данными и несколькими циклами и несколькими чайлдами компонентов Чайлдса (слой). Надеюсь, это поможет.

Я делаю это также, но затем с циклом и дочерним компонентом, используя [data]="data", чтобы установить его.

Итак, что вы хотите сделать, это установить вкладку programmatic с:

[(selectedIndex)]="selectedTab" (selectedTabChange)="tabChanged($event)"

это гарантирует, что если ваша вкладка изменит это.свойства selectedIndex будет быть текущей вкладке. Итак, что вам нужно сейчас, это Mat-tab с циклом. В вашем шаблоне:

<mat-tab-group [(selectedIndex)]="selectedTab" (selectedTabChange)="tabChanged($event)">
   <div *ngFor="let tab of tabs; let j = index">
     <mat-tab label={{tab.name}}>
       <div *ngIf="selectedIndex === j"> //To ensure your component is not loaded in the dom when it's not selected.
         <customcomponent [data]="data"></customcomponent>
       <div>
      </mat-tab>
    <div>
    <mat-tab label="...">
      // can put input fields here or even a button. Up to you. selectedTabChange not really have to change then.
   </mat-tab>
 </mat-tab-group>

теперь вы действительно хотите добавить дополнительную вкладку, если вы нажмете на ...

в компоненте:

private tabChanged(_event: any) {
    let index = _event.index;
    if(index < this.tabs.length){
      this.data = this.tabs[index].data; // set data first before showing the component (*ngIf).
      this.selectedIndex = index;
    if(index === this.tabs.length){
      this.tabs.push({name: newName, data: yourData});
      this.selectedIndex = this.tabs.length - 1; // length will be 2 now so new tab will be 1 in this tabs.
    }

теперь вы сможете сделать новую вкладку с именем и данными, которые необходимы для компонента. Теперь вы можете добавить ngOnChanges на customcomponent и получить данные с @Input () data: any;. Какие у вас вообще есть до вы.