webentwicklung-frage-antwort-db.com.de

wiederverwendbare Komponente und Schablone

Ich bin beim Erstellen einer wiederverwendbaren Komponente in Angular hängen geblieben. 4. Ich habe eine Reihe von Berichten, die alle aus einem Suchformular (Felder sind für jeden Bericht unterschiedlich) und einer Ergebnistabelle (Feldliste ist unterschiedlich) bestehen für jeden Bericht). Es funktioniert wie erwartet, wenn ich die gesamte Komponente für jeden Bericht dupliziere, aber ich möchte es in eine wiederverwendbare Komponente/Vorlage und untergeordnete Komponenten umgestalten, die es erweitern. Aber die Bereiche sind alle falsch und ich kann nicht verstehen, wie das funktioniert.

report.component.ts (wiederverwendbare Komponente)

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 (wiederverwendbare Vorlage)

<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 (ein bestimmter Bericht)

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 (das Suchformular für diesen Bericht, eingebettet in die übergeordnete Vorlage)

<app-report>
    <div class="container-fluid">
        <mat-form-field>
            <input matInput placeholder="some field" name="fieldx">
        </mat-form-field>
    </div>
</app-report>

Was funktioniert: Ich bekomme das Formular in die Hauptvorlage eingebettet und keine Fehler.

Was nicht funktioniert: Das Formular und die Tabelle sind an ReportComponent anstatt an ChildreportComponent gebunden. Ich verstehe ein bisschen, warum dies passiert (weil der Bereich dieser Vorlage diese Komponente ist), aber ich habe keine Ahnung, wie ich die Vorlage "erben" und im Bereich der ChildreportComponent sein könnte. Was vermisse ich?

5
masterfloda

Ich habe es selbst herausgefunden. Tatsächlich ist die Lösung eher trivial. Mein Fehler bestand darin, zwei Dinge gleichzeitig in meiner report.component auszuprobieren und eine Vorlage sowie eine Logik bereitzustellen. Am Ende habe ich eine abstrakte Komponente, die die Logik enthält und um jeden Bericht erweitert wird, sowie mehrere kleinere Komponenten für die ähnlichen Teile in jedem Bericht (Shell, Ergebnisliste usw.). Ich habe auch von Template-Formularen zu reaktiven Formularen gewechselt.

report-base.component.ts enthält die allgemeine Logik

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 ist eine CHILD-Komponente (einer meiner logischen Fehler), die die Shell um die Komponenten herum bereitstellt:

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 liefert den HTML-Code um das Suchformular und die Ergebnisliste

<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>

Meine Berichte erweitern also die Berichtsbasis und verwenden die Shell einfach als Kind:

childreport.component.ts ist ein bestimmter Bericht, der nur das implementiert, was für diesen Bericht spezifisch ist

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>

Ich werde nicht auf die Details der Formulare und Ergebnislisten-Komponenten eingehen, diese Antwort ist lang genug, wie es ist :-)

Ich habe es also geschafft, die Code-Wiederholung stark zu reduzieren, obwohl es immer noch welche gibt (bis auf das Formular immer noch in der childreport.component.html).

2
masterfloda

Ich schlage vor, Sie werfen einen Blick auf diesen Artikel . @ WjComponent Decorator kann Ihnen einen Hinweis auf Ihre Vorgehensweise geben. Was ich aus dem Artikel verstehe, ist, dass Sie einen neuen Komponentendekorator benötigen, um Eigenschaften zwischen Basis- und Kindklassen zu teilen.

Zitat aus dem Artikel:

@Component({  selector: 'inherited-grid'
})
export class InheritedGrid extends wjGrid.WjFlexGrid {
...
}

Jetzt haben wir den neuen Elementnamen für unsere Komponente! Wir haben jedoch alle anderen erforderlichen Einstellungen übersehen, die im Decorator der Basisklasse WjFlexGrid definiert wurden. Beispielsweise weist der Dekorator von WjFlexGrid der Eigenschaft des Eingabedekorators ein Array von Rastereigenschaften zu, die für Bindungen im Markup verfügbar sind. Wir haben sie in unserer neuen Komponente verloren. Wenn Sie jetzt versuchen, sie zu binden, werden Sie feststellen, dass die Bindungen nicht funktionieren.

Die Antwort: der vom Wijmo angebotene @ WjComponent-Dekorator für das Angular 2-Modul. Es wird auf die gleiche Weise wie der [email protected] verwendet und akzeptiert alle Eigenschaften des @Component-Dekorators (plus einige, die Wijmo-spezifisch sind). Der Hauptvorteil besteht jedoch darin, dass seine Eigenschaftswerte mit den Eigenschaften des Basisklassen-Dekorators zusammengeführt werden. Der letzte Schritt in unserer Komponentendefinition ist das Ersetzen von @Component durch @WjComponent: {WjComponent} aus 'wijmo/wijmo.angular2.directiveBase' importieren;

@WjComponent({
  selector: 'inherited-grid'
})
export class InheritedGrid extends wjGrid.WjFlexGrid {
...
}

Möglicherweise haben wir die Selector-Eigenschaft des Decorators mit dem Namen "inherited-grid" neu definiert, aber alle anderen erforderlichen Eigenschaften wie Ein- und Ausgaben wurden vom Decorator der Basis-WjFlexGrid-Komponente übernommen. Und jetzt erstellt element unsere InheritedGrid-Komponente mit allen ordnungsgemäß funktionierenden Eigenschafts- und Ereignisbindungen!


Ein anderer Ansatz könnte darin bestehen, die ReportComponent als Direktive zu definieren und Daten zwischen ChildReport und der Basis über @Host decorator auszutauschen.

Sie können auch den Quellcode von ngx-datatable überprüfen. Der Quellcode der Beispiele und Komponenten ist sehr informativ und kann Ihnen Anregungen zum Austausch von Daten zwischen Komponenten und zum Überschreiben von Vorlagen geben.