Angular 8通用组件

时间:2019-09-17 08:50:46

标签: angular generics

我有许多组件的逻辑几乎完全相同。例如:

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

import { Rule } from '@models';
import { ConfirmationDialogComponent } from '@core';
import { RulesSaveComponent } from './rules-save.component';
import { RuleService } from '@services';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';

@Component({
    selector: 'app-rules',
    templateUrl: './rules.component.html',
    styleUrls: ['./rules.component.scss'],
})
export class RulesComponent implements OnInit {
    rules: Rule[];

    constructor(private modalService: NgbModal, private ruleService: RuleService) {}

    ngOnInit() {
        this.ruleService.items.subscribe(rules => (this.rules = rules));
    }

    openModal(id: number) {
        const modalRef = this.modalService.open(ConfirmationDialogComponent);
        modalRef.componentInstance.message = 'Deleting a rule is irreversible. Do you wish to continue?';
        modalRef.result.then(
            () => {
                this.ruleService.delete(id);
            },
            () => {
                // Do nothing
            },
        );
    }

    openSaveForm(rule: Rule) {
        const modalRef = this.modalService.open(RulesSaveComponent);
        modalRef.componentInstance.feedId = rule.feedId;
        modalRef.componentInstance.ruleId = rule.id;
        modalRef.componentInstance.modal = true;
    }
}

并且:

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

import { Conversion } from '@models';
import { ConfirmationDialogComponent } from '@core';
import { ConversionsSaveComponent } from './conversions-save.component';
import { ConversionService } from '@services';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';

@Component({
    selector: 'app-conversions',
    templateUrl: './conversions.component.html',
    styleUrls: ['./conversions.component.scss'],
})
export class ConversionsComponent implements OnInit {
    conversions: Conversion[];

    constructor(private modalService: NgbModal, private conversionService: ConversionService) {}

    ngOnInit() {
        this.conversionService.items.subscribe(conversions => (this.conversions = conversions));
    }

    openModal(id: number) {
        const modalRef = this.modalService.open(ConfirmationDialogComponent);
        modalRef.componentInstance.message = 'Deleting a conversion is irreversible. Do you wish to continue?';
        modalRef.result.then(
            () => {
                this.conversionService.delete(id);
            },
            () => {
                // Do nothing
            },
        );
    }

    openSaveForm(conversion: Conversion) {
        const modalRef = this.modalService.open(ConversionsSaveComponent);
        modalRef.componentInstance.feedId = conversion.feedId;
        modalRef.componentInstance.conversionId = conversion.id;
        modalRef.componentInstance.modal = true;
    }
}

或者为了保存详细信息,我有:

import { Component, OnInit, Input } from '@angular/core';
import { first } from 'rxjs/operators';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

import { Rule } from '@models';
import { RuleService } from '@services';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';

@Component({
    selector: 'app-rules-save',
    templateUrl: './rules-save.component.html',
    styleUrls: ['./rules-save.component.scss'],
})
export class RulesSaveComponent implements OnInit {
    @Input() feedId: number;
    @Input() id: number;
    @Input() modal: boolean;
    saveForm: FormGroup;
    loading = false;
    submitted = false;
    editing: boolean;

    constructor(
        private activeModal: NgbActiveModal,
        private formBuilder: FormBuilder,
        private ruleService: RuleService,
    ) {}

    ngOnInit() {
        this.get(this.feedId);
    }

    // convenience getter for easy access to form fields
    get f() {
        return this.saveForm.controls;
    }

    onSubmit() {
        this.submitted = true;

        if (this.saveForm.invalid) {
            return;
        }

        let rule: Rule = {
            id: this.id,
            feedId: this.feedId,
            name: this.f.name.value,
            fieldName: this.f.fieldName.value,
            filterOperator: this.f.filterOperator.value,
            expression: this.f.expression.value,
        };

        this.loading = true;
        this.ruleService[this.editing ? 'update' : 'create'](rule).subscribe(() => {
            this.reset();
            this.activeModal.close('ok');
        });
    }

