= [
6 | {
7 | id: 1,
8 | name: 'Some Name',
9 | description: 'Some Description'
10 | },
11 | {
12 | id: 2,
13 | name: 'Better Name',
14 | description: 'Better Description'
15 | },
16 | {
17 | id: 3,
18 | name: 'Oh yeah? Well I have the best name!',
19 | description: 'And the best description, too!'
20 | },
21 | {
22 | id: 4,
23 | name: 'Well, agree to disagree, then.',
24 | description: 'And for the record, I disagree!'
25 | }
26 | ];
27 |
28 | return { briebug };
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/sandbox-app/src/app/entities/components/entity-form/entity-form.component.css:
--------------------------------------------------------------------------------
1 | label {
2 | margin-right: 10px;
3 | }
4 |
5 | input {
6 | margin-bottom: 10px;
7 | width: 250px;
8 | }
9 |
10 | .invalid {
11 | color: red;
12 | }
13 |
14 | button {
15 | margin-bottom: 10px;
16 | }
17 |
--------------------------------------------------------------------------------
/sandbox-app/src/app/entities/components/entity-form/entity-form.component.html:
--------------------------------------------------------------------------------
1 |
38 |
--------------------------------------------------------------------------------
/sandbox-app/src/app/entities/components/entity-form/entity-form.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 | import { SimpleChange } from '@angular/core';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 |
5 | import { BriebugFormComponent } from './entity-form.component';
6 | import { generateBriebug } from '@state/briebug/briebug.model';
7 | import { fromEvent } from 'rxjs';
8 |
9 | describe('BreibugFormComponent', () => {
10 | let component: BriebugFormComponent;
11 | let fixture: ComponentFixture;
12 |
13 | beforeEach(async(() => {
14 | TestBed.configureTestingModule({
15 | imports: [ReactiveFormsModule],
16 | declarations: [BriebugFormComponent]
17 | }).compileComponents();
18 | }));
19 |
20 | beforeEach(() => {
21 | fixture = TestBed.createComponent(BriebugFormComponent);
22 | component = fixture.componentInstance;
23 | fixture.detectChanges();
24 | });
25 |
26 | it('should create', () => {
27 | expect(component).toBeTruthy();
28 | });
29 |
30 | describe('ngOnChanges', () => {
31 | it('should patch changes into the entity', () => {
32 | component.briebug = generateBriebug();
33 |
34 | component.ngOnChanges({
35 | briebug: new SimpleChange(null, component.briebug, true)
36 | });
37 |
38 | expect(component.formGroup.value).toEqual({ ...component.briebug });
39 | });
40 | });
41 |
42 | describe('constructor', () => {
43 | it('should emit briebugChanged when the form changes', (done) => {
44 | const briebug = generateBriebug();
45 |
46 | component.briebugChanged.subscribe((value) => {
47 | expect(value).toEqual({
48 | briebug,
49 | valid: component.formGroup.valid
50 | });
51 | done();
52 | });
53 |
54 | // Called twice because the first value is skipped:
55 | component.formGroup.patchValue(briebug);
56 | component.formGroup.patchValue(briebug);
57 | });
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/sandbox-app/src/app/entities/components/entity-form/entity-form.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Component,
3 | Input,
4 | Output,
5 | EventEmitter,
6 | OnChanges,
7 | SimpleChanges,
8 | OnDestroy,
9 | ChangeDetectionStrategy
10 | } from '@angular/core';
11 |
12 | import { Briebug } from '@state/briebug/briebug.model';
13 | import { FormGroup, Validators, FormBuilder } from '@angular/forms';
14 | import { Subject } from 'rxjs';
15 | import { takeUntil, skip, debounceTime } from 'rxjs/operators';
16 |
17 | @Component({
18 | selector: 'app-briebug-form',
19 | templateUrl: './entity-form.component.html',
20 | styleUrls: ['./entity-form.component.css'],
21 | changeDetection: ChangeDetectionStrategy.OnPush
22 | })
23 | export class BriebugFormComponent implements OnChanges, OnDestroy {
24 | formGroup: FormGroup;
25 |
26 | @Input() briebug: Briebug;
27 | @Input() disableFields: boolean;
28 | @Input() showErrors: boolean;
29 | @Output() submit = new EventEmitter();
30 | @Output() briebugChanged = new EventEmitter<{ briebug: Briebug; valid: boolean }>();
31 |
32 | private destroyed$ = new Subject();
33 |
34 | constructor(private formBuilder: FormBuilder) {
35 | this.buildForm();
36 | }
37 |
38 | ngOnChanges(changes: SimpleChanges) {
39 | if (changes.briebug && changes.briebug.currentValue) {
40 | this.formGroup.patchValue(this.briebug);
41 | }
42 | }
43 |
44 | ngOnDestroy() {
45 | this.destroyed$.next();
46 | this.destroyed$.complete();
47 | }
48 |
49 | private buildForm() {
50 | // FIXME: Fields are not disabling as expected.
51 | this.formGroup = this.formBuilder.group({
52 | id: null,
53 | name: [{ value: '', disabled: this.disableFields }, Validators.required],
54 | description: [{ value: '', disabled: this.disableFields }, Validators.required]
55 | });
56 |
57 | this.formGroup.valueChanges
58 | .pipe(
59 | takeUntil(this.destroyed$),
60 | skip(1),
61 | debounceTime(500)
62 | )
63 | .subscribe((value) => {
64 | this.briebugChanged.emit({
65 | briebug: value,
66 | valid: this.formGroup.valid
67 | });
68 | });
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/sandbox-app/src/app/entities/containers/entity-list/entity-list.component.css:
--------------------------------------------------------------------------------
1 | .error-message {
2 | color: red;
3 | }
4 | ul {
5 | list-style-type: none;
6 | }
7 |
8 | li {
9 | margin-bottom: 20px;
10 | }
11 |
--------------------------------------------------------------------------------
/sandbox-app/src/app/entities/containers/entity-list/entity-list.component.html:
--------------------------------------------------------------------------------
1 | Test Entities
2 |
3 | Loading...
4 | {{errorMessage$ | async}}
5 |
6 |
7 | -
8 | Name: {{briebug.name}}
9 | Description: {{briebug.description}}
10 |
11 |
12 |
13 |
14 |
15 | (Add new entity)
16 |
--------------------------------------------------------------------------------
/sandbox-app/src/app/entities/containers/entity-list/entity-list.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 |
4 | import { Store, StoreModule } from '@ngrx/store';
5 |
6 | import { BriebugListComponent } from './entity-list.component';
7 | import { appReducer } from '@state/app.reducer';
8 | import { AppState } from '@state/app.interfaces';
9 | import {
10 | SearchAllBriebugEntities,
11 | SearchAllBriebugEntitiesSuccess,
12 | SearchAllBriebugEntitiesFail
13 | } from '@state/briebug/briebug.actions';
14 | import { generateBriebugArray } from '@state/briebug/briebug.model';
15 |
16 | describe('BriebugListComponent', () => {
17 | let component: BriebugListComponent;
18 | let fixture: ComponentFixture;
19 | let store: Store;
20 |
21 | beforeEach(async(() => {
22 | TestBed.configureTestingModule({
23 | declarations: [BriebugListComponent],
24 | imports: [RouterTestingModule, StoreModule.forRoot(appReducer)]
25 | }).compileComponents();
26 | }));
27 |
28 | beforeEach(() => {
29 | fixture = TestBed.createComponent(BriebugListComponent);
30 | component = fixture.componentInstance;
31 | store = TestBed.get(Store);
32 | fixture.detectChanges();
33 |
34 | spyOn(store, 'dispatch').and.callThrough();
35 | });
36 |
37 | it('should create', () => {
38 | expect(component).toBeTruthy();
39 | });
40 |
41 | it('should get briebug entities when available', (done) => {
42 | const entities = generateBriebugArray();
43 |
44 | store.dispatch(new SearchAllBriebugEntitiesSuccess({ result: entities }));
45 |
46 | component.briebugEntities$.subscribe((result) => {
47 | expect(result).toEqual(entities);
48 | done();
49 | });
50 | });
51 |
52 | it('should get the loading status when available', (done) => {
53 | // Used to set loading to true:
54 | store.dispatch(new SearchAllBriebugEntities());
55 |
56 | component.isLoading$.subscribe((isLoading) => {
57 | expect(isLoading).toBe(true);
58 | done();
59 | });
60 | });
61 |
62 | it('should get the error message when available', (done) => {
63 | const testError = 'Some Error Message';
64 | store.dispatch(new SearchAllBriebugEntitiesFail({ error: testError }));
65 |
66 | component.errorMessage$.subscribe((errorResult) => {
67 | if (errorResult) { // Needed because the first errorResult is blank
68 | expect(errorResult).toContain(testError);
69 | // This done() ensures that this test isn't skipped as a result of the
70 | // if block - tests fail if done is never called.
71 | done();
72 | }
73 | });
74 | });
75 |
76 | describe('ngOnInit', () => {
77 | it('should request all Briebug entities', () => {
78 | component.ngOnInit();
79 |
80 | expect(store.dispatch).toHaveBeenCalledWith(
81 | new SearchAllBriebugEntities()
82 | );
83 | });
84 | });
85 | });
86 |
--------------------------------------------------------------------------------
/sandbox-app/src/app/entities/containers/entity-list/entity-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
2 | import { select, Store } from '@ngrx/store';
3 |
4 | import { Observable } from 'rxjs';
5 | import { shareReplay } from 'rxjs/operators';
6 |
7 | import {
8 | briebug,
9 | briebugLoading,
10 | briebugError
11 | } from '@state/briebug';
12 | import { BriebugState } from '@state/briebug/briebug.reducer';
13 | import {
14 | SearchAllBriebugEntities,
15 | DeleteBriebugById
16 | } from '@state/briebug/briebug.actions';
17 | import { Briebug } from '@state/briebug/briebug.model';
18 |
19 | @Component({
20 | templateUrl: './entity-list.component.html',
21 | styleUrls: ['./entity-list.component.css'],
22 | changeDetection: ChangeDetectionStrategy.OnPush
23 | })
24 | export class BriebugListComponent implements OnInit {
25 | briebugEntities$ = this.store.pipe(select(briebug));
26 | isLoading$ = this.store.pipe(select(briebugLoading));
27 | errorMessage$ = this.store.pipe(
28 | select(briebugError),
29 | // This allows us to use the async pipe twice without creating two subscriptions:
30 | shareReplay()
31 | );
32 |
33 | constructor(private store: Store) {}
34 |
35 | ngOnInit() {
36 | this.store.dispatch(new SearchAllBriebugEntities());
37 | }
38 |
39 | deleteBriebug(id) {
40 | this.store.dispatch(new DeleteBriebugById({ id }));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/sandbox-app/src/app/entities/containers/entity/entity.component.css:
--------------------------------------------------------------------------------
1 | .error-message {
2 | color: red;
3 | }
4 |
--------------------------------------------------------------------------------
/sandbox-app/src/app/entities/containers/entity/entity.component.html:
--------------------------------------------------------------------------------
1 | Test Entity
2 |
3 | Loading...
4 | {{errorMessage$ | async}}
5 |
6 |
13 |
14 | Back to list
15 |
--------------------------------------------------------------------------------
/sandbox-app/src/app/entities/containers/entity/entity.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ReactiveFormsModule } from '@angular/forms';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
4 | import { ActivatedRoute, convertToParamMap } from '@angular/router';
5 |
6 | import * as fromRoot from '@state/app.reducer';
7 | import { Store, StoreModule } from '@ngrx/store';
8 | import { Subject } from 'rxjs';
9 |
10 | import { BriebugComponent } from './entity.component';
11 | import { BriebugFormComponent } from '../../components/entity-form/entity-form.component';
12 | import { AppState } from '@state/app.interfaces';
13 | import { generateBriebug } from '@state/briebug/briebug.model';
14 | import {
15 | LoadBriebugById,
16 | LoadBriebugByIdSuccess,
17 | SelectBriebugById,
18 | LoadBriebugByIdFail,
19 | UpdateBriebug,
20 | CreateBriebug
21 | } from '@state/briebug/briebug.actions';
22 | import { skip } from 'rxjs/operators';
23 |
24 | describe('BriebugComponent', () => {
25 | let component: BriebugComponent;
26 | let fixture: ComponentFixture;
27 | let store: Store;
28 | const paramMap = new Subject();
29 | const url = new Subject();
30 |
31 | beforeEach(async(() => {
32 | TestBed.configureTestingModule({
33 | imports: [
34 | ReactiveFormsModule,
35 | RouterTestingModule,
36 | StoreModule.forRoot(fromRoot.appReducer)
37 | ],
38 | declarations: [BriebugComponent, BriebugFormComponent],
39 | providers: [
40 | {
41 | provide: ActivatedRoute,
42 | useValue: {
43 | paramMap,
44 | url
45 | }
46 | }
47 | ]
48 | }).compileComponents();
49 | }));
50 |
51 | beforeEach(() => {
52 | fixture = TestBed.createComponent(BriebugComponent);
53 | component = fixture.componentInstance;
54 | store = TestBed.get(Store);
55 | fixture.detectChanges();
56 |
57 | spyOn(store, 'dispatch').and.callThrough();
58 | });
59 |
60 | it('should create', () => {
61 | expect(component).toBeTruthy();
62 | });
63 |
64 | it('should get the specified briebug entities if editing', (done) => {
65 | const entity = generateBriebug();
66 |
67 | component.briebug$.subscribe((result) => {
68 | if (result && result.id) {
69 | expect(result).toEqual(entity);
70 | // This done() ensures that this test isn't skipped as a result of the
71 | // if block - tests fail if done is never called.
72 | done();
73 | }
74 | });
75 |
76 | paramMap.next(convertToParamMap({ id: entity.id }));
77 | url.next([{ path: '1' }]);
78 | store.dispatch(new LoadBriebugByIdSuccess({ result: entity }));
79 | });
80 |
81 | // it('should select a null ID if adding', (done) => {
82 | // component.briebug$.subscribe((result) => {
83 | // expect(Object.keys(result).length).toBe(0);
84 | // expect(store.dispatch).toHaveBeenCalledWith(
85 | // new SelectBriebugById({ id: null })
86 | // );
87 | // done();
88 | // });
89 |
90 | // paramMap.next(convertToParamMap({}));
91 | // url.next([{ path: 'add' }]);
92 | // });
93 |
94 | it('should get the error message when available', (done) => {
95 | const testError = 'Some Error Message';
96 | store.dispatch(new LoadBriebugByIdFail({ error: testError }));
97 |
98 | component.errorMessage$.pipe(skip(1)).subscribe((errorResult) => {
99 | expect(errorResult).toContain(testError);
100 | done();
101 | });
102 | });
103 |
104 | it('should get the loading status when available', (done) => {
105 | // Used to set loading to true:
106 | store.dispatch(new LoadBriebugById({ id: 1 }));
107 |
108 | component.isLoading$.subscribe((isLoading) => {
109 | if (isLoading) {
110 | // Protects against initial value of false
111 | expect(isLoading).toBe(true);
112 | // This done() ensures that this test isn't skipped as a result of the
113 | // if block - tests fail if done is never called.
114 | done();
115 | }
116 | });
117 | });
118 |
119 | describe('ngOnInit', () => {
120 | it('should not show the form errors', () => {
121 | component.ngOnInit();
122 |
123 | expect(component.showFormErrors).toBe(false);
124 | });
125 | });
126 |
127 | describe('onBriebugChanged', () => {
128 | it('should match the returned entity and valid values', () => {
129 | const briebug = generateBriebug();
130 | const valid = true;
131 |
132 | component.onBriebugChanged({ briebug, valid });
133 |
134 | expect(component.briebugEdits).toEqual(briebug);
135 | expect(component.valid).toBe(valid);
136 | });
137 | });
138 |
139 | describe('onSubmit', () => {
140 | it('should show the form errors', () => {
141 | component.showFormErrors = false;
142 |
143 | component.onSubmit();
144 |
145 | expect(component.showFormErrors).toBe(true);
146 | });
147 |
148 | it('should not dispatch anything if the form isn\'t valid', () => {
149 | component.valid = false;
150 |
151 | component.onSubmit();
152 |
153 | expect(store.dispatch).not.toHaveBeenCalled();
154 | });
155 |
156 | it('should dispatch UpdateBriebug if an ID is present', () => {
157 | component.valid = true;
158 | component.briebugEdits = generateBriebug();
159 |
160 | component.onSubmit();
161 |
162 | expect(store.dispatch).toHaveBeenCalledWith(
163 | new UpdateBriebug({ briebug: component.briebugEdits })
164 | );
165 | });
166 |
167 | it('should dispatch CreateBriebug if an ID is not present', () => {
168 | component.valid = true;
169 | component.briebugEdits = generateBriebug();
170 | delete component.briebugEdits.id;
171 |
172 | component.onSubmit();
173 |
174 | expect(store.dispatch).toHaveBeenCalledWith(
175 | new CreateBriebug({ briebug: component.briebugEdits })
176 | );
177 | });
178 | });
179 | });
180 |
--------------------------------------------------------------------------------
/sandbox-app/src/app/entities/containers/entity/entity.component.ts:
--------------------------------------------------------------------------------
1 | import { ActivatedRoute } from '@angular/router';
2 | import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
3 |
4 | import { Observable } from 'rxjs';
5 | import { select, Store } from '@ngrx/store';
6 | import {
7 | tap,
8 | filter,
9 | map,
10 | switchMap,
11 | shareReplay,
12 | combineLatest
13 | } from 'rxjs/operators';
14 |
15 | import { briebugLoading, currentBriebug, briebugError } from '@state/briebug';
16 | import { Briebug } from '@state/briebug/briebug.model';
17 | import {
18 | LoadBriebugById,
19 | CreateBriebug,
20 | UpdateBriebug,
21 | SelectBriebugById
22 | } from '@state/briebug/briebug.actions';
23 | import { BriebugState } from '@state/briebug/briebug.reducer';
24 |
25 | @Component({
26 | templateUrl: './entity.component.html',
27 | styleUrls: ['./entity.component.css'],
28 | changeDetection: ChangeDetectionStrategy.OnPush
29 | })
30 | export class BriebugComponent implements OnInit {
31 | briebug$ = this.activatedRoute.paramMap.pipe(
32 | combineLatest(this.activatedRoute.url),
33 | filter(([params, url]) =>
34 | params.has('id') || url[0].path === 'add'
35 | ),
36 | map(([params]) => params.get('id')),
37 | tap((id) => {
38 | // If no ID is present, then the user is here to add a new entity.
39 | const BriebugAction = id ? LoadBriebugById : SelectBriebugById;
40 | this.store.dispatch(new BriebugAction({ id: +id || null }));
41 | }),
42 | switchMap(() => this.store.pipe(select(currentBriebug))),
43 | map((briebug) => ({ ...briebug }))
44 | );
45 | // The following shareReplay calls allow us to use the async pipe multiple
46 | // times without creating multiple subscriptions:
47 | errorMessage$ = this.store.pipe(
48 | select(briebugError),
49 | shareReplay()
50 | );
51 | isLoading$ = this.store.pipe(
52 | select(briebugLoading),
53 | shareReplay()
54 | );
55 |
56 | briebugEdits: Briebug;
57 | showFormErrors: boolean;
58 | valid: boolean;
59 |
60 | constructor(
61 | private activatedRoute: ActivatedRoute,
62 | private store: Store
63 | ) {}
64 |
65 | ngOnInit() {
66 | this.showFormErrors = false;
67 | }
68 |
69 | onBriebugChanged({ briebug, valid }: { briebug: Briebug; valid: boolean }) {
70 | this.briebugEdits = briebug;
71 | this.valid = valid;
72 | }
73 |
74 | onSubmit() {
75 | this.showFormErrors = true;
76 |
77 | if (!this.valid) {
78 | return;
79 | }
80 |
81 | const BriebugAction = this.briebugEdits.id
82 | ? UpdateBriebug
83 | : CreateBriebug;
84 | this.store.dispatch(new BriebugAction({ briebug: this.briebugEdits }));
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/sandbox-app/src/app/entities/entities-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | import { BriebugListComponent } from './containers/entity-list/entity-list.component';
5 | import { BriebugComponent } from './containers/entity/entity.component';
6 |
7 | const routes: Routes = [
8 | {
9 | path: '',
10 | component: BriebugListComponent
11 | },
12 | {
13 | path: 'add',
14 | component: BriebugComponent
15 | },
16 | {
17 | path: ':id',
18 | component: BriebugComponent
19 | }
20 | ];
21 |
22 | @NgModule({
23 | imports: [RouterModule.forChild(routes)],
24 | exports: [RouterModule]
25 | })
26 | export class EntitiesRoutingModule {}
27 |
--------------------------------------------------------------------------------
/sandbox-app/src/app/entities/entities.module.spec.ts:
--------------------------------------------------------------------------------
1 | import { EntitiesModule } from './entities.module';
2 |
3 | describe('EntitiesModule', () => {
4 | let entitiesModule: EntitiesModule;
5 |
6 | beforeEach(() => {
7 | entitiesModule = new EntitiesModule();
8 | });
9 |
10 | it('should create an instance', () => {
11 | expect(entitiesModule).toBeTruthy();
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/sandbox-app/src/app/entities/entities.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { ReactiveFormsModule } from '@angular/forms';
3 | import { NgModule } from '@angular/core';
4 |
5 | import { BriebugComponent } from './containers/entity/entity.component';
6 | import { BriebugListComponent } from './containers/entity-list/entity-list.component';
7 | import { BriebugFormComponent } from './components/entity-form/entity-form.component';
8 | import { EntitiesRoutingModule } from './entities-routing.module';
9 |
10 | @NgModule({
11 | imports: [
12 | CommonModule,
13 | ReactiveFormsModule,
14 | EntitiesRoutingModule
15 | ],
16 | declarations: [BriebugListComponent, BriebugComponent, BriebugFormComponent]
17 | })
18 | export class EntitiesModule {}
19 |
--------------------------------------------------------------------------------
/sandbox-app/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briebug/ngrx-entity-schematic/33958bba121c62fda692cf6d0ddba0769f4d5212/sandbox-app/src/assets/.gitkeep
--------------------------------------------------------------------------------
/sandbox-app/src/browserslist:
--------------------------------------------------------------------------------
1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 | # For IE 9-11 support, please uncomment the last line of the file and adjust as needed
5 | > 0.5%
6 | last 2 versions
7 | Firefox ESR
8 | not dead
9 | # IE 9-11
--------------------------------------------------------------------------------
/sandbox-app/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/sandbox-app/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * In development mode, to ignore zone related error stack frames such as
11 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can
12 | * import the following file, but please comment it out in production mode
13 | * because it will have performance impact when throw error
14 | */
15 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI.
16 |
--------------------------------------------------------------------------------
/sandbox-app/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briebug/ngrx-entity-schematic/33958bba121c62fda692cf6d0ddba0769f4d5212/sandbox-app/src/favicon.ico
--------------------------------------------------------------------------------
/sandbox-app/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NGRX Entity Generator Example App
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/sandbox-app/src/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, '../coverage'),
20 | reports: ['html', 'lcovonly'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false
30 | });
31 | };
--------------------------------------------------------------------------------
/sandbox-app/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule)
12 | .catch(err => console.log(err));
13 |
--------------------------------------------------------------------------------
/sandbox-app/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 | // import 'core-js/es6/symbol';
23 | // import 'core-js/es6/object';
24 | // import 'core-js/es6/function';
25 | // import 'core-js/es6/parse-int';
26 | // import 'core-js/es6/parse-float';
27 | // import 'core-js/es6/number';
28 | // import 'core-js/es6/math';
29 | // import 'core-js/es6/string';
30 | // import 'core-js/es6/date';
31 | // import 'core-js/es6/array';
32 | // import 'core-js/es6/regexp';
33 | // import 'core-js/es6/map';
34 | // import 'core-js/es6/weak-map';
35 | // import 'core-js/es6/set';
36 |
37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
38 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
39 |
40 | /** IE10 and IE11 requires the following for the Reflect API. */
41 | // import 'core-js/es6/reflect';
42 |
43 |
44 | /** Evergreen browsers require these. **/
45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
46 | import 'core-js/es7/reflect';
47 |
48 |
49 | /**
50 | * Web Animations `@angular/platform-browser/animations`
51 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
52 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
53 | **/
54 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
55 |
56 | /**
57 | * By default, zone.js will patch all possible macroTask and DomEvents
58 | * user can disable parts of macroTask/DomEvents patch by setting following flags
59 | */
60 |
61 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
62 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
63 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
64 |
65 | /*
66 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
67 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
68 | */
69 | // (window as any).__Zone_enable_cross_context_check = true;
70 |
71 | /***************************************************************************************************
72 | * Zone JS is required by default for Angular itself.
73 | */
74 | import 'zone.js/dist/zone'; // Included with Angular CLI.
75 |
76 |
77 |
78 | /***************************************************************************************************
79 | * APPLICATION IMPORTS
80 | */
81 |
--------------------------------------------------------------------------------
/sandbox-app/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/sandbox-app/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/dist/zone-testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: any;
11 |
12 | // First, initialize the Angular testing environment.
13 | getTestBed().initTestEnvironment(
14 | BrowserDynamicTestingModule,
15 | platformBrowserDynamicTesting()
16 | );
17 | // Then we find all the tests.
18 | const context = require.context('./', true, /\.spec\.ts$/);
19 | // And load the modules.
20 | context.keys().map(context);
21 |
--------------------------------------------------------------------------------
/sandbox-app/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "module": "es2015",
6 | "types": []
7 | },
8 | "exclude": [
9 | "src/test.ts",
10 | "**/*.spec.ts"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/sandbox-app/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "module": "commonjs",
6 | "types": [
7 | "jasmine",
8 | "node"
9 | ]
10 | },
11 | "files": [
12 | "test.ts",
13 | "polyfills.ts"
14 | ],
15 | "include": [
16 | "**/*.spec.ts",
17 | "**/*.d.ts"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/sandbox-app/src/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tslint.json",
3 | "rules": {
4 | "directive-selector": [
5 | true,
6 | "attribute",
7 | "app",
8 | "camelCase"
9 | ],
10 | "component-selector": [
11 | true,
12 | "element",
13 | "app",
14 | "kebab-case"
15 | ]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/sandbox-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "paths": {
6 | "@state/*": ["src/app/state/*"],
7 | "@core/*": ["src/app/core/*"]
8 | },
9 | "outDir": "./dist/out-tsc",
10 | "sourceMap": true,
11 | "declaration": false,
12 | "moduleResolution": "node",
13 | "emitDecoratorMetadata": true,
14 | "experimentalDecorators": true,
15 | "target": "es5",
16 | "typeRoots": [
17 | "node_modules/@types"
18 | ],
19 | "lib": [
20 | "es2017",
21 | "dom"
22 | ]
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/sandbox-app/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "linterOptions": {
6 | "exclude": [
7 | "**/*.json"
8 | ]
9 | },
10 | "rules": {
11 | "arrow-return-shorthand": true,
12 | "callable-types": true,
13 | "class-name": true,
14 | "comment-format": [
15 | true,
16 | "check-space"
17 | ],
18 | "curly": true,
19 | "deprecation": {
20 | "severity": "warn"
21 | },
22 | "eofline": true,
23 | "forin": true,
24 | "import-blacklist": [
25 | true,
26 | "rxjs/Rx"
27 | ],
28 | "import-spacing": true,
29 | "indent": [
30 | true,
31 | "spaces"
32 | ],
33 | "interface-over-type-literal": true,
34 | "label-position": true,
35 | "max-line-length": [
36 | true,
37 | 140
38 | ],
39 | "member-access": false,
40 | "member-ordering": [
41 | true,
42 | {
43 | "order": [
44 | "static-field",
45 | "instance-field",
46 | "static-method",
47 | "instance-method"
48 | ]
49 | }
50 | ],
51 | "no-arg": true,
52 | "no-bitwise": true,
53 | "no-console": [
54 | true,
55 | "debug",
56 | "info",
57 | "time",
58 | "timeEnd",
59 | "trace"
60 | ],
61 | "no-construct": true,
62 | "no-debugger": true,
63 | "no-duplicate-super": true,
64 | "no-empty": false,
65 | "no-empty-interface": true,
66 | "no-eval": true,
67 | "no-inferrable-types": [
68 | true,
69 | "ignore-params"
70 | ],
71 | "no-misused-new": true,
72 | "no-non-null-assertion": true,
73 | "no-shadowed-variable": true,
74 | "no-string-literal": false,
75 | "no-string-throw": true,
76 | "no-switch-case-fall-through": true,
77 | "no-trailing-whitespace": true,
78 | "no-unnecessary-initializer": true,
79 | "no-unused-expression": true,
80 | "no-use-before-declare": true,
81 | "no-var-keyword": true,
82 | "object-literal-sort-keys": false,
83 | "one-line": [
84 | true,
85 | "check-open-brace",
86 | "check-catch",
87 | "check-else",
88 | "check-whitespace"
89 | ],
90 | "prefer-const": true,
91 | "quotemark": [
92 | true,
93 | "single"
94 | ],
95 | "radix": true,
96 | "semicolon": [
97 | true,
98 | "always"
99 | ],
100 | "triple-equals": [
101 | true,
102 | "allow-null-check"
103 | ],
104 | "typedef-whitespace": [
105 | true,
106 | {
107 | "call-signature": "nospace",
108 | "index-signature": "nospace",
109 | "parameter": "nospace",
110 | "property-declaration": "nospace",
111 | "variable-declaration": "nospace"
112 | }
113 | ],
114 | "unified-signatures": true,
115 | "variable-name": false,
116 | "whitespace": [
117 | true,
118 | "check-branch",
119 | "check-decl",
120 | "check-operator",
121 | "check-separator",
122 | "check-type"
123 | ],
124 | "no-output-on-prefix": true,
125 | "use-input-property-decorator": true,
126 | "use-output-property-decorator": true,
127 | "use-host-property-decorator": true,
128 | "no-input-rename": true,
129 | "no-output-rename": true,
130 | "use-life-cycle-interface": true,
131 | "use-pipe-transform-interface": true,
132 | "component-class-suffix": true,
133 | "directive-class-suffix": true
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
3 | "schematics": {
4 | "ng-add": {
5 | "description": "Generates all NgRx Entity files for an application",
6 | "factory": "./ngrx-entity/index",
7 | "schema": "./ngrx-entity/schema.json"
8 | },
9 | "add": {
10 | "description": "Generates all NgRx Entity files for an application",
11 | "factory": "./ngrx-entity/index",
12 | "schema": "./ngrx-entity/schema.json"
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/ngrx-entity/__files__/__name@dasherize@if-flat__/__name@dasherize__.actions.ts:
--------------------------------------------------------------------------------
1 | import { Action } from '@ngrx/store';
2 | import { Update } from '@ngrx/entity';
3 | import { <%= classify(name) %> } from './<%= dasherize(name) %>.model';
4 | import { <%= classify(name) %>SearchQuery } from './<%= dasherize(name) %>.reducer';
5 |
6 | export enum <%= classify(name) %>ActionTypes {
7 | Create<%= classify(name) %> = '[<%= classify(name) %>] Create',
8 | Create<%= classify(name) %>Success = '[<%= classify(name) %>] Insert Success',
9 | Create<%= classify(name) %>Fail = '[<%= classify(name) %>] Insert Fail',
10 |
11 | SearchAll<%= classify(name) %>Entities = '[<%= classify(name) %>] Search',
12 | SearchAll<%= classify(name) %>EntitiesSuccess = '[<%= classify(name) %>] Search Success',
13 | SearchAll<%= classify(name) %>EntitiesFail = '[<%= classify(name) %>] Search Fail',
14 |
15 | Load<%= classify(name) %>ById = '[<%= classify(name) %>] Load By ID',
16 | Load<%= classify(name) %>ByIdSuccess = '[<%= classify(name) %>] Load Success',
17 | Load<%= classify(name) %>ByIdFail = '[<%= classify(name) %>] Load Fail',
18 |
19 | Update<%= classify(name) %> = '[<%= classify(name) %>] Update',
20 | Update<%= classify(name) %>Success = '[<%= classify(name) %>] Update Success',
21 | Update<%= classify(name) %>Fail = '[<%= classify(name) %>] Update Fail',
22 |
23 | Delete<%= classify(name) %>ById = '[<%= classify(name) %>] Delete By ID',
24 | Delete<%= classify(name) %>ByIdSuccess = '[<%= classify(name) %>] Delete Success',
25 | Delete<%= classify(name) %>ByIdFail = '[<%= classify(name) %>] Delete Fail',
26 |
27 | SetSearchQuery = '[<%= classify(name) %>] Set Search Query',
28 | Select<%= classify(name) %>ById = '[<%= classify(name) %>] Select By ID'
29 | }
30 |
31 | // ========================================= CREATE
32 |
33 | export class Create<%= classify(name) %> implements Action {
34 | readonly type = <%= classify(name) %>ActionTypes.Create<%= classify(name) %>;
35 | constructor(public payload: { <%= name %>: <%= classify(name) %> }) {}
36 | }
37 |
38 | export class Create<%= classify(name) %>Success implements Action {
39 | readonly type = <%= classify(name) %>ActionTypes.Create<%= classify(name) %>Success;
40 | constructor(public payload: { result: <%= classify(name) %> }) {}
41 | }
42 |
43 | export class Create<%= classify(name) %>Fail implements Action {
44 | readonly type = <%= classify(name) %>ActionTypes.Create<%= classify(name) %>Fail;
45 | constructor(public payload: { error: string }) {}
46 | }
47 |
48 | // ========================================= SEARCH
49 |
50 | export class SearchAll<%= classify(name) %>Entities implements Action {
51 | readonly type = <%= classify(name) %>ActionTypes.SearchAll<%= classify(name) %>Entities;
52 | }
53 |
54 | export class SearchAll<%= classify(name) %>EntitiesSuccess implements Action {
55 | readonly type = <%= classify(name) %>ActionTypes.SearchAll<%= classify(name) %>EntitiesSuccess;
56 | constructor(public payload: { result: Array<<%= classify(name) %>> }) {}
57 | }
58 |
59 | export class SearchAll<%= classify(name) %>EntitiesFail implements Action {
60 | readonly type = <%= classify(name) %>ActionTypes.SearchAll<%= classify(name) %>EntitiesFail;
61 | constructor(public payload: { error: string }) {}
62 | }
63 |
64 | // ========================================= LOAD BY ID
65 |
66 | export class Load<%= classify(name) %>ById implements Action {
67 | readonly type = <%= classify(name) %>ActionTypes.Load<%= classify(name) %>ById;
68 | constructor(public payload: { id: number }) {}
69 | }
70 |
71 | export class Load<%= classify(name) %>ByIdSuccess implements Action {
72 | readonly type = <%= classify(name) %>ActionTypes.Load<%= classify(name) %>ByIdSuccess;
73 | constructor(public payload: { result: <%= classify(name) %> }) {}
74 | }
75 |
76 | export class Load<%= classify(name) %>ByIdFail implements Action {
77 | readonly type = <%= classify(name) %>ActionTypes.Load<%= classify(name) %>ByIdFail;
78 | constructor(public payload: { error: string }) {}
79 | }
80 |
81 | // ========================================= UPDATE
82 |
83 | export class Update<%= classify(name) %> implements Action {
84 | readonly type = <%= classify(name) %>ActionTypes.Update<%= classify(name) %>;
85 | constructor(public payload: { <%= name %>: <%= classify(name) %> }) {}
86 | }
87 |
88 | export class Update<%= classify(name) %>Success implements Action {
89 | readonly type = <%= classify(name) %>ActionTypes.Update<%= classify(name) %>Success;
90 | constructor(public payload: { update: Update<<%= classify(name) %>> }) {}
91 | }
92 |
93 | export class Update<%= classify(name) %>Fail implements Action {
94 | readonly type = <%= classify(name) %>ActionTypes.Update<%= classify(name) %>Fail;
95 | constructor(public payload: { error: string }) {}
96 | }
97 |
98 | // ========================================= DELETE
99 |
100 | export class Delete<%= classify(name) %>ById implements Action {
101 | readonly type = <%= classify(name) %>ActionTypes.Delete<%= classify(name) %>ById;
102 | constructor(public payload: { id: number }) {}
103 | }
104 |
105 | export class Delete<%= classify(name) %>ByIdSuccess implements Action {
106 | readonly type = <%= classify(name) %>ActionTypes.Delete<%= classify(name) %>ByIdSuccess;
107 | constructor(public payload: { id: number }) {}
108 | }
109 |
110 | export class Delete<%= classify(name) %>ByIdFail implements Action {
111 | readonly type = <%= classify(name) %>ActionTypes.Delete<%= classify(name) %>ByIdFail;
112 | constructor(public payload: { error: string }) {}
113 | }
114 |
115 | // ========================================= QUERY
116 |
117 | export class SetSearchQuery implements Action {
118 | readonly type = <%= classify(name) %>ActionTypes.SetSearchQuery;
119 | constructor(public payload: Partial<<%= classify(name) %>SearchQuery>) {}
120 | }
121 |
122 | // ========================================= SELECTED ID
123 |
124 | export class Select<%= classify(name) %>ById implements Action {
125 | readonly type = <%= classify(name) %>ActionTypes.Select<%= classify(name) %>ById;
126 | constructor(public payload: { id: number }) {}
127 | }
128 |
129 | export type <%= classify(name) %>Actions =
130 | | Create<%= classify(name) %>
131 | | Create<%= classify(name) %>Success
132 | | Create<%= classify(name) %>Fail
133 | | SearchAll<%= classify(name) %>Entities
134 | | SearchAll<%= classify(name) %>EntitiesSuccess
135 | | SearchAll<%= classify(name) %>EntitiesFail
136 | | Load<%= classify(name) %>ById
137 | | Load<%= classify(name) %>ByIdSuccess
138 | | Load<%= classify(name) %>ByIdFail
139 | | Update<%= classify(name) %>
140 | | Update<%= classify(name) %>Success
141 | | Update<%= classify(name) %>Fail
142 | | Delete<%= classify(name) %>ById
143 | | Delete<%= classify(name) %>ByIdSuccess
144 | | Delete<%= classify(name) %>ByIdFail
145 | | SetSearchQuery
146 | | Select<%= classify(name) %>ById;
147 |
--------------------------------------------------------------------------------
/src/ngrx-entity/__files__/__name@dasherize@if-flat__/__name@dasherize__.effects.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { provideMockActions } from '@ngrx/effects/testing';
3 | import { cold, hot } from 'jasmine-marbles';
4 | import { Observable } from 'rxjs';
5 |
6 | import {
7 | Create<%= classify(name) %>,
8 | Create<%= classify(name) %>Success,
9 | Create<%= classify(name) %>Fail,
10 | SearchAll<%= classify(name) %>Entities,
11 | SearchAll<%= classify(name) %>EntitiesSuccess,
12 | SearchAll<%= classify(name) %>EntitiesFail,
13 | Load<%= classify(name) %>ById,
14 | Load<%= classify(name) %>ByIdSuccess,
15 | Load<%= classify(name) %>ByIdFail,
16 | Update<%= classify(name) %>,
17 | Update<%= classify(name) %>Success,
18 | Update<%= classify(name) %>Fail,
19 | Delete<%= classify(name) %>ById,
20 | Delete<%= classify(name) %>ByIdSuccess,
21 | Delete<%= classify(name) %>ByIdFail
22 | } from './<%= dasherize(name) %>.actions';
23 | import { generate<%= classify(name) %>, generate<%= classify(name) %>Array } from './<%= dasherize(name) %>.model';
24 | // TODO: Change this path when you move your service file:
25 | import { <%= classify(name) %>Service } from './<%= dasherize(name) %>.service';
26 | import { <%= classify(name) %>Effects } from './<%= dasherize(name) %>.effects';
27 |
28 | describe('<%= classify(name) %>Effects', () => {
29 | let actions: Observable;
30 | let effects: <%= classify(name) %>Effects;
31 | let service;
32 |
33 | beforeEach(() => {
34 | TestBed.configureTestingModule({
35 | providers: [
36 | <%= classify(name) %>Effects,
37 | provideMockActions(() => actions),
38 | {
39 | provide: <%= classify(name) %>Service,
40 | useValue: jasmine.createSpyObj('service', [
41 | 'create',
42 | 'search',
43 | 'getById',
44 | 'update',
45 | 'deleteById'
46 | ])
47 | }
48 | ]
49 | });
50 |
51 | effects = TestBed.get(<%= classify(name) %>Effects);
52 | service = TestBed.get(<%= classify(name) %>Service);
53 | });
54 |
55 | it('should be constructed', () => {
56 | expect(effects).toBeTruthy();
57 | });
58 |
59 | describe('create', () => {
60 | it('should return Create<%= classify(name) %>Success action with entity on success', () => {
61 | const entity = generate<%= classify(name) %>();
62 | const insertAction = new Create<%= classify(name) %>({ <%= name %>: entity });
63 | const successAction = new Create<%= classify(name) %>Success({ result: entity });
64 |
65 | actions = hot('a-', { a: insertAction });
66 | service.create.and.returnValue(cold('-e|', { e: entity }));
67 | const expected = cold('-s', { s: successAction });
68 |
69 | expect(effects.create).toBeObservable(expected);
70 | });
71 |
72 | it('should return Create<%= classify(name) %>Fail with error object on failure', () => {
73 | const entity = generate<%= classify(name) %>();
74 | const insertAction = new Create<%= classify(name) %>({ <%= name %>: entity });
75 | const failAction = new Create<%= classify(name) %>Fail({ error: 'fail' });
76 |
77 | actions = hot('i-', { i: insertAction });
78 | service.create.and.returnValue(cold('-#|', {}, { message: 'fail'}));
79 | const expected = cold('-f', { f: failAction });
80 |
81 | expect(effects.create).toBeObservable(expected);
82 | });
83 | });
84 |
85 | describe('search', () => {
86 | it('should return SearchAll<%= classify(name) %>EntitiesSuccess action with entities on success', () => {
87 | const entities = generate<%= classify(name) %>Array();
88 | const searchAction = new SearchAll<%= classify(name) %>Entities();
89 | const successAction = new SearchAll<%= classify(name) %>EntitiesSuccess({ result: entities });
90 |
91 | actions = hot('a-', { a: searchAction });
92 | service.search.and.returnValue(cold('-e|', { e: entities }));
93 | const expected = cold('-s', { s: successAction });
94 |
95 | expect(effects.search).toBeObservable(expected);
96 | });
97 |
98 | it('should return SearchAll<%= classify(name) %>EntitiesFail with error object on failure', () => {
99 | const searchAction = new SearchAll<%= classify(name) %>Entities();
100 | const failAction = new SearchAll<%= classify(name) %>EntitiesFail({ error: 'fail' });
101 |
102 | actions = hot('a-', { a: searchAction });
103 | service.search.and.returnValue(cold('-#|', {}, { message: 'fail'}));
104 | const expected = cold('-f', { f: failAction });
105 |
106 | expect(effects.search).toBeObservable(expected);
107 | });
108 | });
109 |
110 | describe('loadById', () => {
111 | it('should return Load<%= classify(name) %>ByIdSuccess action with entity on success', () => {
112 | const entity = generate<%= classify(name) %>();
113 | const loadAction = new Load<%= classify(name) %>ById({ id: entity.id });
114 | const successAction = new Load<%= classify(name) %>ByIdSuccess({ result: entity});
115 |
116 | actions = hot('a-', { a: loadAction });
117 | service.getById.and.returnValue(cold('-e|', { e: entity }));
118 | const expected = cold('-s', { s: successAction });
119 |
120 | expect(effects.loadById).toBeObservable(expected);
121 | });
122 |
123 | it('should return Load<%= classify(name) %>ByIdFail with error object on failure', () => {
124 | const entity = generate<%= classify(name) %>();
125 | const loadAction = new Load<%= classify(name) %>ById({ id: entity.id });
126 | const failAction = new Load<%= classify(name) %>ByIdFail({ error: 'fail' });
127 |
128 | actions = hot('a-', { a: loadAction });
129 | service.getById.and.returnValue(cold('-#|', {}, { message: 'fail'}));
130 | const expected = cold('-f', { f: failAction });
131 |
132 | expect(effects.loadById).toBeObservable(expected);
133 | });
134 | });
135 |
136 | describe('update', () => {
137 | it('should return Update<%= classify(name) %>Success action with entity on success', () => {
138 | const entity = generate<%= classify(name) %>();
139 | const updateAction = new Update<%= classify(name) %>({ <%= name %>: entity });
140 | const successAction = new Update<%= classify(name) %>Success({ update: {
141 | id: entity.id,
142 | changes: entity
143 | }});
144 |
145 | actions = hot('a-', { a: updateAction });
146 | service.update.and.returnValue(cold('-e|', { e: entity }));
147 | const expected = cold('-s', { s: successAction });
148 |
149 | expect(effects.update).toBeObservable(expected);
150 | });
151 |
152 | it('should return Update<%= classify(name) %>Fail with error object on failure', () => {
153 | const entity = generate<%= classify(name) %>();
154 | const updateAction = new Update<%= classify(name) %>({ <%= name %>: entity });
155 | const failAction = new Update<%= classify(name) %>Fail({ error: 'fail' });
156 |
157 | actions = hot('a-', { a: updateAction });
158 | service.update.and.returnValue(cold('-#|', {}, { message: 'fail'}));
159 | const expected = cold('-f', { f: failAction });
160 |
161 | expect(effects.update).toBeObservable(expected);
162 | });
163 | });
164 |
165 | describe('delete', () => {
166 | it('should return Delete<%= classify(name) %>ByIdSuccess action with entity ID on success', () => {
167 | const entity = generate<%= classify(name) %>();
168 | const deleteAction = new Delete<%= classify(name) %>ById({ id: entity.id });
169 | const successAction = new Delete<%= classify(name) %>ByIdSuccess({ id: entity.id });
170 |
171 | actions = hot('a-', { a: deleteAction });
172 | service.deleteById.and.returnValue(cold('-e|', { e: entity.id }));
173 | const expected = cold('-s', { s: successAction });
174 |
175 | expect(effects.delete).toBeObservable(expected);
176 | });
177 |
178 | it('should return Delete<%= classify(name) %>ByIdFail with error object on failure', () => {
179 | const entity = generate<%= classify(name) %>();
180 | const deleteAction = new Delete<%= classify(name) %>ById({ id: entity.id });
181 | const failAction = new Delete<%= classify(name) %>ByIdFail({ error: 'fail' });
182 |
183 | actions = hot('a-', { a: deleteAction });
184 | service.deleteById.and.returnValue(cold('-#|', {}, { message: 'fail'}));
185 | const expected = cold('-f', { f: failAction });
186 |
187 | expect(effects.delete).toBeObservable(expected);
188 | });
189 | });
190 |
191 | });
192 |
--------------------------------------------------------------------------------
/src/ngrx-entity/__files__/__name@dasherize@if-flat__/__name@dasherize__.effects.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | import { Observable, of } from 'rxjs';
4 | import {
5 | exhaustMap,
6 | map,
7 | catchError,
8 | tap,
9 | switchMap
10 | } from 'rxjs/operators';
11 | import { Actions, Effect, ofType } from '@ngrx/effects';
12 | import { Action } from '@ngrx/store';
13 | import { Update } from '@ngrx/entity';
14 |
15 | import {
16 | <%= classify(name) %>ActionTypes,
17 | Create<%= classify(name) %>,
18 | Create<%= classify(name) %>Success,
19 | Create<%= classify(name) %>Fail,
20 | SearchAll<%= classify(name) %>Entities,
21 | SearchAll<%= classify(name) %>EntitiesSuccess,
22 | SearchAll<%= classify(name) %>EntitiesFail,
23 | Load<%= classify(name) %>ById,
24 | Load<%= classify(name) %>ByIdSuccess,
25 | Load<%= classify(name) %>ByIdFail,
26 | Update<%= classify(name) %>,
27 | Update<%= classify(name) %>Success,
28 | Update<%= classify(name) %>Fail,
29 | Delete<%= classify(name) %>ById,
30 | Delete<%= classify(name) %>ByIdSuccess,
31 | Delete<%= classify(name) %>ByIdFail,
32 | SetSearchQuery,
33 | Select<%= classify(name) %>ById
34 | } from './<%= dasherize(name) %>.actions';
35 | import { <%= classify(name) %> } from './<%= dasherize(name) %>.model';
36 | import { <%= classify(name) %>Service } from './<%= dasherize(name) %>.service';
37 |
38 | @Injectable()
39 | export class <%= classify(name) %>Effects {
40 |
41 | // ========================================= CREATE
42 | @Effect()
43 | create: Observable = this.actions$
44 | .pipe(
45 | ofType>(<%= classify(name) %>ActionTypes.Create<%= classify(name) %>),
46 | exhaustMap((action) =>
47 | this.service.create(action.payload.<%= name %>).pipe(
48 | map((<%= name %>: <%= classify(name) %>) => new Create<%= classify(name) %>Success({ result: <%= name %> })),
49 | catchError(({ message }) =>
50 | of(new Create<%= classify(name) %>Fail({ error: message }))
51 | )
52 | )
53 | )
54 | );
55 |
56 | // ========================================= SEARCH
57 | @Effect()
58 | search: Observable = this.actions$
59 | .pipe(
60 | ofTypeEntities>(<%= classify(name) %>ActionTypes.SearchAll<%= classify(name) %>Entities),
61 | // Use the state's filtering and pagination values in this search call
62 | // here if desired:
63 | exhaustMap(() =>
64 | this.service.search().pipe(
65 | map((entities: Array<<%= classify(name) %>>) =>
66 | new SearchAll<%= classify(name) %>EntitiesSuccess({ result: entities })
67 | ),
68 | catchError(({ message }) =>
69 | of(new SearchAll<%= classify(name) %>EntitiesFail({ error: message }))
70 | )
71 | )
72 | )
73 | );
74 |
75 | // ========================================= LOAD BY ID
76 | @Effect()
77 | loadById: Observable = this.actions$
78 | .pipe(
79 | ofTypeById>(<%= classify(name) %>ActionTypes.Load<%= classify(name) %>ById),
80 | switchMap((action) =>
81 | this.service.getById(action.payload.id).pipe(
82 | map((<%= name %>: <%= classify(name) %>) => new Load<%= classify(name) %>ByIdSuccess({ result: <%= name %> })
83 | ),
84 | catchError(({ message }) =>
85 | of(new Load<%= classify(name) %>ByIdFail({ error: message }))
86 | )
87 | )
88 | )
89 | );
90 |
91 | // ========================================= UPDATE
92 | @Effect()
93 | update: Observable = this.actions$
94 | .pipe(
95 | ofType>(<%= classify(name) %>ActionTypes.Update<%= classify(name) %>),
96 | exhaustMap((action) =>
97 | this.service.update(action.payload.<%= name %>).pipe(
98 | map((<%= name %>: <%= classify(name) %>) =>
99 | new Update<%= classify(name) %>Success({
100 | update: {
101 | id: <%= name %>.id,
102 | changes: <%= name %>
103 | } as Update<<%= classify(name) %>>
104 | })
105 | ),
106 | catchError(({ message }) =>
107 | of(new Update<%= classify(name) %>Fail({ error: message }))
108 | )
109 | )
110 | )
111 | );
112 |
113 | // ========================================= DELETE
114 | @Effect()
115 | delete: Observable = this.actions$
116 | .pipe(
117 | ofTypeById>(<%= classify(name) %>ActionTypes.Delete<%= classify(name) %>ById),
118 | exhaustMap((action) =>
119 | this.service.deleteById(action.payload.id).pipe(
120 | map((id: number) => new Delete<%= classify(name) %>ByIdSuccess({ id })),
121 | catchError(({ message }) =>
122 | of(new Delete<%= classify(name) %>ByIdFail({ error: message }))
123 | )
124 | )
125 | )
126 | );
127 |
128 | // ========================================= QUERY
129 | @Effect({
130 | dispatch: false
131 | })
132 | paging: Observable = this.actions$
133 | .pipe(
134 | ofType(<%= classify(name) %>ActionTypes.SetSearchQuery),
135 | tap((action) => {
136 | // do stuff with: action.payload.limit & action.payload.page
137 | })
138 | );
139 |
140 | // ========================================= SELECTED ID
141 | @Effect({
142 | dispatch: false
143 | })
144 | selectedId: Observable = this.actions$
145 | .pipe(
146 | ofType