Skip to content

Commit

Permalink
feat(redux): introduce withRedux extension
Browse files Browse the repository at this point in the history
  • Loading branch information
rainerhahnekamp committed Dec 19, 2023
1 parent 0fa5e67 commit c5fa930
Show file tree
Hide file tree
Showing 16 changed files with 532 additions and 96 deletions.
4 changes: 2 additions & 2 deletions apps/demo/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
"error",
{
"type": "attribute",
"prefix": "ngrxToolkit",
"prefix": "demo",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "ngrx-toolkit",
"prefix": "demo",
"style": "kebab-case"
}
]
Expand Down
47 changes: 5 additions & 42 deletions apps/demo/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,43 +1,6 @@
<mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!-- Checkbox Column -->
<ng-container matColumnDef="finished">
<mat-header-cell *matHeaderCellDef></mat-header-cell>
<mat-cell *matCellDef="let row" class="actions">
<mat-checkbox
(click)="$event.stopPropagation()"
(change)="checkboxLabel(row)"
[checked]="row.finished"
>
</mat-checkbox>
<mat-icon (click)="removeTodo(row)">delete</mat-icon>
</mat-cell>
</ng-container>
<ul>
<li><a routerLink="/todo">Todo - DevTools Showcase</a></li>
<li><a routerLink="/flight-search">Flight Search - withRedux Showcase</a></li>
</ul>

<!-- Name Column -->
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef>Name</mat-header-cell>
<mat-cell *matCellDef="let element">{{ element.name }}</mat-cell>
</ng-container>

<!-- Description Column -->
<ng-container matColumnDef="description">
<mat-header-cell *matHeaderCellDef>Description</mat-header-cell>
<mat-cell *matCellDef="let element">{{ element.description }}</mat-cell>
</ng-container>

<!-- Deadline Column -->
<ng-container matColumnDef="deadline">
<mat-header-cell mat-header-cell *matHeaderCellDef
>Deadline</mat-header-cell
>
<mat-cell mat-cell *matCellDef="let element">{{
element.deadline
}}</mat-cell>
</ng-container>

<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row
*matRowDef="let row; columns: displayedColumns"
(click)="selection.toggle(row)"
></mat-row>
</mat-table>
<router-outlet />
10 changes: 8 additions & 2 deletions apps/demo/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@ import { Component, effect, inject } from '@angular/core';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { Todo, TodoStore } from './todo-store';
import { JsonPipe } from '@angular/common';
import { MatIconModule } from '@angular/material/icon';
import { CategoryStore } from './category.store';
import { RouterLink, RouterOutlet } from '@angular/router';

@Component({
selector: 'demo-root',
templateUrl: './app.component.html',
standalone: true,
imports: [MatTableModule, MatCheckboxModule, MatIconModule],
imports: [
MatTableModule,
MatCheckboxModule,
MatIconModule,
RouterLink,
RouterOutlet,
],
styleUrl: './app.component.css',
})
export class AppComponent {
Expand Down
8 changes: 7 additions & 1 deletion apps/demo/src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser';
import { provideAnimations } from '@angular/platform-browser/animations';
import { provideHttpClient } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
providers: [provideClientHydration(), provideRouter(appRoutes), provideAnimations()],
providers: [
provideClientHydration(),
provideRouter(appRoutes),
provideAnimations(),
provideHttpClient(),
],
};
7 changes: 6 additions & 1 deletion apps/demo/src/app/app.routes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { Route } from '@angular/router';
import { TodoComponent } from './todo/todo.component';
import { FlightSearchComponent } from './flight-search/flight-search.component';

export const appRoutes: Route[] = [];
export const appRoutes: Route[] = [
{ path: 'todo', component: TodoComponent },
{ path: 'flight-search', component: FlightSearchComponent },
];
45 changes: 45 additions & 0 deletions apps/demo/src/app/flight-search/flight-search.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<form (ngSubmit)="search()">
<div>
<mat-form-field>
<mat-label>Name</mat-label>
<input [(ngModel)]="searchParams.from" name="from" matInput />
</mat-form-field>
</div>

<div>
<mat-form-field>
<mat-label>Name</mat-label>
<input [(ngModel)]="searchParams.to" name="to" matInput />
</mat-form-field>
</div>

<button mat-raised-button>Search</button>
</form>

<mat-table [dataSource]="dataSource">
<!-- From Column -->
<ng-container matColumnDef="from">
<mat-header-cell *matHeaderCellDef>From</mat-header-cell>
<mat-cell *matCellDef="let element">{{ element.from }}</mat-cell>
</ng-container>

<!-- To Column -->
<ng-container matColumnDef="to">
<mat-header-cell *matHeaderCellDef>To</mat-header-cell>
<mat-cell *matCellDef="let element">{{ element.to }}</mat-cell>
</ng-container>

<!-- Date Column -->
<ng-container matColumnDef="date">
<mat-header-cell mat-header-cell *matHeaderCellDef>Date</mat-header-cell>
<mat-cell mat-cell *matCellDef="let element">{{
element.date | date
}}</mat-cell>
</ng-container>

<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row
*matRowDef="let row; columns: displayedColumns"
(click)="selection.toggle(row)"
></mat-row>
</mat-table>
40 changes: 40 additions & 0 deletions apps/demo/src/app/flight-search/flight-search.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Component, effect, inject } from '@angular/core';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { DatePipe } from '@angular/common';
import { SelectionModel } from '@angular/cdk/collections';
import { Flight } from './flight';
import { FlightStore } from './flight-store';
import { MatInputModule } from '@angular/material/input';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';

@Component({
selector: 'demo-flight-search',
templateUrl: 'flight-search.component.html',
standalone: true,
imports: [
MatTableModule,
DatePipe,
MatInputModule,
FormsModule,
MatButtonModule,
],
})
export class FlightSearchComponent {
searchParams: { from: string; to: string } = { from: 'Paris', to: 'London' };
flightStore = inject(FlightStore);

displayedColumns: string[] = ['from', 'to', 'date'];
dataSource = new MatTableDataSource<Flight>([]);
selection = new SelectionModel<Flight>(true, []);

constructor() {
effect(() => {
this.dataSource.data = this.flightStore.flights();
});
}

search() {
this.flightStore.loadFlights(this.searchParams);
}
}
53 changes: 53 additions & 0 deletions apps/demo/src/app/flight-search/flight-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { signalStore, withState } from '@ngrx/signals';
import {
noPayload,
payload,
withDevtools,
withRedux,
patchState,
} from 'ngrx-toolkit';
import { inject } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { map, switchMap } from 'rxjs';
import { Flight } from './flight';

export const FlightStore = signalStore(
{ providedIn: 'root' },
withDevtools('flights'),
withState({ flights: [] as Flight[] }),
withRedux({
actions: {
public: {
loadFlights: payload<{ from: string; to: string }>(),
delayFirst: noPayload,
},
private: {
flightsLoaded: payload<{ flights: Flight[] }>(),
},
},

reducer: (actions, on) => {
on(actions.flightsLoaded, ({ flights }, state) => {
patchState(state, 'flights loaded', { flights });
});
},

effects: (actions, create) => {
const httpClient = inject(HttpClient);

return {
loadFlights$: create(actions.loadFlights).pipe(
switchMap(({ from, to }) => {
return httpClient.get<Flight[]>(
'https://demo.angulararchitects.io/api/flight',
{
params: new HttpParams().set('from', from).set('to', to),
}
);
}),
map((flights) => actions.flightsLoaded({ flights }))
),
};
},
})
);
7 changes: 7 additions & 0 deletions apps/demo/src/app/flight-search/flight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface Flight {
id: number;
from: string;
to: string;
delayed: boolean;
date: Date;
}
43 changes: 43 additions & 0 deletions apps/demo/src/app/todo/todo.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!-- Checkbox Column -->
<ng-container matColumnDef="finished">
<mat-header-cell *matHeaderCellDef></mat-header-cell>
<mat-cell *matCellDef="let row" class="actions">
<mat-checkbox
(click)="$event.stopPropagation()"
(change)="checkboxLabel(row)"
[checked]="row.finished"
>
</mat-checkbox>
<mat-icon (click)="removeTodo(row)">delete</mat-icon>
</mat-cell>
</ng-container>

<!-- Name Column -->
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef>Name</mat-header-cell>
<mat-cell *matCellDef="let element">{{ element.name }}</mat-cell>
</ng-container>

<!-- Description Column -->
<ng-container matColumnDef="description">
<mat-header-cell *matHeaderCellDef>Description</mat-header-cell>
<mat-cell *matCellDef="let element">{{ element.description }}</mat-cell>
</ng-container>

<!-- Deadline Column -->
<ng-container matColumnDef="deadline">
<mat-header-cell mat-header-cell *matHeaderCellDef
>Deadline</mat-header-cell
>
<mat-cell mat-cell *matCellDef="let element">{{
element.deadline
}}</mat-cell>
</ng-container>

<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row
*matRowDef="let row; columns: displayedColumns"
(click)="selection.toggle(row)"
></mat-row>
</mat-table>
5 changes: 5 additions & 0 deletions apps/demo/src/app/todo/todo.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.actions{
display: flex;
align-items: center;

}
37 changes: 37 additions & 0 deletions apps/demo/src/app/todo/todo.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Component, effect, inject } from '@angular/core';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatIconModule } from '@angular/material/icon';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { Todo, TodoStore } from '../todo-store';
import { CategoryStore } from '../category.store';
import { SelectionModel } from '@angular/cdk/collections';

@Component({
selector: 'demo-todo',
templateUrl: 'todo.component.html',
styleUrl: 'todo.component.scss',
standalone: true,
imports: [MatCheckboxModule, MatIconModule, MatTableModule],
})
export class TodoComponent {
todoStore = inject(TodoStore);
categoryStore = inject(CategoryStore);

displayedColumns: string[] = ['finished', 'name', 'description', 'deadline'];
dataSource = new MatTableDataSource<Todo>([]);
selection = new SelectionModel<Todo>(true, []);

constructor() {
effect(() => {
this.dataSource.data = this.todoStore.entities();
});
}

checkboxLabel(todo: Todo) {
this.todoStore.toggleFinished(todo.id);
}

removeTodo(todo: Todo) {
this.todoStore.remove(todo.id);
}
}
1 change: 1 addition & 0 deletions libs/ngrx-toolkit/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { withDevtools, patchState, Action } from './lib/with-devtools';
export * from './lib/with-redux';
9 changes: 9 additions & 0 deletions libs/ngrx-toolkit/src/lib/assertions/assertions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ActionsFnSpecs } from '../with-redux';

export function assertActionFnSpecs(
obj: unknown
): asserts obj is ActionsFnSpecs {
if (!obj || typeof obj !== 'object') {
throw new Error('%o is not an Action Specification');
}
}
Loading

0 comments on commit c5fa930

Please sign in to comment.