    private get(feedId: number) {
        this.editing = !!this.id;

        if (this.editing) {
            this.ruleService.get(this.id).subscribe(rule => {
                this.buildForm(rule);
            });
        } else {
            var rule = new Rule();

            rule.id = 0;
            rule.feedId = feedId;

            this.buildForm(rule);
        }
    }

    private buildForm(rule: Rule) {
        this.saveForm = this.formBuilder.group({
            name: [rule.name, Validators.required],
            fieldName: [rule.fieldName, Validators.required],
            filterOperator: [rule.filterOperator, Validators.required],
            expression: [rule.expression, Validators.required],
        });
    }

    private reset() {
        if (this.editing) return;

        this.submitted = false;
        this.saveForm.reset();
    }
}

import { Component, OnInit, Input } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

import { Conversion } from '@models';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { ConversionService } from '@services';

@Component({
    selector: 'app-conversions-save',
    templateUrl: './conversions-save.component.html',
    styleUrls: ['./conversions-save.component.scss'],
})
export class ConversionsSaveComponent implements OnInit {
    @Input() feedId: number;
    @Input() id: number;
    @Input() modal: boolean;
    saveForm: FormGroup;
    loading = false;
    submitted = false;
    editing: boolean;

    constructor(
        private activeModal: NgbActiveModal,
        private formBuilder: FormBuilder,
        private conversionService: ConversionService,
    ) {}

    ngOnInit() {
        this.get(this.feedId);
    }

    // convenience getters for easy access to form fields
    get f() {
        return this.saveForm.controls;
    }

    onSubmit() {
        this.submitted = true;

        if (this.saveForm.invalid) {
            return;
        }

        let conversion: Conversion = {
            id: this.id,
            feedId: this.feedId,
            name: this.f.name.value,
            fieldName: this.f.fieldName.value,
            filterOperator: this.f.filterOperator.value,
            expression: this.f.expression.value,
            mathOperator: this.f.mathOperator.value,
            value: this.f.value.value,
        };

        this.loading = true;
        this.conversionService[this.editing ? 'update' : 'create'](conversion).subscribe(() => {
            this.reset();
            this.activeModal.close('ok');
        });
    }

    private get(feedId: number) {
        this.editing = !!this.id;

        if (this.editing) {
            this.conversionService.get(this.id).subscribe(conversion => {
                this.buildForm(conversion);
            });
        } else {
            var conversion = new Conversion();

            conversion.id = 0;
            conversion.feedId = feedId;

            this.buildForm(conversion);
        }
    }

    private buildForm(conversion: Conversion) {
        this.saveForm = this.formBuilder.group({
            name: [conversion.name, Validators.required],
            fieldName: [conversion.fieldName, Validators.required],
            filterOperator: [conversion.filterOperator, Validators.required],
            expression: [conversion.expression, Validators.required],
            mathOperator: [conversion.mathOperator, Validators.required],
            value: [conversion.value, Validators.required],
        });
    }

    private reset() {
        if (this.editing) return;

        this.submitted = false;
        this.saveForm.reset();
    }
}

这些之间没有太大区别。实际上,对于每种类型(列表保存),您都可以看到所有更改都是相同的。 因此,在列表组件中,更改为:

  • 注入的服务(RuleServiceConversionService)和
  • 消息。

除此之外,它们是相同的。

对于保存组件,更改为:

  • 注入服务
  • 保存之前建立的模型
  • 用于创建表单组的buildForm方法

因此,由于我多次重复使用同一模式,所以我希望可能有一种方法可以处理通用组件?

2 个答案:

答案 0 :(得分:0)

您可以创建充当服务工厂的服务,因为它们具有相同的接口,因此可以抽象出实际使用的服务。

伪代码:

export class FactoryService {
  constructor(private rule_service: RuleService, private conversion_service: ConversionService) { }

  public correctService(name: string): MyServiceInterface {
    if(name === 'rule') {
      return this.rule_service;
    } else if (name === 'conversion') {
      return this.conversion_service
    } else {
      // Handle error...
    }
  }
}

您在其中定义类型MyServiceInterface并带有适当的签名,这样它就干净了。

然后从您的代码中调用this.factory_service.correctService('rule').delete(id),依此类推。

buildForm相同,通过工厂服务抽象出来。

