Skip to content

Commit bc7e005

Browse files
authored
Merge pull request #74 from ioet/63-read-activities
closes #63
2 parents b2f3cd0 + c351059 commit bc7e005

16 files changed

+259
-41
lines changed

package-lock.json

Lines changed: 19 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
"@angular/platform-browser": "~9.0.3",
2121
"@angular/platform-browser-dynamic": "~9.0.3",
2222
"@angular/router": "~9.0.3",
23+
"@ngrx/effects": "^9.0.0",
24+
"@ngrx/store": "^9.0.0",
25+
"@ngrx/store-devtools": "^9.0.0",
2326
"bootstrap": "^4.4.1",
2427
"jquery": "^3.4.1",
2528
"minimist": "^1.2.5",

src/app/app.module.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { BrowserModule } from '@angular/platform-browser';
33
import { NgModule } from '@angular/core';
44
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
55
import { HttpClientModule } from '@angular/common/http';
6+
import { StoreModule } from '@ngrx/store';
7+
import { EffectsModule } from '@ngrx/effects';
8+
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
69

710
import { AppRoutingModule } from './app-routing.module';
811
import { AppComponent } from './app.component';
@@ -28,6 +31,8 @@ import { FilterProjectPipe } from './modules/shared/pipes/filter-project/filter-
2831
import { SearchProjectComponent } from './modules/shared/components/search-project/search-project.component';
2932
import { HomeComponent } from './modules/home/home.component';
3033
import { LoginComponent } from './modules/login/login.component';
34+
import { ActivityEffects } from './modules/activities-management/store/activity-management.effects';
35+
import { activityManagementReducer } from './modules/activities-management/store';
3136

3237
@NgModule({
3338
declarations: [
@@ -56,7 +61,19 @@ import { LoginComponent } from './modules/login/login.component';
5661
FilterProjectPipe,
5762
SearchProjectComponent,
5863
],
59-
imports: [CommonModule, BrowserModule, AppRoutingModule, FormsModule, ReactiveFormsModule, HttpClientModule],
64+
imports: [
65+
CommonModule,
66+
BrowserModule,
67+
AppRoutingModule,
68+
FormsModule,
69+
ReactiveFormsModule,
70+
HttpClientModule,
71+
StoreModule.forRoot({ activities: activityManagementReducer }),
72+
EffectsModule.forRoot([ActivityEffects]),
73+
StoreDevtoolsModule.instrument({
74+
maxAge: 15, // Retains last 15 states
75+
}),
76+
],
6077
providers: [],
6178
bootstrap: [AppComponent],
6279
})

src/app/modules/activities-management/components/activity-list/activity-list.component.spec.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
1-
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
1+
import { provideMockStore, MockStore } from '@ngrx/store/testing';
22

3+
import { allActivities } from './../../store/activity-management.selectors';
4+
import { ActivityState } from './../../store/activity-management.reducers';
5+
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
36
import { ActivityListComponent } from './activity-list.component';
47

58
describe('ActivityListComponent', () => {
69
let component: ActivityListComponent;
710
let fixture: ComponentFixture<ActivityListComponent>;
11+
let mockActivitiesSelector;
12+
13+
const state = { data: [{id: 'id', name: 'name', description: 'description'}], isLoading: false, message: '' };
14+
15+
let store: MockStore<ActivityState>;
816

917
beforeEach(async(() => {
1018
TestBed.configureTestingModule({
11-
declarations: [ ActivityListComponent ]
19+
declarations: [ ActivityListComponent ],
20+
providers: [ provideMockStore({ initialState: state }) ]
1221
})
1322
.compileComponents();
23+
24+
store = TestBed.inject(MockStore);
25+
26+
mockActivitiesSelector = store.overrideSelector( allActivities, state );
1427
}));
1528

1629
beforeEach(() => {
@@ -22,4 +35,21 @@ describe('ActivityListComponent', () => {
2235
it('should create', () => {
2336
expect(component).toBeTruthy();
2437
});
38+
39+
it('onInit, LoadActivities action is dispatched', () => {
40+
spyOn(store, 'dispatch');
41+
42+
component.ngOnInit();
43+
44+
expect(store.dispatch).toHaveBeenCalled();
45+
});
46+
47+
it('onInit, activities field is populated with data from store', () => {
48+
component.ngOnInit();
49+
50+
expect(component.activities).toBe(state.data);
51+
});
52+
53+
afterEach(() => { fixture.destroy(); });
54+
2555
});
Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,28 @@
1-
import { Input } from '@angular/core';
1+
import { Input, OnInit } from '@angular/core';
22
import { Component } from '@angular/core';
3+
import { Store, select } from '@ngrx/store';
4+
5+
import { allActivities } from '../../store';
6+
import { LoadActivities } from './../../store/activity-management.actions';
7+
import { ActivityState } from './../../store/activity-management.reducers';
38
import { Activity } from '../../../shared/models';
49

5-
@Component({
6-
selector: 'app-activity-list',
7-
templateUrl: './activity-list.component.html',
8-
styleUrls: ['./activity-list.component.scss']
9-
})
10-
export class ActivityListComponent {
10+
@Component({selector: 'app-activity-list', templateUrl: './activity-list.component.html', styleUrls: ['./activity-list.component.scss']})
11+
export class ActivityListComponent implements OnInit {
12+
13+
@Input()activities: Activity[] = [];
14+
public isLoading: boolean;
15+
16+
constructor(private store: Store<ActivityState>) { }
1117

12-
@Input() activities: Activity[] = [];
18+
ngOnInit() {
19+
this.store.dispatch(new LoadActivities());
20+
const activities$ = this.store.pipe(select(allActivities));
1321

14-
constructor() { }
22+
activities$.subscribe(response => {
23+
this.isLoading = response.isLoading;
24+
this.activities = response.data;
25+
});
26+
}
1527

1628
}

src/app/modules/activities-management/services/activity.service.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { TestBed, inject } from '@angular/core/testing';
22
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
3+
34
import { Activity } from '../../shared/models';
45
import { ActivityService } from './activity.service';
56

@@ -28,14 +29,15 @@ describe('Activity Service', () => {
2829
expect(httpClient).toBeTruthy();
2930
}));
3031

31-
it('activities are read using GET from assets/activities.json URL', () => {
32+
it('activities are read using GET from baseUrl', () => {
3233
const activitiesFoundSize = activities.length;
34+
service.baseUrl = 'foo';
3335
service
3436
.getActivities()
3537
.subscribe(activitiesInResponse => {
3638
expect(activitiesInResponse.length).toBe(activitiesFoundSize);
3739
});
38-
const getActivitiesRequest = httpMock.expectOne('assets/activities.json');
40+
const getActivitiesRequest = httpMock.expectOne(service.baseUrl);
3941
expect(getActivitiesRequest.request.method).toBe('GET');
4042
getActivitiesRequest.flush(activities);
4143
});
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
import { Injectable } from '@angular/core';
22
import { HttpClient } from '@angular/common/http';
33
import { Observable } from 'rxjs';
4+
5+
import { environment } from './../../../../environments/environment';
46
import { Activity } from '../../shared/models';
57

68
@Injectable({
79
providedIn: 'root'
810
})
911
export class ActivityService {
1012

11-
url = 'assets/activities.json';
13+
baseUrl = `${environment.timeTrackerApiUrl}/activities`;
1214

1315
constructor(private http: HttpClient) {}
1416

1517
getActivities(): Observable<Activity[]> {
16-
return this.http.get<Activity[]>(this.url);
18+
return this.http.get<Activity[]>(this.baseUrl);
1719
}
1820
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { LoadActivitiesFail } from './activity-management.actions';
2+
import { LoadActivitiesSuccess, ActivityManagementActionTypes } from './activity-management.actions';
3+
4+
describe('LoadActivitiesSuccess', () => {
5+
6+
it('LoadActivitiesSuccess type is ActivityManagementActionTypes.LoadActivitiesSuccess', () => {
7+
const loadActivitiesSuccess = new LoadActivitiesSuccess([]);
8+
expect(loadActivitiesSuccess.type).toEqual(ActivityManagementActionTypes.LoadActivitiesSuccess);
9+
});
10+
11+
it('LoadActivitiesFail type is ActivityManagementActionTypes.LoadActivitiesFail', () => {
12+
const loadActivitiesFail = new LoadActivitiesFail('error');
13+
expect(loadActivitiesFail.type).toEqual(ActivityManagementActionTypes.LoadActivitiesFail);
14+
});
15+
16+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Action } from '@ngrx/store';
2+
3+
import { Activity } from './../../shared/models/activity.model';
4+
5+
export enum ActivityManagementActionTypes {
6+
LoadActivities = '[ActivityManagement] Load Activities',
7+
LoadActivitiesSuccess = '[ActivityManagement] Load Activities Successs',
8+
LoadActivitiesFail = '[ActivityManagement] Load Activities Fail',
9+
}
10+
11+
12+
export class LoadActivities implements Action {
13+
public readonly type = ActivityManagementActionTypes.LoadActivities;
14+
}
15+
16+
export class LoadActivitiesSuccess implements Action {
17+
public readonly type = ActivityManagementActionTypes.LoadActivitiesSuccess;
18+
19+
constructor(public payload: Activity[]) { }
20+
}
21+
22+
export class LoadActivitiesFail implements Action {
23+
public readonly type = ActivityManagementActionTypes.LoadActivitiesFail;
24+
25+
constructor(public error) { }
26+
}
27+
28+
export type ActivityManagementActions = LoadActivities | LoadActivitiesSuccess | LoadActivitiesFail;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Injectable } from '@angular/core';
2+
import { Actions, Effect, ofType } from '@ngrx/effects';
3+
import { Action } from '@ngrx/store';
4+
import { Observable, of } from 'rxjs';
5+
import { catchError, map, mergeMap } from 'rxjs/operators';
6+
7+
import { ActivityManagementActionTypes, LoadActivitiesSuccess, LoadActivitiesFail } from './activity-management.actions';
8+
import { Activity } from './../../shared/models/activity.model';
9+
import { ActivityService } from './../services/activity.service';
10+
11+
@Injectable()
12+
export class ActivityEffects {
13+
14+
constructor(private actions$: Actions, private activityService: ActivityService) { }
15+
16+
@Effect()
17+
getActivities$: Observable<Action> = this.actions$.pipe(
18+
ofType(ActivityManagementActionTypes.LoadActivities),
19+
mergeMap(() =>
20+
this.activityService.getActivities().pipe(
21+
map((activities: Activity[]) => {
22+
return new LoadActivitiesSuccess(activities);
23+
}),
24+
catchError((error) =>
25+
of(new LoadActivitiesFail(error)))
26+
)
27+
));
28+
}

0 commit comments

Comments
 (0)