简体   繁体   English

如何在Angular 6+中将子组件模板包含到其父组件中?

[英]how can I transclude child component template into its parent in Angular 6+?

Back in AngularJS, I created a simple smart table component that could be used like this for our team: 回到AngularJS,我创建了一个简单的智能表组件,可用于我们的团队:

<smart-table items="vm.people">
    <column field="name">{{row.firstname}} {{row.lastname}}</column>
    <column field="age">{{vm.getAge(row.birthday)}}</column>
</smart-table>

This is a contrived example, but it worked like this. 这是一个人为的示例,但是它像这样工作。 it generated the table, with the headers and used the inner content of the <column> tag as the template for each cell (the <td> element) correctly. 它生成带有标题的表,并正确使用<column>标记的内部内容作为每个单元格<td>元素)的模板。

Now, I am trying to port this to Angular (6+). 现在,我试图将其移植到Angular(6+)。 So far, using @ContentChildren I am able to easily extract the list of columns. 到目前为止,使用@ContentChildren可以轻松提取列列表。

import { Component, OnInit, Input, ContentChildren, QueryList } from '@angular/core';

@Component({
    selector: 'app-root',
    template: `
<app-table [data]="data">
    <app-column title="name">{{name}}</app-column>
    <app-column title="age">{{birthday}}</app-column>
</app-table>
`,
})
export class AppComponent {
    data = [{
        name: 'Lorem Ipsum',
        birthday: new Date(1980, 1, 23),
    }, {
        name: 'John Smith',
        birthday: new Date(1990, 4, 5),
    }, {
        name: 'Jane Doe',
        birthday: new Date(2000, 6, 7),

    }];
}

@Component({
    selector: 'app-column',
    template: ``,
})
export class ColumnComponent implements OnInit {
    @Input() title: string;

    constructor() { }

    ngOnInit() {
    }

}

@Component({
    selector: 'app-table',
    template: `
<table>
    <thead>
    <th *ngFor="let column of columns">{{column.title}}</th>
    </thead>
    <tbody>
    <tr *ngFor="let row of data">
        <td *ngFor="let column of columns">
        <!-- <ng-container *ngTemplateOutlet="column.title;context:row" ></ng-container> -->
        </td>
    </tr>
    </tbody>
</table>
`,
})
export class TableComponent implements OnInit {
    @Input() data: any[];

    @ContentChildren(ColumnComponent) columns: QueryList<ColumnComponent>;

    constructor() { }

    ngOnInit() {
    }

}

This renders the following HTML: 这将呈现以下HTML:

<table>
    <thead>
        <!--bindings={"ng-reflect-ng-for-of": "[object Object],[object Object"}-->
        <th>name</th>
        <th>age</th>
    </thead>
    <tbody>
        <!--bindings={"ng-reflect-ng-for-of": "[object Object],[object Object"}-->
        <tr>
            <!--bindings={"ng-reflect-ng-for-of": "[object Object],[object Object"}-->
            <td></td>
            <td></td>
        </tr>
        <tr>
            <!--bindings={"ng-reflect-ng-for-of": "[object Object],[object Object"}-->
            <td></td>
            <td></td>
        </tr>
        <tr>
            <!--bindings={"ng-reflect-ng-for-of": "[object Object],[object Object"}-->
            <td></td>
            <td></td>
        </tr>
    </tbody>
</table>

But now I am stuck trying to insert the content of the <app-column> component into the <app-table> template. 但是现在我被困在尝试 <app-column>组件的内容插入<app-table>模板中。 I have read several answers around here ( this one and this one ). 我在这里已经阅读了几个答案( 这个这个 )。 But the issue I have with those is that either you have a static set of templates or you insert them in static places. 但是我的问题是,要么有一组静态模板,要么将它们插入静态位置。 In my case, I need to use that template inside an *ngFor at runtime. 就我而言,我需要在运行时在*ngFor中使用该模板。

In AngularJS, I was using the following code: 在AngularJS中,我使用以下代码:

function compile(tElement) {
    const columnElements = tElement.find('column');
    const columns = columnElements.toArray()
        .map(cEl => ({
            field: cEl.attributes.field.value,
            header: cEl.attributes.header.value,
        }));

    const template = angular.element(require('./smart-table.directive.html'));

    tElement.append(template);

    // The core of the functionality here is that we generate <td> elements and set their
    // content to the exact content of the "smart column" element.
    // during the linking phase, we actually compile the whole template causing those <td> elements
    // to be compiled within the scope of the ng-repeat.
    const tr = template.find('tr[ng-repeat-start]');
    columnElements.toArray()
        .forEach(cEl => {
            const td = angular.element('<td/>')
                .html(cEl.innerHTML);
            tr.append(td);
        });
    const compile = $compile(template);

    // comment out originals
    columnElements.wrap(function () {
        return `<!-- ${this.outerHTML} -->`;
    });

    return function smartTableLink($scope) {
        $scope.vm.columns = columns;
        compile($scope);
    };
}