答案 1 :(得分:0)

我不喜欢给出的答案,所以我决定自己一个疯子。 这是我的解决方案(这是列表组件):

import { Component, OnInit, ViewChild } from '@angular/core';

import { FilterResource } from 'src/app/_core/models/filter-resource';
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
import { BaseSaveComponent } from './base-save.component';
import { DataService } from 'src/app/_core/services/data.service';

@Component({
    selector: 'app-base-list',
    templateUrl: './base-list.component.html',
    styleUrls: ['./base-list.component.scss'],
})
export class BaseListComponent<T extends FilterResource> implements OnInit {
    @ViewChild(ConfirmationDialogComponent, { static: true }) confirmationDialog: ConfirmationDialogComponent;
    @ViewChild(BaseSaveComponent, { static: true }) saveForm: BaseSaveComponent<T>;
    items: T[];

    constructor(private dataService: DataService<T>, private deleteMessage: string) {}

    ngOnInit() {
        this.dataService.items.subscribe(items => (this.items = items));
    }

    openModal(id: number) {
        this.confirmationDialog.message = this.deleteMessage;
        this.confirmationDialog.open();
        this.confirmationDialog.closed.subscribe(() => {
            this.dataService.delete(id);
        });
    }

    openSaveForm(model: T) {
        this.saveForm.id = model.id;
        this.saveForm.feedId = model.feedId;
        this.saveForm.open();
    }
}

FilterResource 就是这样:

export class FilterResource {
    public id: number;
    public feedId: number;
}

我的 DataService 还是通用的:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';

import { environment } from '@environments/environment';
import { Resource } from '../models/resource';
import { ToastrService } from 'ngx-toastr';
import { BehaviorSubject } from 'rxjs';

@Injectable({
    providedIn: 'root',
})
export class DataService<T extends Resource> {
    items: BehaviorSubject<T[]>;

    constructor(private endpoint: string, private http: HttpClient, private toastr: ToastrService) {
        this.items = new BehaviorSubject<T[]>([]);
    }

    initialize(feedId: number) {
        this.http.get<T[]>(`${environment.apiUrl}/feeds/${feedId}/${this.endpoint}`).subscribe(response => {
            this.items.next(response);
        });
    }

    get(id: number) {
        return this.http.get<T>(`${environment.apiUrl}/${this.endpoint}/${id}`);
    }

    create(filter: T) {
        return this.http.post<T>(`${environment.apiUrl}/${this.endpoint}`, filter).pipe(
            map((response: any) => {
                const message = response.message;
                const item = response.model;

                let items = this.items.value;
                items.push(item);

                this.emit(items, message);

                return response.model;
            }),
        );
    }

    update(filter: T) {
        return this.http.put<T>(`${environment.apiUrl}/${this.endpoint}`, filter).pipe(
            map((response: any) => {
                const message = response.message;
                const item = response.model;

                let items = this.items.value;
                this.remove(items, filter.id);
                items.push(item);

                this.emit(items, message);

                return response.model;
            }),
        );
    }

    delete(id: number) {
        this.http.delete<any>(`${environment.apiUrl}/${this.endpoint}/${id}`).subscribe(response => {
            let items = this.items.value;
            items.forEach((item, i) => {
                if (item.id !== id) return;
                items.splice(i, 1);
            });

            this.emit(items, response.message);
        });
    }

    private remove(items: T[], id: number) {
        items.forEach((item, i) => {
            if (item.id !== id) return;
            items.splice(i, 1);
        });
    }

    private emit(items: T[], message: string) {
        this.items.next(items);
        this.toastr.success(message);
    }
}

这意味着我能够扩展该组件:

import { Component } from '@angular/core';

import { Filter } from '@models';
import { FilterService } from '@services';
import { BaseListComponent } from '../base/base-list.component';

@Component({
    selector: 'app-filters',
    templateUrl: './filters.component.html',
    styleUrls: ['./filters.component.scss'],
})
export class FiltersComponent extends BaseListComponent<Filter> {
    constructor(filterService: FilterService) {
        super(filterService, 'Deleting a filter is irreversible. Do you wish to continue?');
    }
}

我以相同的方式进行保存