Skip to content

Commit 144d81c

Browse files
Chore/factor out more duplicate code (#20)
Also: * turn on onPush change detection. * update CLI to rc1 * update circle-ci to fail faster * update circle-ci to use a newer chrome required by protractor.
1 parent 2d39a40 commit 144d81c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+405
-442
lines changed

circle.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
machine:
22
node:
33
version: 6.9.5
4+
post:
5+
- wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
6+
- sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
7+
- sudo apt-get update
8+
- sudo apt-get install google-chrome-stable
49

510
test:
611
override:
7-
- ng lint
8-
- ng test --single-run
9-
- ng e2e
12+
- ng lint && ng test --single-run && ng e2e

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"zone.js": "^0.7.6"
3535
},
3636
"devDependencies": {
37-
"@angular/cli": "1.0.0-rc.0",
37+
"@angular/cli": "1.0.0-rc.1",
3838
"@angular/compiler-cli": "^2.4.0",
3939
"@types/jasmine": "2.5.38",
4040
"@types/node": "~6.0.60",
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { TestBed, async } from '@angular/core/testing';
2+
import { RouterTestingModule } from '@angular/router/testing';
3+
import { By } from '@angular/platform-browser';
4+
import { AnimalListComponent } from './animal-list.component';
5+
import { CoreModule } from '../../core/core.module';
6+
7+
describe('AppComponent', () => {
8+
beforeEach(async(() => {
9+
TestBed.configureTestingModule({
10+
declarations: [AnimalListComponent],
11+
imports: [CoreModule],
12+
}).compileComponents();
13+
}));
14+
15+
it(`should have as title 'Welcome to the Zoo'`, async(() => {
16+
const fixture = TestBed.createComponent(AnimalListComponent);
17+
const app = fixture.debugElement.componentInstance;
18+
19+
app.animalsName = 'Wallabies';
20+
fixture.detectChanges();
21+
22+
const titleElement = fixture.debugElement.query(By.css('h2'));
23+
expect(titleElement.nativeElement.textContent).toContain('We have Wallabies');
24+
}));
25+
});

src/app/core/animal-list/animal-list.component.ts renamed to src/app/animals/animal-list/animal-list.component.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { Component, Input } from '@angular/core';
1+
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
22
import { Observable } from 'rxjs/Observable';
33

44
@Component({
55
selector: 'zoo-animal-list',
66
templateUrl: './animal-list.component.html',
7-
styleUrls: ['./animal-list.component.css']
7+
styleUrls: ['./animal-list.component.css'],
8+
changeDetection: ChangeDetectionStrategy.OnPush,
89
})
910
export class AnimalListComponent {
1011
@Input() animalsName: string;

src/app/animals/animal.actions.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Injectable } from '@angular/core';
2+
import { Action } from 'redux';
3+
4+
@Injectable()
5+
export class AnimalActions {
6+
static readonly LOAD_STARTED = 'LOAD_STARTED';
7+
static readonly LOAD_SUCCEEDED = 'LOAD_SUCCEEDED';
8+
static readonly LOAD_FAILED = 'LOAD_FAILED';
9+
10+
loadAnimals(animalType) {
11+
return {
12+
type: AnimalActions.LOAD_STARTED,
13+
meta: { animalType },
14+
};
15+
}
16+
17+
loadSucceeded(animalType, payload) {
18+
return {
19+
type: AnimalActions.LOAD_SUCCEEDED,
20+
meta: { animalType },
21+
payload,
22+
};
23+
}
24+
25+
loadFailed(animalType, error) {
26+
return {
27+
type: AnimalActions.LOAD_FAILED,
28+
meta: { animalType },
29+
error,
30+
};
31+
}
32+
}

src/app/animals/animal.epics.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Injectable } from '@angular/core';
2+
import { Epic, createEpicMiddleware } from 'redux-observable';
3+
import { Action, Store } from 'redux';
4+
import { of } from 'rxjs/observable/of';
5+
import 'rxjs/add/operator/catch';
6+
import 'rxjs/add/operator/map';
7+
8+
import { AnimalType, ANIMAL_TYPES } from '../animals/animal.types';
9+
import { AnimalActions } from '../animals/animal.actions';
10+
import { AnimalService } from './animal.service';
11+
12+
@Injectable()
13+
export class AnimalEpics {
14+
constructor(
15+
private service: AnimalService,
16+
private actions: AnimalActions,
17+
) {}
18+
19+
public createEpic(animalType: AnimalType) {
20+
return createEpicMiddleware(this.createLoadAnimalEpic(animalType));
21+
}
22+
23+
private createLoadAnimalEpic(animalType) {
24+
return action$ => action$
25+
.ofType(AnimalActions.LOAD_STARTED)
26+
.filter(({ meta }) => meta.animalType === animalType)
27+
.switchMap(a => this.service.getAll(animalType)
28+
.map(data => this.actions.loadSucceeded(animalType, data))
29+
.catch(response => of(this.actions.loadFailed(animalType, {
30+
status: '' + response.status,
31+
}))));
32+
}
33+
}

src/app/animals/animal.module.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { NgModule } from '@angular/core';
2+
import { CommonModule } from '@angular/common';
3+
4+
import { CoreModule } from '../core/core.module';
5+
import { AnimalListComponent } from './animal-list/animal-list.component';
6+
import { AnimalActions } from './animal.actions';
7+
import { AnimalEpics } from './animal.epics';
8+
import { AnimalService } from './animal.service';
9+
import { StoreModule } from '../store/store.module';
10+
11+
@NgModule({
12+
declarations: [AnimalListComponent],
13+
exports: [AnimalListComponent],
14+
imports: [CoreModule, StoreModule, CommonModule],
15+
providers: [AnimalActions, AnimalEpics, AnimalService],
16+
})
17+
export class AnimalModule {}

src/app/animals/animal.reducer.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { AnimalActions } from './animal.actions';
2+
import { IPayloadAction } from '../core/utils/payload-action.types';
3+
import { IAnimalList, IAnimal, AnimalType } from './animal.types';
4+
5+
const INITIAL_STATE: IAnimalList = {
6+
items: [],
7+
loading: false,
8+
error: null,
9+
};
10+
11+
// A higher-order reducer: accepts an animal type and returns a reducer
12+
// that only responds to actions for that particular animal type.
13+
export function createAnimalReducer(animalType: AnimalType) {
14+
return function animalReducer(state: IAnimalList = INITIAL_STATE,
15+
action: IPayloadAction<IAnimal[], any>): IAnimalList {
16+
if (!action.meta || action.meta.animalType !== animalType) {
17+
return state;
18+
}
19+
20+
switch (action.type) {
21+
case AnimalActions.LOAD_STARTED:
22+
return {
23+
items: [],
24+
loading: true,
25+
error: null,
26+
};
27+
case AnimalActions.LOAD_SUCCEEDED:
28+
return {
29+
items: action.payload,
30+
loading: false,
31+
error: null
32+
};
33+
case AnimalActions.LOAD_FAILED:
34+
return {
35+
items: [],
36+
loading: false,
37+
error: action.error
38+
};
39+
}
40+
41+
return state;
42+
};
43+
}

0 commit comments

Comments
 (0)