-
-
Save NetanelBasal/8c033644ea0c6c8105a27072ae1af461 to your computer and use it in GitHub Desktop.
@Component({ | |
selector: 'add-story-form', | |
template: ` | |
<div class="container"> | |
<h1>New Story</h1> | |
<form [formGroup]="newStory" | |
(submit)="submit($event)" | |
(success)="onSuccess()" | |
(error)="onError($event)" | |
connectForm="newStory"> | |
<div class="form-group"> | |
<input type="text" formControlName="title" class="form-control" placeholder="Title"> | |
</div> | |
<div class="form-group"> | |
<textarea formControlName="description" | |
placeholder="Story.." | |
cols="10" rows="3" class="form-control"></textarea> | |
</div> | |
<div class="form-group"> | |
<input type="checkbox" formControlName="draft"> Draft | |
</div> | |
<select formControlName="category"> | |
<option *ngFor="let option of options" [ngValue]="option.id">{{option.label}}</option> | |
</select> | |
<button [disabled]="newStory.invalid" class="btn btn-primary" type="submit">Submit</button> | |
</form> | |
<div class="alert alert-success" *ngIf="success">Success!</div> | |
<div class="alert alert-danger" *ngIf="error">{{error}}</div> | |
</div> | |
` | |
}) | |
export class NewStoryComponent { | |
options = [{ id: 1, label: 'Category One' }, { id: 2, label: 'Category Two' }] | |
onError( error ) { | |
this.error = error; | |
} | |
onSuccess() { | |
this.success = true; | |
} | |
ngOnInit() { | |
this.newStory = new FormGroup({ | |
title: new FormControl(null, Validators.required), | |
description: new FormControl(null, Validators.required), | |
draft: new FormControl(false), | |
category: new FormControl(this.options[1].id, Validators.required) | |
}); | |
} | |
submit() { | |
this.success = false; | |
this.store.dispatch({ | |
type: 'ADD_STORY' | |
}) | |
} | |
} |
import { Directive, EventEmitter, Input, Output } from '@angular/core'; | |
import { FormGroupDirective } from '@angular/forms'; | |
import { Store } from '@ngrx/store'; | |
import { Subscription } from 'rxjs/Subscription'; | |
import { Actions } from '@ngrx/effects'; | |
const FORM_SUBMIT_SUCCESS = 'FORM_SUBMIT_SUCCESS'; | |
const FORM_SUBMIT_ERROR = 'FORM_SUBMIT_ERROR'; | |
const UPDATE_FORM = 'UPDATE_FORM'; | |
export const formSuccessAction = path => ({ | |
type: FORM_SUBMIT_SUCCESS, | |
payload: { | |
path | |
} | |
}); | |
export const formErrorAction = ( path, error ) => ({ | |
type: FORM_SUBMIT_ERROR, | |
payload: { | |
path, | |
error | |
} | |
}); | |
@Directive({ | |
selector: '[connectForm]' | |
}) | |
export class ConnectFormDirective { | |
@Input('connectForm') path : string; | |
@Input() debounce : number = 300; | |
@Output() error = new EventEmitter(); | |
@Output() success = new EventEmitter(); | |
formChange : Subscription; | |
formSuccess : Subscription; | |
formError : Subscription; | |
constructor( private formGroupDirective : FormGroupDirective, | |
private actions$ : Actions, | |
private store : Store<any> ) { | |
} | |
ngOnInit() { | |
this.store.select(state => state.forms[this.path]).take(1).subscribe(val => { | |
this.formGroupDirective.form.patchValue(val); | |
}); | |
this.formChange = this.formGroupDirective.form.valueChanges | |
.debounceTime(this.debounce).subscribe(value => { | |
this.store.dispatch({ | |
type: UPDATE_FORM, | |
payload: { | |
value, | |
path: this.path, | |
} | |
}); | |
}); | |
this.formSuccess = this.actions$ | |
.ofType(FORM_SUBMIT_SUCCESS) | |
.filter(( { payload } ) => payload.path === this.path) | |
.subscribe(() => { | |
this.formGroupDirective.form.reset(); | |
this.success.emit(); | |
}); | |
this.formError = this.actions$ | |
.ofType(FORM_SUBMIT_ERROR) | |
.filter(( { payload } ) => payload.path === this.path) | |
.subscribe(( { payload } ) => this.error.emit(payload.error)) | |
} | |
ngOnDestroy() { | |
this.formChange.unsubscribe(); | |
this.formError.unsubscribe(); | |
this.formSuccess.unsubscribe(); | |
} | |
} |
import { from } from 'rxjs/observable/from'; | |
import { formErrorAction, formSuccessAction } from '../connect-form.directive'; | |
@Injectable() | |
export class StoryEffects { | |
constructor( private storyService : StoryService, | |
private store : Store<any>, | |
private actions$ : Actions ) { | |
} | |
@Effect() addStory$ = this.actions$ | |
.ofType('ADD_STORY') | |
.switchMap(action => | |
this.storyService.add(action.payload) | |
.switchMap(story => (from([{ | |
type: 'ADD_STORY_SUCCESS' | |
}, formSuccessAction('newStory')]))) | |
.catch(err => (of(formErrorAction('newStory', err)))) | |
) | |
} |
I have looked at the code you have here and it some parts seem to be missing. For example StoryService
@NetanelBasal I am trying to use this in he ngrx4 example app. It is separated into multiple modules, each module broken into multiple stores. Problem I have is that since the store is imported into this directive, need to make copy of directive for each store. Any ideas how to abstract the store from this so I can just have one copy of the directive?
@snapnurse-joshuamatthews
make a base effect class and inherit that class on all the effects classes, and in the base path use the path aliasing feature of angular/cli and tsconfig so you can import from '(at)connect-form/connect-form.directive';
Add all the possible paths to that directive to the paths array, then you are good with only one directive
this is not truely sync. store changes wont be reflected in form.
import { of } from 'rxjs/observable/of';