简体   繁体   中英

Is there a way to use a variable Angular component inside another component, and also pass data to that variable component?

What I want

Let's say component2 is the component I want to be able to use variable components in. Let's say for an example, the variable component we'll use is component1.

component1.html: <div>Hi, I'm comp 1. Data: {{data}}</div>

component2.html: <div>Hi, I'm comp 2. Variable component: <variableComponent [data]="data"></variableComponent></div>

Usage of component2 somewhere: <component2 [variableComponent]="component1" ></component2>

What I'm trying to do

I was following the Angular expandable table example, where each row can be clicked, causing it to expand to reveal details about that row. https://material.angular.io/components/table/examples#table-expandable-rows

I'd already made a generic table-component that you can pass data into, and I added expandable rows to it, but I want to be able to pass a detail-component into it as well, so that our developers can make different detail components for different objects so that we can use those detail components for the different tables. (ie, you could make an element-detail component, which would be used if you were making a table of elements, like in the linked example, and then just pass that component in to the table-component)

I tried using [innerHTML] so I could just pass a variable HTML string to be rendered as the detail panel, which would let me stick a component and parameters in pretty easy. But it doesn't look like it's working, probably because [innerHTML] is skipping some stuff Angular does.

table-component html (you might not need to read this to answer the question):

<mat-table class="lessons-table mat-elevation-z8" [dataSource]="matData" matSort [matSortActive]="tableKeys[0]" matSortDirection="asc" matSortDisableClear multiTemplateDataRows>
  <ng-container *ngFor="let key of tableKeys;" [matColumnDef]="key" >
    <mat-header-cell *matHeaderCellDef mat-sort-header> {{dataKeysToHeadersDictionary[key]}}</mat-header-cell>
    <mat-cell *matCellDef="let row">{{row[key]}}</mat-cell>
  </ng-container>

  <ng-container [matColumnDef]="'expandButtonColumnDef'">
    <mat-header-cell *matHeaderCellDef>:</mat-header-cell>
    <mat-cell *matCellDef="let row">
      <span  (click)="expandedRow = expandedRow === row ? null : row">::</span>
    </mat-cell>
  </ng-container>

  <ng-container [matColumnDef]="'expandedDetail'">
    <mat-cell class="detailCell" *matCellDef="let row;" [attr.colspan]="tableKeys.length">
      <div class="detailCellDiv" [@detailExpand]="row == expandedRow ? 'expanded' : 'collapsed'">
        <!-- vvv THIS IS THE IMPORTANT ROW vvv -->
        <div [innerHTML]="getRowDetail(row)"></div>
        <!-- ^^^ THIS IS THE IMPORTANT ROW ^^^ -->
      </div>
    </mat-cell>
  </ng-container>

  <mat-header-row *matHeaderRowDef="tableKeys.concat('expandButtonColumnDef')"></mat-header-row>
  <mat-row *matRowDef="let row; columns: tableKeys.concat('expandButtonColumnDef')"></mat-row>
  <mat-row class="detailRow" *matRowDef="let row; columns: ['expandedDetail']"></mat-row>
</mat-table>

table-component typescript:

// Right now, this is returning a static string with html to use the account-details component
// But eventually I want this to be a dynamic or variable string so I can use whatever component I give to the table-component
getRowDetail(row): any {
  return '<app-account-details style="display: flex;" [account]="row"></app-account-details>';
}

I suppose a workaround would be to make a generic-details component, and then in the html for that put an ngSwitch with every single details component I end up making, but that's a workaround.

EDIT: The Workaround

So, the issue is that I want to be able to "pass in" component1 to component2, but also have data pass from component2 to component1. ng-content doesn't appear to allow me to do that, so I've created a generic-variable component that you can instruct to display whatever component you want, and pass data to it also.

generic-variable-component typescript:

...
@Input() data: any;
@Input() componentName: string;
...

generic-variable-component.html:

<ng-container *ngIf="componentName === 'c1'"><component1 [data]="data"></component1></ng-container>
<!-- Repeat the above with whatever components you end up needing -->

component1.html: <div>Hi, I'm comp 1. Data: {{data}}</div>

component2.html: <div>Hi, I'm comp 2. Variable component: <generic-variable-component [data]="data" [componentName]="varCompName"></generic-variable-component></div>

Usage of component2 somewhere: <component2 [varCompName]="c1" ></component2>

This seems to work the way I want it to. The downside is that I have to go edit generic-variable-component.html every time I add a new type of component I want to use in this situation, but that's really not a big deal, you just copy that line. There might be a better way to do this, involving content injection and directives, but I'm not sure how to do that.

Also, I'm a bit new to posting on stackoverflow, so I'm not sure if I should mark my question as answered because of my workaround. I think for now I'll leave it up in case someone wants to swoop in with the "correct" answer.

I think you should read about content projection . What is does is give you a way to pass whatever you want to a component as a parameter. For example:

Parent component

<component1>
    <componentAsAParameter></componentAsAParameter>
</component1>

In component1 Html you can use the component passed as aa parameter using ng-content

<div>
    whatever you want
    <ng-content></ng-content>
</div>

The Html, at runtime would look like:

<div>
    whatever you want
    <componentAsAParameter></componentAsAParameter>
</div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM