-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support dispatch multiple actions (#133)
- Loading branch information
Showing
13 changed files
with
478 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { ACTION_ID_PREFIX } from '../types'; | ||
type ExtractTypeToPayload<T> = T extends [...infer A, never] ? ExtractTypeToPayload<A> : T extends Array<any> ? T : never; | ||
|
||
export interface ActionRef<T = any> { | ||
type: string; | ||
payload: ExtractTypeToPayload<T>; | ||
} | ||
|
||
export type ActionCreator<T = any> = (...payload: ExtractTypeToPayload<T>) => ActionRef<T>; | ||
|
||
let internalMaxActionId = 0; | ||
|
||
function internalCreateActionId(actionType: string): string { | ||
const name = `${ACTION_ID_PREFIX}${actionType}_${internalMaxActionId}`; | ||
internalMaxActionId++; | ||
return name; | ||
} | ||
|
||
export function defineAction<T1 = never, T2 = never, T3 = never, T4 = never>(type: string): ActionCreator<[T1, T2, T3, T4]> { | ||
const actionId = internalCreateActionId(type); | ||
function creator(...payload: ExtractTypeToPayload<[T1, T2, T3, T4]>) { | ||
return Object.defineProperty( | ||
{ | ||
type: type, | ||
payload: payload | ||
}, | ||
'id', | ||
{ | ||
value: actionId, | ||
writable: false, | ||
enumerable: false, | ||
configurable: false | ||
} | ||
); | ||
} | ||
creator.prototype.id = actionId; | ||
return creator; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { Injectable } from '@angular/core'; | ||
import { ActionRef } from './action/action-definition'; | ||
import { isObject, isUndefinedOrNull } from '@tethys/cdk'; | ||
import { InternalStoreFactory } from './internals/internal-store-factory'; | ||
import { InternalDispatcher } from './internals/dispatcher'; | ||
import { StoreMetaInfo } from './inner-types'; | ||
import { META_KEY } from './types'; | ||
@Injectable({ | ||
providedIn: 'root' | ||
}) | ||
export class Dispatcher { | ||
constructor() {} | ||
|
||
public dispatch(action: ActionRef) { | ||
dispatch(action); | ||
} | ||
} | ||
|
||
export function dispatch(action: ActionRef) { | ||
if (!isObject(action)) { | ||
throw new TypeError(`Action must be object`); | ||
} else if (isUndefinedOrNull(action.type)) { | ||
throw new TypeError(`Action must have a type property`); | ||
} | ||
invokeActions(action); | ||
} | ||
|
||
function invokeActions(action: ActionRef) { | ||
InternalStoreFactory.instance.getAllStores().forEach((store) => { | ||
const meta = store[META_KEY] as StoreMetaInfo; | ||
if (!meta || !meta.actions || !meta.actions[action['id']]) { | ||
return; | ||
} | ||
const actionMeta = meta.actions[action['id']]; | ||
InternalDispatcher.instance.dispatch(store.defaultStoreInstanceId, actionMeta, () => { | ||
return actionMeta.originalFn.call(store, ...action.payload); | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { defineAction } from '@tethys/store'; | ||
|
||
export const updateTitle = defineAction<string, { title: string }>('updateTitle'); | ||
|
||
export const updateContent = defineAction<string, { content: string }>('updateContent'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
--- | ||
title: Dispatch Actions | ||
order: 11 | ||
--- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { Injectable } from '@angular/core'; | ||
import { Action, EntityState, EntityStore } from '@tethys/store'; | ||
import { of } from 'rxjs'; | ||
import { tap } from 'rxjs/operators'; | ||
import { updateTitle } from './actions'; | ||
|
||
export interface Page { | ||
_id: string; | ||
title: string; | ||
} | ||
|
||
interface PagesState extends EntityState<Page> {} | ||
|
||
@Injectable({ providedIn: 'root' }) | ||
export class PagesStore extends EntityStore<PagesState, Page> { | ||
constructor() { | ||
super({ entities: [] }, {}); | ||
} | ||
|
||
@Action() | ||
fetchPages() { | ||
const data = { | ||
pages: [ | ||
{ _id: '1', title: 'First Page Title' }, | ||
{ _id: '2', title: 'Second Page Title' }, | ||
{ _id: '3', title: 'Third Page Title' }, | ||
{ _id: '4', title: 'Fourth Page Title' } | ||
] | ||
}; | ||
return of(data).pipe( | ||
tap((data) => { | ||
this.initialize(data.pages); | ||
}) | ||
); | ||
} | ||
|
||
@Action(updateTitle) | ||
pureUpdateTitle(_id: string, payload: { title: string }) { | ||
this.update(_id, { title: payload.title }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { Injectable, inject } from '@angular/core'; | ||
import { Action, dispatch, Store } from '@tethys/store'; | ||
import { of } from 'rxjs'; | ||
import { tap } from 'rxjs/operators'; | ||
import { updateContent, updateTitle } from './actions'; | ||
|
||
interface PageDetailState { | ||
detail: { _id: string; title: string; content: string }; | ||
} | ||
|
||
@Injectable() | ||
export class PageDetailStore extends Store<PageDetailState> { | ||
static detailSelector(state: PageDetailState) { | ||
return state.detail; | ||
} | ||
|
||
constructor() { | ||
super({}); | ||
} | ||
|
||
@Action(updateTitle) | ||
pureUpdateTitle(_id: string, payload: { title: string }) { | ||
if (_id === this.snapshot.detail._id) { | ||
this.update({ | ||
detail: { | ||
...this.snapshot.detail, | ||
...payload | ||
} | ||
}); | ||
} | ||
} | ||
|
||
@Action(updateContent) | ||
pureUpdateContent(_id: string, payload: { content: string }) { | ||
if (_id === this.snapshot.detail._id) { | ||
this.update({ | ||
detail: { | ||
...this.snapshot.detail, | ||
...payload | ||
} | ||
}); | ||
} | ||
} | ||
|
||
@Action() | ||
fetchPageDetail() { | ||
const data = { | ||
pageDetail: { _id: '1', title: 'First Page Title', content: 'First Page Detail Content' } | ||
}; | ||
return of(data).pipe( | ||
tap((data) => { | ||
this.update({ | ||
detail: { | ||
...this.snapshot.detail, | ||
...data.pageDetail | ||
} | ||
}); | ||
}) | ||
); | ||
} | ||
|
||
@Action() | ||
updateTitle() { | ||
return of(true).pipe( | ||
tap(() => { | ||
dispatch(updateTitle('1', { title: 'New First Page Title' })); | ||
}) | ||
); | ||
} | ||
|
||
@Action() | ||
updateContent() { | ||
return of(true).pipe( | ||
tap(() => { | ||
dispatch(updateContent('1', { content: 'New First Page Content' })); | ||
}) | ||
); | ||
} | ||
|
||
@Action() | ||
resetTitle() { | ||
return of(true).pipe( | ||
tap(() => { | ||
dispatch(updateTitle('1', { title: 'First Page Title' })); | ||
}) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<div class="page-container"> | ||
<div class="sidebar"> | ||
<ng-container *ngIf="pagesStore.entities$ | async as pages"> | ||
<p *ngFor="let page of pages"> | ||
{{ page.title }} | ||
</p> | ||
</ng-container> | ||
</div> | ||
|
||
<div class="detail"> | ||
<ng-container *ngIf="pageDetail$ | async as pageDetail"> | ||
<h3>{{ pageDetail.title }}</h3> | ||
<p>{{ pageDetail.content }}</p> | ||
</ng-container> | ||
<button class="dg-btn dg-btn-primary dg-btn-xsm" (click)="updateTitle()">更新First标题</button> | ||
<button class="dg-btn dg-btn-primary dg-btn-xsm" (click)="updateContent()">更新First内容</button> | ||
</div> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
.page-container { | ||
width: 700px; | ||
display: flex; | ||
flex-direction: row; | ||
background: #fafafa; | ||
.sidebar { | ||
max-width: 200px; | ||
border-right: 1px solid; | ||
padding-right: 20px; | ||
p { | ||
padding: 4px; | ||
border-bottom: 1px solid #ccc; | ||
} | ||
} | ||
.detail { | ||
flex: 1; | ||
padding-left: 20px; | ||
.dg-btn { | ||
margin-top: 10px; | ||
margin-right: 8px; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { Component, OnInit } from '@angular/core'; | ||
import { PageDetailStore } from './page.store'; | ||
import { PagesStore } from './page-list.store'; | ||
import { Observable } from 'rxjs'; | ||
|
||
@Component({ | ||
selector: 'thy-store-pages-example', | ||
templateUrl: './pages.component.html', | ||
styleUrls: ['./pages.component.scss'], | ||
providers: [PageDetailStore, PagesStore] | ||
}) | ||
export class ThyStorePagesExampleComponent implements OnInit { | ||
constructor(public pagesStore: PagesStore, public pageDetailStore: PageDetailStore) {} | ||
|
||
pageDetail$: Observable<any> = this.pageDetailStore.select(PageDetailStore.detailSelector); | ||
|
||
ngOnInit() { | ||
this.fetchPages(); | ||
this.fetchPageDetail(); | ||
} | ||
|
||
fetchPages() { | ||
this.pagesStore.fetchPages(); | ||
} | ||
|
||
fetchPageDetail() { | ||
this.pageDetailStore.fetchPageDetail(); | ||
} | ||
|
||
updateTitle() { | ||
this.pageDetailStore.updateTitle(); | ||
} | ||
|
||
updateContent() { | ||
this.pageDetailStore.updateContent(); | ||
} | ||
} |
Oops, something went wrong.