Extending MatRowDef

2019-07-14 06:53发布

I want to display some custom component/html between two rows on click. I believe the quick and easy solution would be to use the event from the click handler and manipulate the DOM directly however I'd like to do it the angular way if possible.

For inspiration I first looked at this article on extending structural directive. It was of limited use though since *matRowDef isn't supposed to be used on its own, but in conjunction with other elements as part of the material table. I then went to have a look at the source code directly and tried to mimic the way MatRowDef extended CdkRowDef and ended up with this:

@Directive({
  selector: '[expandableRowDef]',
  providers: [
    {provide: MatRowDef, useExisting: ExpandableRowDirective},
    {provide: CdkRowDef, useExisting: ExpandableRowDirective}
  ],
  inputs: ['columns: expandableRowDefColumns', 'when: expandableRowDefWhen']
})
export class ExpandableRowDirective<T> extends MatRowDef<T> {
  constructor(template: TemplateRef<any>,
              viewContainer: ViewContainerRef,
              _differs: IterableDiffers) {
                super(template, _differs);
              }
}

I then simply switched *matRowDef="..." with *expandableRowDef="...", it compiles fine and doesn't fail at runtime.

Where should I take it from here to edit the DOM inside the created mat-row element?

1条回答
ら.Afraid
2楼-- · 2019-07-14 07:02

I had a basic requirement for something similar a while ago, and managed to put something together based off the discussion and broken plunkr on this thread.

It essentially works by creating a custom component using ComponentFactory and then using @ViewChildren to get a list of the table rows, and on click of the selected table row inserts the custom component at the specified index passing in the row data using @Input:

Template:

<mat-row *matRowDef="let row; columns: displayedColumns; let index = index" 
  (click)="insertComponent(index)"
  #tableRow  
  matRipple>
</mat-row>

Component:

export class TableBasicExample {

  displayedColumns = ['position', 'name', 'weight', 'symbol'];
  dataSource = new MatTableDataSource<Element>(ELEMENT_DATA);

  @ViewChildren('tableRow', { read: ViewContainerRef }) rowContainers;
  expandedRow: number;

  constructor(private resolver: ComponentFactoryResolver) { }

  insertComponent(index: number) {
    if (this.expandedRow != null) {
      // clear old content
      this.rowContainers.toArray()[this.expandedRow].clear();
    }

    if (this.expandedRow === index) {
      this.expandedRow = null;
    } else {
      const container = this.rowContainers.toArray()[index];
      const factory: ComponentFactory<any> = this.resolver.resolveComponentFactory(InlineMessageComponent);
      const inlineComponent = container.createComponent(factory);

      inlineComponent.instance.user = this.dataSource.data[index].name;
      inlineComponent.instance.weight = this.dataSource.data[index].weight;
      this.expandedRow = index;
    }
  }
}

Custom component which gets inserted between the rows:

@Component({
  selector: 'app-inline-message',
  template: 'Name: {{ user }} | Progress {{ progress}}%',
  styles: [`...`]
})
export class InlineMessageComponent {
  @Input() user: string;
  @Input() weight: string;
}

I have created a basic working demo of this on StackBlitz here. Since this functionality is not officially supported yet, doing it in this way may eventually break down the line, however the Angular Material team does have something similar in the pipeline.

查看更多
登录 后发表回答