SOLID Угловая архитектура с «тупыми» компонентами - PullRequest
4 голосов
/ 17 октября 2019

Наша команда в настоящее время пробует архитектуру Angular, и я тоже хочу получить советы от других людей.

"Базовая" архитектура Angular будет выглядеть примерно так

Angular Architecture

Компонент имеет представление, его логику и его зависимости.

Но мы хотим сделать более надежный подход, который говорит: «У класса, функции или метода должна быть только одна ответственность». Поэтому мы подумали, что можем позволить классу компонента управлять только представлением и экспортировать его логику в сильно связанный сервис. Наша архитектура теперь выглядит примерно так:

Наша угловая архитектура

Но что-то странное в использовании сильно связанного сервиса для экспорта логики компонента, мы обсуждали этов нашей команде принцип Angular сервиса должен заключаться в выполнении бизнес-логики, такой как вызов API, и должен использоваться в любом месте, не сильно связан с чем-то вроде компонента.

Так вот почему я здесь, мыХотели бы получить советы от сообщества разработчиков о нашей архитектуре и быть уверенными, что мы не движемся в нестабильном / неправильном направлении (пожалуйста, сообщите нам, если вы так думаете и почему)

Вот пример того, что мыdid

У нас есть компонент, который должен отображать список продуктов. Мы экспортировали логику в две службы:

  • ProductListItemService , которые управляют элементом списка
  • ProductsListService , которые управляют самим списком

Вы можете себе представить, что это будет трудно использовать в другом месте, если нам не нужен тот же список в другом представлении

Мы использовали инверсию управления из SOLID и внедрили наши сервисы с TokenInjection, котороепозволяет нам использовать интерфейсы

Компонент абсолютно ничего не делает, кроме вызова своих логических служб и обработки представления (показать загрузчик, изменить переменную). Здесь вы идете с кодом

// IMPORTS ARE HERE

@Component({
    selector: 'app-products-list',
    templateUrl: './products-list.component.html',
    styleUrls: ['./products-list.component.css'],
    providers: [{
        provide: BASE_PRODUCT_LIST_ITEM_SERVICE_TOKEN, // This is a token used to allows us to inject an interface
        useClass: ProductListItemService,
    },
    {
        provide: PRODUCTS_LIST_SERVICE_TOKEN, // This is a token used to allows us to inject an interface
        useClass: ProductsListService,
    },
    {
        provide: MODAL_SERVICE_TOKEN, // This is a token used to allows us to inject an interface
        useClass: ModalService
    },
    ]
})

export class ProductsListComponent implements OnInit {

    // CLASS MEMBERS ARE HERE

    constructor(
        @Inject(BASE_APP_CONFIG_PROVIDER_TOKEN) private appConfig: BaseAppConfigProvider,
        @Inject(BASE_PRODUCT_LIST_ITEM_SERVICE_TOKEN) private _productListItemService: BaseProductListItemService,
        @Inject(BASE_PRODUCTS_LIST_SERVICE_TOKEN) private _productsListService: BaseProductsListService
    ) {
    }

    async ngOnInit() {
        await this.initProducts();
        // Subscribe on a promise from the service that manage the rows
        // When the service upsert a row, it notify that and the component can perform action(s)
        this._productListItemService.onUpdated.subscribe(async () => {
            await this.initProducts();
        });
    }

    /**
     * Get the list of TD products linked to the btob account
     **/
    public async initProducts() {
        this.loading = true;
        // Call the service that manage the list to populate the view
        this.products = await this._productsListService.loadProducts(this.btobAccount.Id);
        this.loading = false;
    }

    /**
     * Open a dialog to confirm the activation or deactivation of a product
     * @param event
     * @param productId
     * @param newStatus
     */
    public openUpdateProductStatusModal(event: MouseEvent, productId: number, newStatus: boolean) {
        // Call service that manage the rows
        this._productListItemService.openUpdateStatusModal(event, this.products, productId, newStatus);
    }

    /**
     * Open the product modal
     * @param state The state to open the modal in. Either insert or update
     * @param product The product to update if the state is update
     */
    public openProductModal(state: Crud, product?: Product) {
        // Call service that manage the rows
        this._productListItemService.openUpsertModal(state, this.btobAccount.Id, product);
    }

    /**
     * Open the product params modal
     * @param product
     */
    public openProductParamsModal(product?: Product): void {
        // Call service that manage the rows
        this._productListItemService.openProductParamsModal(product);
    }

    /**
     * Check if a product is expired
     * @param product The product to check
     */
    public isExpiredProduct(product: Product): boolean {
        // Call service that manage the rows
        return this._productListItemService.isExpired(product);
    }

    public openCommentModal(id: number, name: string, comment: string): void {
        // Call service that manage the rows
        this._productListItemService.openUpdateCommentModal(id, name, comment);
    }
}
...