【问题标题】:Angular 8 Generic componentAngular 8 通用组件
【发布时间】:2020-01-18 02:31:12
【问题描述】:

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

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();
    }
}

这些之间没有太大区别。事实上,对于每种类型(listsave),您可以看到所有类型的更改都是相同的。 所以在 list 组件中,变化是:

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

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

对于save组件,变化如下:

  • 注入的服务
  • 保存前构建的模型
  • buildForm 创建表单组的方法

所以,因为我重复了很多次相同的模式,所以我希望有一种方法可以做一个通用组件?

【问题讨论】:

    标签: angular generics


    【解决方案1】:

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

    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?');
        }
    }
    

    我以同样的方式进行了保存。

    【讨论】:

    • 所以你永远不会在模板中直接引用 BaseListComponent,这意味着基础组件可以在组件元数据中省略 selector: 'app-base-list',对吗?
    • @r3plica 您是否考虑过使用接口来构造代码?接口可以抽象出功能,以帮助实现可能对功能有不同需求的新组件或服务。
    【解决方案2】:

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

    伪代码:

    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 也是一样,用工厂服务抽象掉。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-23
      • 1970-01-01
      • 2019-11-12
      • 2020-04-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-01
      相关资源
      最近更新 更多