вызывать канал во время выполнения, используя имя канала/метаданные

Я пытаюсь создать динамическую таблицу, в которой я хочу решить во время выполнения, какой канал использовать (если есть).

Я пытаюсь добиться чего-то похожего на (упрощенный):

export class CellModel {
     public content: any;
     public pipe: string
}

Таблица

<tbody>
     <tr *ngFor="let row of data">
         <template ngFor let-cell [ngForOf]=row>
           <td *ngIf="cell.pipe">{{cell.content | cell.pipe}}</td>
           <td *ngIf="!cell.pipe">{{cell.content}}</td>
     </tr>
</tbody>

Я понимаю, что этот пример дает ошибку. Могу ли я использовать Reflect каким-то образом или какое-то другое решение?


person royB    schedule 31.08.2016    source источник


Ответы (2)


Вы не можете применять трубы динамически. Что вы можете сделать, так это построить «мета» канал, который решает, какие преобразования делать.

@Pipe({
  name: 'meta'
})
class MetaPipe implements PipeTransform {
  transform(val, pipes:any[]) {
    var result = val;
    for(var pipe of pipes) {
      result = pipe.transform(result);
    }
    return result;
  }
}

а затем используйте его как

<td *ngIf="cell.pipe">{{cell.content | meta:[cell.pipe]}}</td>
person Günter Zöchbauer    schedule 31.08.2016
comment
Спасибо @Gunter. отличный ответ. Есть небольшая проблема. cell.pipe' is a type of string - the name of the pipe in the @Pipe` метаданные. Итак, в MetaPipe как я могу использовать Reflect (или что-то лучше), чтобы ввести правильный pipe в соответствии с именем? - person royB; 31.08.2016
comment
Вы можете предоставить объект (сопоставить имя с каналом), например {provide: 'pipes', useFactory: (pipe1, pipe2) => {pipe1Name: pipe1, pipe2Name: pipe2}, deps: [Pipe1, Pipe2]}, а затем внедрить его в метаканал constructor(@Inject('pipes') private pipes) {}; и использовать его как result = this.pipes[pipe].transform(result);. - person Günter Zöchbauer; 31.08.2016
comment
ваше решение работает отлично, но я сделал это с помощью инъекции в трубу. Сейчас я пробую ваше решение с PipesProvider. Я не смог найти ни одного примера, где useFactory — это карта между меткой и классом. Я получаю сообщение об ошибке «неиспользуемая метка ts7028», независимо от того, что я делаю. Не могли бы вы добавить более конкретный пример, пожалуйста - person royB; 08.09.2016
comment
Пожалуйста, создайте новый вопрос, в котором вы публикуете код, демонстрирующий, чего вы пытаетесь достичь, и где вы потерпели неудачу. - person Günter Zöchbauer; 08.09.2016

Только для компиляции во время выполнения вы можете создать директиву, которая будет динамически компилировать шаблон.

ОБНОВЛЕНИЕ:

Использование compileModuleAndAllComponentsAsync для RC.6^

динамический-pipe.ts

  ngAfterViewInit() {
    const data = this.data.content;
    const pipe = this.data.pipe;

    @Component({
      selector: 'dynamic-comp',
      template: '{{ data | ' + pipe  + '}}'
    })
    class DynamicComponent  {
        @Input() public data: any;
    };

    @NgModule({
      imports: [BrowserModule],
      declarations: [DynamicComponent]
    })
    class DynamicModule {}

    this.compiler.compileModuleAndAllComponentsAsync(DynamicModule)
      .then(({moduleFactory, componentFactories}) => {
        const compFactory = componentFactories.find(x => x.componentType === DynamicComponent);
        const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
        const cmpRef = this.vcRef.createComponent(compFactory, 0, injector, []);
        cmpRef.instance.data = data;
      });
  }

образец Plunker RC.6^


УСТАРЕВШЕЕ РЕШЕНИЕ

В RC.5 для этого можно использовать Compiler.compileComponentSync/Async:

динамический-pipe.ts

@Directive({
  selector: 'dynamic-pipe' 
})
export class DynamicPipe {
  @Input() data: CellModel;

  constructor(private vcRef: ViewContainerRef, private compiler: Compiler) {}

  ngAfterViewInit() {
    const metadata = new ComponentMetadata({
      template: '{{ data | ' + this.data.pipe  + '}}'
    });

    const data = this.data.content;
    const decoratedCmp = Component(metadata)(class DynamicComponent {  data = data; });

    this.compiler.compileComponentAsync(decoratedCmp)
      .then(factory => {
        const injector = ReflectiveInjector.fromResolvedProviders([], 
           this.vcRef.parentInjector);
        this.vcRef.createComponent(factory, 0, injector, []);
      });
  }
}

И используйте это следующим образом:

<template ngFor let-cell [ngForOf]="row">
   <td><dynamic-pipe [data]="cell"></dynamic-pipe></td>
</template>

См. также образец плункера RC.5, демонстрирующий эту функцию. .

В любом случае, я думаю, что решение Гюнтера предпочтительнее

person yurzui    schedule 31.08.2016
comment
Идеальный!. Какие-нибудь известные хиты исполнения? используя ReflectiveInjector? - person royB; 31.08.2016
comment
Ваш rc.6 Plunker больше не работает, потому что он не указывает конкретно на пакеты rc.6 Angular. Теперь, когда вышел rc.7, он не работает, но есть только одно быстрое исправление, см. обновленный Plunker: plnkr.co/edit/mi85w6QnCeq7zY1VDoaQ?p=preview - person Jeremy Zerr; 14.09.2016
comment
Версия Plunker с исправленной версией Angular на уровне rc.6 находится здесь: plnkr.co/ edit/pvfijMMOfUinMRwwvDde?p=preview - person Jeremy Zerr; 14.09.2016