In Angular, content projection and template instantiation are separate things. 在Angular中,内容投影和模板实例化是分开的。 That is, content projection projects existing nodes, and you will need a structural directive to create nodes on demand. 也就是说,内容投影会投影现有节点,您将需要一个结构指令来按需创建节点。

That is, column needs to become a structural directive, that passes its template to the parent smart-table , which can then add it to its ViewContainer as often as necessary. 也就是说, column需要成为一个结构指令,它将其模板传递给父smart-table ,然后该smart-table可以根据需要将其添加到其ViewContainer中。

Here is what I ended up with, thanks to Meriton's pointers. 这是我最后得到的,这要归功于Meriton的指示。

As he suggested, I changed my app-column component to a directive instead. 正如他建议的那样,我将我的app-column组件改为了指令 That directive must appear on a ng-template element: 该指令必须出现在ng-template元素上:

@Directive({
    selector: 'ng-template[app-column]',
})
export class ColumnDirective {
    @Input() title: string;
    @ContentChild(TemplateRef) template: TemplateRef<any>;
}

The app-table component became: app-table组件变为:

@Component({
    selector: 'app-table',
    template: `
    <table>
        <thead>
        <th *ngFor="let column of columns">{{column.title}}</th>
        </thead>
        <tbody>
        <tr *ngFor="let row of data">
            <td *ngFor="let column of columns">
            <ng-container
                [ngTemplateOutlet]="column.template"
                [ngTemplateOutletContext]="{$implicit:row}">
            </ng-container>
            </td>
        </tr>
        </tbody>
    </table>
    `,
})
export class TableComponent {
    @Input() data: any[];
    @ContentChildren(ColumnDirective) columns: QueryList<ColumnDirective>;
}

While at first I was a bit turned off by having to expose ng-template to some of my teammates (not very Angular savvy team yet...), I ended up loving this approach for several reasons. 起初,由于不得不将ng-template暴露给一些队友(还不是非常精通Angular的团队...)而使我有些沮丧,但由于一些原因,我最终还是喜欢这种方法。 First, the code is still easy to read: 首先,代码仍然易于阅读:

<app-table [data]="data">
    <ng-template app-column title="name" let-person>{{person.name}}</ng-template>
    <ng-template app-column title="age" let-person>{{person.birthday}}</ng-template>
</app-table>

Second, because we have to specify the variable name of the context of the template (the let-person ) code above, it allows us to retain the business context. 其次,由于我们必须指定上面模板的上下文的变量名称( let-person ),因此它允许我们保留业务上下文。 So, if we were to now talk about animals instead of people (on a different page), we can easily write: 因此,如果现在要谈论动物而不是人(在另一页上),我们可以轻松地编写:

<app-table [data]="data">
    <ng-template app-column title="name" let-animal>{{animal.name}}</ng-template>
    <ng-template app-column title="age" let-animal>{{animal.birthday}}</ng-template>
</app-table>

Finally, the implementation totally retains access to the parent component. 最后,该实现完全保留了对父组件的访问。 So, let say we add a year() method that extracts the year from the birthday, on the app-root component, we can use it like: 因此,假设我们在app-root组件上添加了一个year()方法来从生日中提取年份,我们可以像这样使用它:

<app-table [data]="data">
    <ng-template app-column title="name" let-person>{{person.name}}</ng-template>
    <ng-template app-column title="year" let-person>{{year(person.birthday)}}</ng-template>
</app-table>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Angular:我可以将值从控制台传递给组件,就像父组件如何将值传递给它的子组件一样? - Angular: Can I pass values from the console to a component like how a parent component passes a value to its child? 如何在 Angular 中使用自己的路由在父组件中显示特定的子组件? - How can I display a specific child component inside a parent component with its own routing in Angular? Angular:如何访问父组件中的子组件模板变量? - Angular: How can I access Child Component template variable in parent Component? Angular2集成测试,如何测试在其模板中调用其父方法的子组件 - Angular2 integration testing, how to test a child component that calls its parent's method in its template 子组件如何在Angular4中获取父组件模板 - how child component get parent component template in Angular4 在 Angular 8 中,如果子组件是从代码端附加的,如何在父组件和子组件之间传递数据? - In Angular 8, how can I passing data between parent and child component if the child component is appending from the code side? 如何使用当前的 Form API 将父组件的 FormGroup 传递给其子组件 - How can I pass the FormGroup of a parent component to its child component using the current Form API 如何访问或检查超级父级组件中子组件的模板驱动表单的验证 - How can I access or check validation of the child component's template driven form in the super parent's component 如何使用<parent>和<child> angular 模板递归中的组件 - How to use <parent> and <child> component in template recursion in angular Angular 6 在父组件中显示来自子组件的模板 - Angular 6 Show template from child in parent component
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM