NXを用いたUIプレゼンロジックからのビジネスロジックの分離
NXを用いたUIプレゼンロジックからのビジネスロジックの分離
説明
この記事では、UIのプレゼンロジックからアプリケーションのビジネスロジックを分離する方法について説明します.我々は、NXを使用して、これは、プレゼンスコンポーネントがどこにあるアプリを作成するデザインを利用して、それを実現し、ビジネスのロジックがどこにあるlibs.
なぜですか?
私は、与えられたコンポーネントのためにコードの1000 +線を持っている多くのコードベースでいました.この問題は、コンポーネントがどのようにしているかを分離することができないということです.
しかし、ちょっと待って、なぜ我々はそれらのことを分離する必要がありますか?以下は重要な理由です.
アプローチ
我々のNX Monorepoでは、我々のコンポーネントに必要とされるインターフェース、タイプ、および必要に応じて必要なサービスをエクスポートします.また、アプリケーション内の状態ストアを初期化できるように、状態ストアをエクスポートします.
最後にこれについては、アプリはイオンのアプリだということです.これはこの記事には関係がない.
現在のリストモジュール
コンポーネント
component.html
<pmt-mobile-toolbar class="header" title="Current Items">
</pmt-mobile-toolbar>
<ion-content *ngIf="viewModel$ | async as viewModel">
<ion-list *ngIf="viewModel.currentItems?.length; else noItemText">
<ion-item-sliding *ngFor="let item of viewModel.currentItems;">
<ion-item-options side="start">
<ion-item-option color="danger">
<ion-icon name="trash-sharp"></ion-icon>
</ion-item-option>
</ion-item-options>
<ion-item-options side="end">
<ion-item-option (click)="currentListStateSvc.markItemAsUsed(item)">
<ion-icon name="checkmark-sharp"></ion-icon>
</ion-item-option>
<ion-item-option (click)="currentListStateSvc.decrementItem(item)" *ngIf="item.qty > 1"><ion-icon name="remove-sharp"></ion-icon></ion-item-option>
</ion-item-options>
<ion-item lines="full">
<div class="grocery-item-container">
<span class="item-name">{{item.name}}</span>
<div class="item-details">
<div class="details-container">
<span class="label">Date Purchased:</span>
<span>{{item.datePurchased}}</span>
</div>
<div class="details-container">
<span class="label">Qty Left:</span>
<span class="qty">{{item.qty}}</span>
</div>
</div>
</div>
</ion-item>
</ion-item-sliding>
</ion-list>
<ng-template #noItemText>
<main class="no-item-section">
<div>
{{viewModel.noItemsText}}
</div>
</main>
</ng-template>
</ion-content>
注意事項pmt-mobile-toolbar
コンポーネント.これは、イオンのツールバーのコンポーネントのラッパーである私たちのmonorepoの別のライブラリです.viewModel$
. これは、このコンポーネントに必要なすべてのデータを含む観測可能です.使用するasync
角のアプリケーションのための最良の練習としてここでパイプ.component.ts
import { Component, OnInit } from '@angular/core';
import {
CurrentListStateService,
CurrentListViewModel,
} from '@pmt/grocery-list-organizer-business-logic-current-grocery-items';
import { Observable } from 'rxjs';
@Component({
selector: 'pmt-current-list',
templateUrl: './current-list.component.html',
styleUrls: ['./current-list.component.scss'],
providers: [CurrentListStateService],
})
export class CurrentListComponent implements OnInit {
viewModel$!: Observable<CurrentListViewModel>;
constructor(public currentListStateSvc: CurrentListStateService) {}
ngOnInit(): void {
this.viewModel$ = this.currentListStateSvc.getViewModel();
}
}
注意事項@pmt/grocery-list-organizer-business-logic-current-grocery-items
. これはmonorepoで作成したライブラリです.このライブラリは、この特定のコンポーネントを含むモジュールへのマップの1つです.また、インポートする項目はサービスとビューモデルの両方です.providedIn: root
使用する場合@Injectable
注釈.これは、このコンポーネントが作成され破棄されると、このサービスが作成および破棄されることを意味します.app.module.ts
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouteReuseStrategy } from '@angular/router';
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { IonicStorageModule } from '@ionic/storage-angular';
import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import {
GlobalEffects,
globalReducer,
} from '@pmt/grocery-list-organizer-shared-business-logic';
import { EffectsModule } from '@ngrx/effects';
import {
CurrentGroceryItemsEffects,
currentGroceryItemsReducer,
} from '@pmt/grocery-list-organizer-business-logic-current-grocery-items';
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [
BrowserAnimationsModule,
IonicModule.forRoot(),
IonicStorageModule.forRoot(),
StoreModule.forRoot({
app: globalReducer,
'current-list': currentGroceryItemsReducer,
}),
EffectsModule.forRoot([GlobalEffects, CurrentGroceryItemsEffects]),
StoreDevtoolsModule.instrument({}),
AppRoutingModule,
ReactiveFormsModule,
],
providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
bootstrap: [AppComponent],
})
export class AppModule {}
注意事項currentGroceryItemsReducer
and CurrentGroceryItemsEffects
). 他の怠惰なロードされたモジュールでは、特にモジュール内の状態のエクスポートをインポートできます.@pmt/grocery-list-organizer-business-logic-current-items
current-list-state service
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { CurrentGroceryItem } from '@pmt/grocery-list-organizer-shared-business-logic';
import { map, Observable } from 'rxjs';
import { getCurrentItems } from '..';
import {
decrementItemQty,
markItemAsUsed,
} from '../actions/current-grocery-items.actions';
import {
CurrentListState,
CurrentListViewModel,
} from '../models/current-list.interface';
@Injectable()
export class CurrentListStateService {
constructor(private _store: Store<CurrentListState>) {}
getViewModel(): Observable<CurrentListViewModel> {
const viewModel$ = this._store.select(getCurrentItems).pipe(
map((currentItems) => {
const itemsToReturn: CurrentGroceryItem[] = currentItems ?? [];
const viewModel: CurrentListViewModel = {
currentItems: itemsToReturn,
noItemsText: 'You currently have no items.',
};
return viewModel;
})
);
return viewModel$;
}
markItemAsUsed(usedItem: CurrentGroceryItem): void {
this._store.dispatch(markItemAsUsed({ usedItem }));
}
decrementItem(itemToDecrement: CurrentGroceryItem): void {
this._store.dispatch(decrementItemQty({ itemToDecrement }));
}
}
注意事項providedIn: root
に@Injectable
私たちが以前議論したように、ここで注釈.getViewModel
コンポーネントに渡すデータをオーケストレーションし、markItemAsUsed
and decrementItem
UIの相互作用を処理するだけで、ストアにアクションを送信します.actions.ts
import { createAction, props } from '@ngrx/store';
import { CurrentGroceryItem } from '@pmt/grocery-list-organizer-shared-business-logic';
export enum CurrentItemActionType {
LOAD_CURRENT_ITEMS = '[Current] Load Current Items',
LOAD_CURRENT_ITEMS_SUCCESS = '[Current] Load Current Items Success',
ADD_ITEM_TO_CURRENT_LIST = '[Current] Add Item to Current List',
MARK_ITEM_AS_USED = '[Current] Mark Item As Used',
DECREMENT_ITEM_QTY = '[Current] Decrement Item Qty',
}
export const loadCurrentItems = createAction(
CurrentItemActionType.LOAD_CURRENT_ITEMS
);
export const loadCurrentItemsSuccess = createAction(
CurrentItemActionType.LOAD_CURRENT_ITEMS_SUCCESS,
props<{ currentItems: CurrentGroceryItem[] }>()
);
export const addItemToCurrentList = createAction(
CurrentItemActionType.ADD_ITEM_TO_CURRENT_LIST,
props<{ itemToAdd: CurrentGroceryItem }>()
);
export const markItemAsUsed = createAction(
CurrentItemActionType.MARK_ITEM_AS_USED,
props<{ usedItem: CurrentGroceryItem }>()
);
export const decrementItemQty = createAction(
CurrentItemActionType.DECREMENT_ITEM_QTY,
props<{ itemToDecrement: CurrentGroceryItem }>()
);
reducer.ts
import { createReducer, on } from '@ngrx/store';
import {
addItemToCurrentList,
decrementItemQty,
loadCurrentItemsSuccess,
markItemAsUsed,
} from '../actions/current-grocery-items.actions';
import { CurrentListState } from '../models/current-list.interface';
const initialState: CurrentListState = {
currentItems: undefined,
};
export const currentGroceryItemsReducer = createReducer(
initialState,
on(loadCurrentItemsSuccess, (state, { currentItems }) => ({
...state,
currentItems,
})),
on(addItemToCurrentList, (state, { itemToAdd }) => {
const updatedItems = [...(state.currentItems ?? []), itemToAdd];
return { ...state, currentItems: updatedItems };
}),
on(markItemAsUsed, (state, { usedItem }) => {
const currentItems = state.currentItems?.filter(
(item) => item.id !== usedItem.id
);
return { ...state, currentItems };
}),
on(decrementItemQty, (state, { itemToDecrement }) => {
const updatedItems = state.currentItems?.map((item) => {
if (item.id === itemToDecrement.id) {
const updatedItem = { ...item, qty: itemToDecrement.qty - 1 };
return updatedItem;
}
return item;
});
return { ...state, currentItems: updatedItems };
})
);
effects.ts
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { initializeApp } from '@pmt/grocery-list-organizer-shared-business-logic';
import { tap } from 'rxjs';
import {
addItemToCurrentList,
decrementItemQty,
markItemAsUsed,
} from '../actions/current-grocery-items.actions';
import { CurrentGroceryItemsUtilService } from '../services/current-grocery-items-util.service';
@Injectable()
export class CurrentGroceryItemsEffects {
constructor(
private _actions$: Actions,
private _currentItemsUtilSvc: CurrentGroceryItemsUtilService
) {}
initAppLoadItems$ = createEffect(
() =>
this._actions$.pipe(
ofType(initializeApp),
tap(() => this._currentItemsUtilSvc.loadItemsFromStorage())
),
{ dispatch: false }
);
addItemToCurrentListUpdateStorage$ = createEffect(
() =>
this._actions$.pipe(
ofType(addItemToCurrentList),
tap((action) => {
this._currentItemsUtilSvc.addItemToCurrentListOnStorage(
action.itemToAdd
);
})
),
{ dispatch: false }
);
markItemAsUsed$ = createEffect(
() =>
this._actions$.pipe(
ofType(markItemAsUsed),
tap((action) => {
this._currentItemsUtilSvc.updateStorageAfterItemMarkedAsUsed(
action.usedItem
);
})
),
{ dispatch: false }
);
decrementItemUpdateStorage$ = createEffect(
() =>
this._actions$.pipe(
ofType(decrementItemQty),
tap((action) => {
this._currentItemsUtilSvc.updateStoargeAfterDecrementItem(
action.itemToDecrement
);
})
),
{ dispatch: false }
);
}
注意事項index.ts
export * from './lib/actions/current-grocery-items.actions';
export * from './lib/reducer/current-grocery-items.reducer';
export * from './lib/effects/current-grocery-items.effects';
export * from './lib/index';
export { CurrentListStateService } from './lib/services/current-list-state.service';
export * from './lib/models/current-list.interface';
注意事項結論
私は、私たちのアプリケーションのビジネスロジックからUIの部分を分離するためにNXを使用する私のアプローチにこの記事を楽しんだことを願っています.うまくいけば、すべてのそれを試してみて、私はそれがあなたのためにどのように動作するかをお知らせください.あなたはTwitter経由で私に到達することができます
@paulmojicatech
. ハッピーコーディング!Reference
この問題について(NXを用いたUIプレゼンロジックからのビジネスロジックの分離), 我々は、より多くの情報をここで見つけました https://dev.to/paulmojicatech/separate-business-logic-from-ui-presenational-logic-using-nx-5794テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol