Skip to content

Commit 2ba208c

Browse files
committed
feat: todos logic
1 parent 878662e commit 2ba208c

21 files changed

+215
-51
lines changed

src/app/app.component.scss-theme.scss

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@
2020
}
2121
}
2222
}
23+
2324
.footer {
24-
color: mat-color($primary, A700);
25+
color: mat-color($primary, lighter);
2526
background-color: mat-color($primary);
2627

2728
.links {
2829
a {
29-
color: mat-color($primary, A700);
30+
color: mat-color($primary, lighter);
3031
&:hover {
3132
color: mat-color($accent);
3233
}

src/app/core/core.module.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ export function reducerAoT(state, action) {
2828
@NgModule({
2929
imports: [
3030
CommonModule,
31-
StoreModule.provideStore(reducerAoT),
32-
EffectsModule.run(SettingsEffects)
31+
StoreModule.provideStore(reducerAoT)
3332
],
3433
declarations: [],
3534
providers: [LocalStorageService]

src/app/examples/examples.module.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { NgModule } from '@angular/core';
22
import { Store } from '@ngrx/store';
3+
import { EffectsModule } from '@ngrx/effects';
34

45
import { CoreModule, createReducer } from '../core';
56
import { SharedModule } from '../shared';
@@ -11,6 +12,7 @@ import { StockMarketComponent } from './stock-market/stock-market.component';
1112
import { StockMarketService } from './stock-market/stock-market.service';
1213

1314
import { todosReducer } from './todos/todos.reducer';
15+
import { TodosEffects } from './todos/todos.effects';
1416

1517
export const appReducerWithExamples = createReducer({
1618
todos: todosReducer
@@ -20,7 +22,8 @@ export const appReducerWithExamples = createReducer({
2022
imports: [
2123
CoreModule,
2224
SharedModule,
23-
ExamplesRoutingModule
25+
ExamplesRoutingModule,
26+
EffectsModule.run(TodosEffects)
2427
],
2528
declarations: [TodosComponent, ExamplesComponent, StockMarketComponent],
2629
providers: [StockMarketService]

src/app/examples/todos/todos.component.html

+40-8
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,57 @@
11
<div class="container">
22
<div class="row">
33
<div class="offset-md-2 col-md-8 entry">
4-
<anms-big-input placeholder="I want to do..."
4+
<anms-big-input placeholder="I am going to do..."
55
[value]="newTodo"
66
(keyup)="onNewTodoChange($event.target.value)"
7-
(keyup.enter)="onAddTodo()">
7+
(keyup.enter)="!isAddTodoDisabled && onAddTodo()">
88
<anms-big-input-action icon="add" color="accent"
9-
(click)="onAddTodo()"
10-
[disabled]="newTodo.length < 4">
9+
(action)="onAddTodo()"
10+
[disabled]="isAddTodoDisabled">
11+
</anms-big-input-action>
12+
<anms-big-input-action icon="delete_forever"
13+
(action)="onRemoveDoneTodos()"
14+
[disabled]="isRemoveDoneTodosDisabled">
1115
</anms-big-input-action>
12-
<anms-big-input-action icon="delete_forever" disabled></anms-big-input-action>
1316
</anms-big-input>
1417
</div>
1518
</div>
1619
<div class="row">
1720
<div class="col-md-6">
18-
<h2>Todo List</h2>
19-
<md-card *ngFor="let todo of todos.items" class="todo">
20-
{{todo.name}}
21+
<h2>
22+
Todo List
23+
<button class="todos-filter" md-icon-button [mdMenuTriggerFor]="todosFilter">
24+
<md-icon>filter_list</md-icon>
25+
</button>
26+
<md-menu class="todos-filter-menu" #todosFilter="mdMenu" xPosition="before">
27+
<button md-menu-item (click)="onFilterTodos('ALL')" [ngClass]="{ active: todos.filter === 'ALL' }">
28+
<md-icon>assignment</md-icon>
29+
<span>All</span>
30+
</button>
31+
<button md-menu-item (click)="onFilterTodos('DONE')" [ngClass]="{ active: todos.filter === 'DONE' }">
32+
<md-icon>done</md-icon>
33+
<span>Done</span>
34+
</button>
35+
<button md-menu-item (click)="onFilterTodos('ACTIVE')" [ngClass]="{ active: todos.filter === 'ACTIVE' }">
36+
<md-icon>check_box_outline_blank</md-icon>
37+
<span>Active</span>
38+
</button>
39+
</md-menu>
40+
<md-chip-list class="todos-filter-info">
41+
<md-chip>
42+
Displaying {{todos.filter !== 'ALL' ? filteredTodos.length : ''}}
43+
{{todos.filter.toLowerCase()}}
44+
{{todos.filter === 'ALL' ? filteredTodos.length : ''}}
45+
todo{{filteredTodos.length > 1 ? 's' : ''}}
46+
</md-chip>
47+
</md-chip-list>
48+
</h2>
49+
<md-card *ngFor="let todo of filteredTodos" class="todo">
50+
<md-checkbox class="todo-done" [checked]="todo.done" (change)="onToggleTodo(todo)"></md-checkbox>
51+
<span class="todo-label" (click)="onToggleTodo(todo)">{{todo.name}}</span>
2152
</md-card>
2253
<br>
54+
<br>
2355
</div>
2456
<div class="offset-md-1 col-md-5">
2557
<h2>Todo Example</h2>

src/app/examples/todos/todos.component.scss

+24
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,30 @@
33
margin-bottom: 40px;
44
}
55

6+
.todos-filter-info {
7+
float: right;
8+
font-weight: normal;
9+
}
10+
11+
.todos-filter {
12+
float: right;
13+
position: relative;
14+
left: 10px;
15+
top: -5px;
16+
margin-left: -10px;
17+
}
18+
619
.todo {
20+
display: flex;
721
margin-bottom: 10px;
22+
23+
.todo-done {
24+
margin: 0 20px 0 0;
25+
}
26+
27+
.todo-label {
28+
position: relative;
29+
top: 2px;
30+
cursor: pointer;
31+
}
832
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
@import '~@angular/material/theming';
2+
3+
@mixin todos-component-theme($theme) {
4+
$accent: map-get($theme, accent);
5+
6+
.todos-filter-menu {
7+
.active {
8+
color: mat-color($accent, default-contrast);
9+
background-color: mat-color($accent);
10+
11+
&:hover {
12+
color: mat-color($accent, default-contrast);
13+
background-color: mat-color($accent, darker);
14+
}
15+
}
16+
}
17+
}
18+

src/app/examples/todos/todos.component.ts

+38-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { Subject } from 'rxjs/Subject';
44
import 'rxjs/add/operator/takeUntil';
55
import 'rxjs/add/operator/map';
66

7-
import { addTodo } from './todos.reducer';
7+
import {
8+
addTodo, persistTodos, toggleTodo, removeDoneTodos, Todo, filterTodos,
9+
TodoFilter
10+
} from './todos.reducer';
811

912
@Component({
1013
selector: 'anms-todos',
@@ -18,11 +21,6 @@ export class TodosComponent implements OnInit, OnDestroy {
1821
todos: any;
1922
newTodo = '';
2023

21-
inputActions = [
22-
{ type: 'add', icon: 'add', color: 'accent'},
23-
{ type: 'remove', icon: 'delete_forever'}
24-
];
25-
2624
constructor(
2725
public store: Store<any>
2826
) {}
@@ -31,7 +29,10 @@ export class TodosComponent implements OnInit, OnDestroy {
3129
this.store
3230
.select('todos')
3331
.takeUntil(this.unsubscribe$)
34-
.subscribe(todos => (this.todos = todos));
32+
.subscribe(todos => {
33+
this.todos = todos;
34+
this.store.dispatch(persistTodos(todos));
35+
});
3536
}
3637

3738

@@ -40,6 +41,24 @@ export class TodosComponent implements OnInit, OnDestroy {
4041
this.unsubscribe$.complete();
4142
}
4243

44+
get filteredTodos() {
45+
const filter = this.todos.filter;
46+
if (filter === 'ALL') {
47+
return this.todos.items;
48+
} else {
49+
const predicate = filter === 'DONE' ? t => t.done : t => !t.done;
50+
return this.todos.items.filter(predicate);
51+
}
52+
}
53+
54+
get isAddTodoDisabled() {
55+
return this.newTodo.length < 4;
56+
}
57+
58+
get isRemoveDoneTodosDisabled() {
59+
return this.todos.items.filter(item => item.done).length === 0;
60+
}
61+
4362
onNewTodoChange(newTodo: string) {
4463
this.newTodo = newTodo;
4564
}
@@ -49,4 +68,16 @@ export class TodosComponent implements OnInit, OnDestroy {
4968
this.newTodo = '';
5069
}
5170

71+
onToggleTodo(todo: Todo) {
72+
this.store.dispatch(toggleTodo(todo.id));
73+
}
74+
75+
onRemoveDoneTodos() {
76+
this.store.dispatch(removeDoneTodos());
77+
}
78+
79+
onFilterTodos(filter: TodoFilter) {
80+
this.store.dispatch(filterTodos(filter));
81+
}
82+
5283
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Injectable } from '@angular/core';
2+
import { Actions, Effect } from '@ngrx/effects';
3+
import { Action } from '@ngrx/store';
4+
import { Observable } from 'rxjs/Observable';
5+
6+
import { LocalStorageService } from '../../core';
7+
8+
import {
9+
TODOS_KEY,
10+
TODOS_PERSIST,
11+
} from './todos.reducer';
12+
13+
@Injectable()
14+
export class TodosEffects {
15+
16+
constructor(
17+
private actions$: Actions,
18+
private localStorageService: LocalStorageService
19+
) {}
20+
21+
@Effect({ dispatch: false }) persistTodos(): Observable<Action> {
22+
return this.actions$
23+
.ofType(TODOS_PERSIST)
24+
.do(action => this.localStorageService
25+
.setItem(TODOS_KEY, action.payload));
26+
}
27+
28+
}

src/app/examples/todos/todos.reducer.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { v4 as uuid } from 'node-uuid';
33

44
export const initialState = {
55
items: [
6-
{ id: uuid(), name: 'Check the other example', done: false }
6+
{ id: uuid(), name: 'Open Todo list example', done: true },
7+
{ id: uuid(), name: 'Check the other examples', done: false },
8+
{ id: uuid(), name: 'Use Angular ngRx Material Starter in your project', done: false }
79
],
810
filter: 'ALL'
911
};
@@ -15,10 +17,12 @@ export const TODOS_ADD = 'TODOS_ADD';
1517
export const TODOS_TOGGLE = 'TODOS_TOGGLE';
1618
export const TODOS_REMOVE_DONE = 'TODOS_REMOVE_DONE';
1719
export const TODOS_FILTER = 'TODOS_FILTER';
20+
export const TODOS_PERSIST = 'TODOS_PERSIST';
1821

1922
export const addTodo = (name: string) => ({ type: TODOS_ADD, payload: name });
2023
export const toggleTodo = (id: string) => ({ type: TODOS_TOGGLE, payload: id });
2124
export const removeDoneTodos = () => ({ type: TODOS_REMOVE_DONE });
25+
export const persistTodos = (todos) => ({ type: TODOS_PERSIST, payload: todos });
2226
export const filterTodos = (filter: TodoFilter) =>
2327
({ type: TODOS_FILTER, payload: filter });
2428

@@ -31,7 +35,7 @@ export function todosReducer(state = initialState, action: Action) {
3135
});
3236

3337
case TODOS_TOGGLE:
34-
state.items.some((item: any) => {
38+
state.items.some((item: Todo) => {
3539
if (item.id === action.payload) {
3640
item.done = !item.done;
3741
return true;
@@ -43,7 +47,7 @@ export function todosReducer(state = initialState, action: Action) {
4347

4448
case TODOS_REMOVE_DONE:
4549
return Object.assign({}, state,
46-
{ items: state.items.filter((item: any) => !item.done) });
50+
{ items: state.items.filter((item: Todo) => !item.done) });
4751

4852
case TODOS_FILTER:
4953
return Object.assign({}, state, { filter: action.payload });
@@ -53,3 +57,8 @@ export function todosReducer(state = initialState, action: Action) {
5357
}
5458
}
5559

60+
export interface Todo {
61+
id: string;
62+
name: string;
63+
done: boolean;
64+
}

src/app/settings/settings.effects.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { LocalStorageService } from '../core';
88
import {
99
SETTINGS_KEY,
1010
SETTINGS_CHANGE_THEME,
11-
useThemeAction
1211
} from './settings.reducer';
1312

1413
@Injectable()
@@ -19,12 +18,11 @@ export class SettingsEffects {
1918
private localStorageService: LocalStorageService
2019
) {}
2120

22-
@Effect() persistThemeSettings(): Observable<Action> {
21+
@Effect({ dispatch: false }) persistThemeSettings(): Observable<Action> {
2322
return this.actions$
2423
.ofType(SETTINGS_CHANGE_THEME)
2524
.do(action => this.localStorageService
26-
.setItem(SETTINGS_KEY, { theme: action.payload }))
27-
.map(action => useThemeAction(action.payload));
25+
.setItem(SETTINGS_KEY, { theme: action.payload }));
2826
}
2927

3028
}

src/app/settings/settings.module.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import { NgModule } from '@angular/core';
22
import { CommonModule } from '@angular/common';
3+
import { EffectsModule } from '@ngrx/effects';
34

45
import { SharedModule } from '../shared';
56

67
import { SettingsComponent } from './settings/settings.component';
8+
import { SettingsEffects } from './settings.effects';
79

810
@NgModule({
911
imports: [
1012
CommonModule,
11-
SharedModule
13+
SharedModule,
14+
EffectsModule.run(SettingsEffects)
1215
],
1316
declarations: [SettingsComponent]
1417
})

src/app/settings/settings.reducer.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,13 @@ export const initialState = {
66

77
export const SETTINGS_KEY = 'SETTINGS';
88
export const SETTINGS_CHANGE_THEME = 'SETTINGS_CHANGE_THEME';
9-
export const SETTINGS_USE_THEME = 'SETTINGS_USE_THEME';
109

1110
export const changeThemeAction = (theme: string) =>
1211
({ type: SETTINGS_CHANGE_THEME, payload: theme });
1312

14-
export const useThemeAction = (theme: string) =>
15-
({ type: SETTINGS_USE_THEME, payload: theme });
16-
1713
export function settingsReducer(state = initialState, action: Action) {
1814
switch (action.type) {
19-
case SETTINGS_USE_THEME:
15+
case SETTINGS_CHANGE_THEME:
2016
return { theme: action.payload };
2117

2218
default:
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<button md-raised-button [color]="color" [disabled]="disabled">
1+
<button md-raised-button [color]="color" [disabled]="disabled" (click)="onClick()">
22
<md-icon *ngIf="icon">{{icon}}</md-icon>
33
<span *ngIf="label">{{label}}</span>
44
</button>

0 commit comments

Comments
 (0)