angular 4 个可重用的组件和模板
angular 4 reusable component and template
我坚持在 Angular 4 中创建一个可重用的组件。我有一堆报告,它们都包含一个搜索表单(每个报告的字段都不同)和一个 material table 结果列表(每个报告的字段列表不同)。当我为每个报告复制整个组件时,它按预期工作,但我想将其重构为可重用的 component/template 和扩展它的子组件。但是示波器全都错了,我无法理解它是如何工作的。
report.component.ts(可重用组件)
import {Component, ViewChild} from '@angular/core';
import {MatPaginator} from '@angular/material';
import 'rxjs/add/operator/map';
import {ReportsDataSource} from '../services/reports-datasource.service';
@Component({
selector: 'app-report',
templateUrl: './report.component.html',
})
export class ReportComponent {
@ViewChild(MatPaginator) paginator: MatPaginator;
/** result table columns */
columns = [];
/** Column definitions in order */
displayedColumns = this.columns.map(x => x.columnDef);
/** empty search parameters object, used for form field binding */
/** datasource service */
dataSource: ReportsDataSource;
/** submit the form */
getData() {
this.dataSource.getData();
}
}
report.component.html(可重复使用的模板)
<form (ngSubmit)="getData()" #ReportSearchForm="ngForm">
<ng-content select=".container-fluid"></ng-content>
<button type="submit" mat-button class="mat-primary" [disabled]="!ReportSearchForm.form.valid">Search</button>
</form>
<mat-table #table [dataSource]="dataSource">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<mat-header-cell *matHeaderCellDef>{{ column.header }}</mat-header-cell>
<mat-cell *matCellDef="let row">{{ column.cell(row) }}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
<mat-paginator #paginator
[length]="dataSource ? dataSource.meta.total_results : 0"
[pageSize]="dataSource ? dataSource.meta.per_page : 25"
[pageSizeOptions]="[10, 25, 50, 100]"
>
</mat-paginator>
childreport.component.ts(具体报)
import {Component, OnInit} from '@angular/core';
import {ReportComponent} from '../report.component';
import {ChildreportService} from './childreport.service';
import {ReportsDataSource} from '../../services/reports-datasource.service';
@Component({
selector: 'app-report-child',
templateUrl: './childreport.component.html',
providers: [ChildreportService, ReportsDataSource]
})
export class ChildreportComponent extends ReportComponent implements OnInit {
constructor(private childreportService: ChildreportService) {
super();
}
/** result table columns */
columns = [
{columnDef: 'column1', header: 'Label 1', cell: (row) => `${row.column1}`},
{columnDef: 'column2', header: 'Label 2', cell: (row) => `${row.column2}`}
];
ngOnInit() {
this.dataSource = new ReportsDataSource(this.ChildreportService, this.paginator);
}
}
childreport.component.html(此报告的搜索表单,嵌入在父模板中)
<app-report>
<div class="container-fluid">
<mat-form-field>
<input matInput placeholder="some field" name="fieldx">
</mat-form-field>
</div>
</app-report>
有效方法:我将表单嵌入到主模板中并且没有错误。
什么不起作用:表单和 table 绑定到 ReportComponent
而不是 ChildreportComponent
。我有点理解为什么会发生这种情况(因为此模板的范围是该组件)但我不知道如何 "inherit" 模板并在 ChildreportComponent
的范围内。我错过了什么?
建议你去this article看看。 @WjComponent 装饰器可能会为您提供有关您的方法的线索。
我从这篇文章中了解到,您需要一个新的组件装饰器来在 base 和 child 类.
之间共享属性
引自文章:
@Component({ selector: 'inherited-grid'
})
export class InheritedGrid extends wjGrid.WjFlexGrid {
...
}
Now we have the new element name for our component! But we’ve missed all the other necessary settings defined in the decorator of the base WjFlexGrid class. For example, WjFlexGrid’s decorator assigns the inputs decorator property with an array of grid properties available for bindings in markup. We lost it in our new component, and if you try to bind to them now, you’ll find that the bindings don’t work.
The answer: the @WjComponent decorator offered by the Wijmo for Angular 2 module. It’s used in the same way as the standard @Component decorator and accepts all @Component decorator’s properties (plus some that are Wijmo-specific), but its main benefit is that it merges its property values with the properties provided by the base class decorator. So the last step in our component definition is replacing @Component with @WjComponent:
import { WjComponent } from 'wijmo/wijmo.angular2.directiveBase';
@WjComponent({
selector: 'inherited-grid'
})
export class InheritedGrid extends wjGrid.WjFlexGrid {
...
}
We may have redefined the decorator’s selector property with the ”inherited-grid” name, but all the other necessary properties like inputs and outputs were taken from the base WjFlexGrid component’s decorator. And now element creates our InheritedGrid component with all the property and event bindings correctly functioning!
另一种方法可能是将 ReportComponent 定义为指令,并通过 @Host decorator 在 ChildReport 和基础之间共享数据。
您还可以查看ngx-datatable的源代码。他们的示例和组件的源代码非常有用,可能会给您关于在组件之间共享数据和覆盖模板的想法。
我自己想出来了。事实上,解决方案相当简单。我的错误是在 report.component 中同时尝试了两件事,提供了模板和逻辑。我最终得到的是一个包含逻辑并由每个报告扩展的抽象组件,以及每个报告中类似部分的几个较小组件(shell、结果列表等)。我也从模板表单切换到响应式表单。
report-base.component.ts 持有共同逻辑
import {OnInit} from '@angular/core';
import {FormBuilder, FormGroup} from '@angular/forms';
import {MatPaginator, MatSidenav} from '@angular/material';
import 'rxjs/add/operator/map';
import {ReportsDataSource} from '../common/services/reports-datasource.service';
import {ReportsService} from '../common/services/reports.service';
import {ReportsResultlistService} from '../common/services/reports-resultlist.service';
export abstract class ReportBaseComponent implements OnInit {
constructor(
protected _formBuilder: FormBuilder, protected _reportService: ReportsService, protected _resultlistService: ReportsResultlistService) {
}
/**
* For toggling the search form and resultlist action buttons
* @type {boolean}
*/
protected hasResults = false;
/** Default data source for the table */
protected dataSource: ReportsDataSource;
/** search form controls */
protected searchForm: FormGroup;
/** result table columns */
protected columns = [];
ngOnInit() {
this.createForm();
this.dataSource = new ReportsDataSource(this._reportService, this._resultlistService);
}
/**
* Builds the searchForm Group
*/
protected createForm() {
// create an empty form
this.searchForm = this._formBuilder.group({});
}
/**
* Submits the form/loads data (f.ex. pagination)
*/
protected getData() {
this.hasResults = true;
this.dataSource.search = this.searchForm.value;
this.dataSource.getData();
}
}
report-shell.component.ts 是一个 CHILD 组件(我的逻辑错误之一)提供了 shell组件:
import {Component, Input} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
@Component({
selector: 'app-report-shell',
templateUrl: './report-shell.component.html',
})
export class ReportShellComponent {
constructor(private route: ActivatedRoute) {
this.title = route.routeConfig.data['caption'];
}
@Input() hasResults = false;
title: string;
}
report-shell.component.html提供HTML周围的搜索表单和结果列表
<mat-expansion-panel [expanded]="!hasResults">
<mat-expansion-panel-header>
Search
</mat-expansion-panel-header>
<ng-content select="form"></ng-content>
</mat-expansion-panel>
<div class="result-list">
<mat-toolbar class="result-header"><span>{{ title }}</span>
<span class="fill-remaining-space"></span>
<button class="fa fa-file-excel-o" (click)="exportExcel()"></button>
</mat-toolbar>
<ng-content select=".result-table"></ng-content>
</div>
所以我的报告扩展了 report-base 并简单地使用 shell 作为 child:
childreport.component.ts是一个特定的报告,它只实现了这个报告的特定内容
import {Component, OnInit} from '@angular/core';
import {FormBuilder, Validators} from '@angular/forms';
import {ReportChildreportService} from './childreport.service';
import {ReportsDataSource} from '../../common/services/reports-datasource.service';
import {ReportsResultlistService} from '../../common/services/reports-resultlist.service';
import {ReportBaseComponent} from '../report-base.component';
@Component({
selector: 'app-report-dispatches',
templateUrl: './dispatches.component.html',
providers: [ReportChildreportService, ReportsResultlistService, ReportsDataSource]
})
export class ReportDispatchesComponent extends ReportBaseComponent implements OnInit {
constructor(protected _reportService: ReportChildreportService, protected _formBuilder: FormBuilder, protected _resultlistService: ReportsResultlistService) {
super(_formBuilder, _reportService, _resultlistService);
}
/** result table columns */
columns = [
{columnDef: 'name', header: 'Name', cell: (row) => `${row.name}`}
];
createForm() {
this.searchForm = this._formBuilder.group({
name: ''
});
}
}
childreport.component.html
<app-report-shell [hasResults]="hasResults">
<form (ngSubmit)="getData()" [formGroup]="searchForm" novalidate>
<mat-form-field>
<input matInput placeholder="search for a name" name="name" formControlName="name">
<mat-error>Invalid name</mat-error>
</mat-form-field>
</div>
</div>
<app-form-buttons [status]="searchForm.status"></app-form-buttons>
</form>
<app-report-result-list
[(dataSource)]="dataSource"
[columns]="columns"
[displayedColumns]="displayedColumns"
class="result-table"
></app-report-result-list>
</app-report-shell>
我不会详细介绍表单和结果列表组件,这个答案已经够长了:-)
所以我设法减少了很多代码重复,尽管仍然有一些(在 childreport.component.html 除了表格之外)。
我坚持在 Angular 4 中创建一个可重用的组件。我有一堆报告,它们都包含一个搜索表单(每个报告的字段都不同)和一个 material table 结果列表(每个报告的字段列表不同)。当我为每个报告复制整个组件时,它按预期工作,但我想将其重构为可重用的 component/template 和扩展它的子组件。但是示波器全都错了,我无法理解它是如何工作的。
report.component.ts(可重用组件)
import {Component, ViewChild} from '@angular/core';
import {MatPaginator} from '@angular/material';
import 'rxjs/add/operator/map';
import {ReportsDataSource} from '../services/reports-datasource.service';
@Component({
selector: 'app-report',
templateUrl: './report.component.html',
})
export class ReportComponent {
@ViewChild(MatPaginator) paginator: MatPaginator;
/** result table columns */
columns = [];
/** Column definitions in order */
displayedColumns = this.columns.map(x => x.columnDef);
/** empty search parameters object, used for form field binding */
/** datasource service */
dataSource: ReportsDataSource;
/** submit the form */
getData() {
this.dataSource.getData();
}
}
report.component.html(可重复使用的模板)
<form (ngSubmit)="getData()" #ReportSearchForm="ngForm">
<ng-content select=".container-fluid"></ng-content>
<button type="submit" mat-button class="mat-primary" [disabled]="!ReportSearchForm.form.valid">Search</button>
</form>
<mat-table #table [dataSource]="dataSource">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<mat-header-cell *matHeaderCellDef>{{ column.header }}</mat-header-cell>
<mat-cell *matCellDef="let row">{{ column.cell(row) }}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
<mat-paginator #paginator
[length]="dataSource ? dataSource.meta.total_results : 0"
[pageSize]="dataSource ? dataSource.meta.per_page : 25"
[pageSizeOptions]="[10, 25, 50, 100]"
>
</mat-paginator>
childreport.component.ts(具体报)
import {Component, OnInit} from '@angular/core';
import {ReportComponent} from '../report.component';
import {ChildreportService} from './childreport.service';
import {ReportsDataSource} from '../../services/reports-datasource.service';
@Component({
selector: 'app-report-child',
templateUrl: './childreport.component.html',
providers: [ChildreportService, ReportsDataSource]
})
export class ChildreportComponent extends ReportComponent implements OnInit {
constructor(private childreportService: ChildreportService) {
super();
}
/** result table columns */
columns = [
{columnDef: 'column1', header: 'Label 1', cell: (row) => `${row.column1}`},
{columnDef: 'column2', header: 'Label 2', cell: (row) => `${row.column2}`}
];
ngOnInit() {
this.dataSource = new ReportsDataSource(this.ChildreportService, this.paginator);
}
}
childreport.component.html(此报告的搜索表单,嵌入在父模板中)
<app-report>
<div class="container-fluid">
<mat-form-field>
<input matInput placeholder="some field" name="fieldx">
</mat-form-field>
</div>
</app-report>
有效方法:我将表单嵌入到主模板中并且没有错误。
什么不起作用:表单和 table 绑定到 ReportComponent
而不是 ChildreportComponent
。我有点理解为什么会发生这种情况(因为此模板的范围是该组件)但我不知道如何 "inherit" 模板并在 ChildreportComponent
的范围内。我错过了什么?
建议你去this article看看。 @WjComponent 装饰器可能会为您提供有关您的方法的线索。 我从这篇文章中了解到,您需要一个新的组件装饰器来在 base 和 child 类.
之间共享属性引自文章:
@Component({ selector: 'inherited-grid'
})
export class InheritedGrid extends wjGrid.WjFlexGrid {
...
}
Now we have the new element name for our component! But we’ve missed all the other necessary settings defined in the decorator of the base WjFlexGrid class. For example, WjFlexGrid’s decorator assigns the inputs decorator property with an array of grid properties available for bindings in markup. We lost it in our new component, and if you try to bind to them now, you’ll find that the bindings don’t work.
The answer: the @WjComponent decorator offered by the Wijmo for Angular 2 module. It’s used in the same way as the standard @Component decorator and accepts all @Component decorator’s properties (plus some that are Wijmo-specific), but its main benefit is that it merges its property values with the properties provided by the base class decorator. So the last step in our component definition is replacing @Component with @WjComponent: import { WjComponent } from 'wijmo/wijmo.angular2.directiveBase';
@WjComponent({
selector: 'inherited-grid'
})
export class InheritedGrid extends wjGrid.WjFlexGrid {
...
}
We may have redefined the decorator’s selector property with the ”inherited-grid” name, but all the other necessary properties like inputs and outputs were taken from the base WjFlexGrid component’s decorator. And now element creates our InheritedGrid component with all the property and event bindings correctly functioning!
另一种方法可能是将 ReportComponent 定义为指令,并通过 @Host decorator 在 ChildReport 和基础之间共享数据。
您还可以查看ngx-datatable的源代码。他们的示例和组件的源代码非常有用,可能会给您关于在组件之间共享数据和覆盖模板的想法。
我自己想出来了。事实上,解决方案相当简单。我的错误是在 report.component 中同时尝试了两件事,提供了模板和逻辑。我最终得到的是一个包含逻辑并由每个报告扩展的抽象组件,以及每个报告中类似部分的几个较小组件(shell、结果列表等)。我也从模板表单切换到响应式表单。
report-base.component.ts 持有共同逻辑
import {OnInit} from '@angular/core';
import {FormBuilder, FormGroup} from '@angular/forms';
import {MatPaginator, MatSidenav} from '@angular/material';
import 'rxjs/add/operator/map';
import {ReportsDataSource} from '../common/services/reports-datasource.service';
import {ReportsService} from '../common/services/reports.service';
import {ReportsResultlistService} from '../common/services/reports-resultlist.service';
export abstract class ReportBaseComponent implements OnInit {
constructor(
protected _formBuilder: FormBuilder, protected _reportService: ReportsService, protected _resultlistService: ReportsResultlistService) {
}
/**
* For toggling the search form and resultlist action buttons
* @type {boolean}
*/
protected hasResults = false;
/** Default data source for the table */
protected dataSource: ReportsDataSource;
/** search form controls */
protected searchForm: FormGroup;
/** result table columns */
protected columns = [];
ngOnInit() {
this.createForm();
this.dataSource = new ReportsDataSource(this._reportService, this._resultlistService);
}
/**
* Builds the searchForm Group
*/
protected createForm() {
// create an empty form
this.searchForm = this._formBuilder.group({});
}
/**
* Submits the form/loads data (f.ex. pagination)
*/
protected getData() {
this.hasResults = true;
this.dataSource.search = this.searchForm.value;
this.dataSource.getData();
}
}
report-shell.component.ts 是一个 CHILD 组件(我的逻辑错误之一)提供了 shell组件:
import {Component, Input} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
@Component({
selector: 'app-report-shell',
templateUrl: './report-shell.component.html',
})
export class ReportShellComponent {
constructor(private route: ActivatedRoute) {
this.title = route.routeConfig.data['caption'];
}
@Input() hasResults = false;
title: string;
}
report-shell.component.html提供HTML周围的搜索表单和结果列表
<mat-expansion-panel [expanded]="!hasResults">
<mat-expansion-panel-header>
Search
</mat-expansion-panel-header>
<ng-content select="form"></ng-content>
</mat-expansion-panel>
<div class="result-list">
<mat-toolbar class="result-header"><span>{{ title }}</span>
<span class="fill-remaining-space"></span>
<button class="fa fa-file-excel-o" (click)="exportExcel()"></button>
</mat-toolbar>
<ng-content select=".result-table"></ng-content>
</div>
所以我的报告扩展了 report-base 并简单地使用 shell 作为 child:
childreport.component.ts是一个特定的报告,它只实现了这个报告的特定内容
import {Component, OnInit} from '@angular/core';
import {FormBuilder, Validators} from '@angular/forms';
import {ReportChildreportService} from './childreport.service';
import {ReportsDataSource} from '../../common/services/reports-datasource.service';
import {ReportsResultlistService} from '../../common/services/reports-resultlist.service';
import {ReportBaseComponent} from '../report-base.component';
@Component({
selector: 'app-report-dispatches',
templateUrl: './dispatches.component.html',
providers: [ReportChildreportService, ReportsResultlistService, ReportsDataSource]
})
export class ReportDispatchesComponent extends ReportBaseComponent implements OnInit {
constructor(protected _reportService: ReportChildreportService, protected _formBuilder: FormBuilder, protected _resultlistService: ReportsResultlistService) {
super(_formBuilder, _reportService, _resultlistService);
}
/** result table columns */
columns = [
{columnDef: 'name', header: 'Name', cell: (row) => `${row.name}`}
];
createForm() {
this.searchForm = this._formBuilder.group({
name: ''
});
}
}
childreport.component.html
<app-report-shell [hasResults]="hasResults">
<form (ngSubmit)="getData()" [formGroup]="searchForm" novalidate>
<mat-form-field>
<input matInput placeholder="search for a name" name="name" formControlName="name">
<mat-error>Invalid name</mat-error>
</mat-form-field>
</div>
</div>
<app-form-buttons [status]="searchForm.status"></app-form-buttons>
</form>
<app-report-result-list
[(dataSource)]="dataSource"
[columns]="columns"
[displayedColumns]="displayedColumns"
class="result-table"
></app-report-result-list>
</app-report-shell>
我不会详细介绍表单和结果列表组件,这个答案已经够长了:-)
所以我设法减少了很多代码重复,尽管仍然有一些(在 childreport.component.html 除了表格之外)。