The follwing example of an editable card has 4 components:
- Home Page
- Editable Try Card
- Try Card Form
- Try Card
In context a 'try' standards for 'try this...', with a suggestion for handling a rhetorical situation.
Home Page
.ts
import { Component, OnInit } from '@angular/core';
import * as fromRoot from '../../reducers';
import * as tryActions from '../../actions/try.actions';
import { map } from 'rxjs/operators';
import { Store, select } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
@Component({
selector: 'arg-home-page',
templateUrl: './home-page.component.html',
styleUrls: ['./home-page.component.css']
})
export class HomePageComponent implements OnInit {
complete$: Observable<any>;
randomTry$: Observable<any>;
editing$: Observable<any>;
constructor(
private store: Store<fromRoot.State>,
private actions$: PopActions,
) {
this.randomTry$ = store.pipe(select(fromRoot.getSelectedTry));
this.editing$ = store.pipe(select(fromRoot.selectTryEditing));
}
ngOnInit() {
this.store.dispatch(new tryActions.Get());
}
onSave(data) {
this.store.dispatch(new tryActions.Put(data));
}
onEdit() {
this.store.dispatch(new tryActions.Edit());
}
onRefresh() {
this.store.dispatch(new tryActions.SetRandom());
}
}
.html
<arg-navigation title="Home">
<arg-editable-try-card [try]="randomTry$ | async" [editing]="editing$ | async" (save)="onSave($event)" (edit)="onEdit()" (refresh)="onRefresh()"></arg-editable-try-card>
</arg-navigation>
Editable Try Card
.ts
import { Component, OnInit, Input, Output, EventEmitter, } from '@angular/core';
@Component({
selector: 'arg-editable-try-card',
templateUrl: './editable-try-card.component.html',
styleUrls: ['./editable-try-card.component.css']
})
export class EditableTryCardComponent implements OnInit {
@Input() try;
@Input() editing;
@Output() save: EventEmitter<any> = new EventEmitter();
@Output() edit: EventEmitter<any> = new EventEmitter();
@Output() refresh: EventEmitter<any> = new EventEmitter();
constructor() { }
ngOnInit() {
}
}
.html
<arg-try-card [try]="try" (edit)="edit.emit()" (refresh)="refresh.emit()" *ngIf="!editing; else editForm"></arg-try-card>
<ng-template #editForm>
<arg-try-card-form [try]="try" (cancel)="edit.emit()" (save)="save.emit($event)" ></arg-try-card-form>
</ng-template>
Try Card
.ts
import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core';
@Component({
selector: 'arg-try-card',
templateUrl: './try-card.component.html',
styleUrls: ['./try-card.component.css']
})
export class TryCardComponent implements OnInit {
@Input() try;
@Output() edit: EventEmitter<any> = new EventEmitter();
@Output() refresh: EventEmitter<any> = new EventEmitter();
constructor(
) {
}
ngOnInit() {
}
}
.html
<section>
<mat-card>
<mat-card-header>
<mat-card-title-group>
<mat-card-title>Try This...</mat-card-title>
<mat-card-subtitle>{{ try?.title }}</mat-card-subtitle>
</mat-card-title-group>
<mat-card-title-group>
<button mat-icon-button (click)="refresh.emit()">
<mat-icon>refresh</mat-icon>
</button>
<button mat-icon-button (click)="edit.emit()">
<mat-icon>edit</mat-icon>
</button>
</mat-card-title-group>
</mat-card-header>
<mat-card-content>
<div class="mat-body-1" [innerHTML]="try?.text | markdown"></div>
</mat-card-content>
<mat-card-actions>
</mat-card-actions>
<div class="footer">
<span class="mat-body-1">#{{try?.id}}</span>
<span class="mat-body-1">Chapter {{ try?.chapter?.id }}, Page {{ try?.page }}</span>
</div>
</mat-card>
</section>
Try Card Form
.ts
import { Component, OnInit, Input, EventEmitter, Output, NgZone } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
@Component({
selector: 'arg-try-card-form',
templateUrl: './try-card-form.component.html',
styleUrls: ['./try-card-form.component.css']
})
export class TryCardFormComponent implements OnInit {
@Input() set try(data) {
this.form.get('title').patchValue(data.title);
this.form.get('text').patchValue(data.text);
this.form.get('page').patchValue(data.page);
this.form.get('id').patchValue(data.id);
this.chapter = data.chapter.id;
}
@Output() save: EventEmitter<any> = new EventEmitter();
@Output() cancel: EventEmitter<any> = new EventEmitter();
form: FormGroup;
chapter: number;
id: number;
constructor(
private fb: FormBuilder,
private zone: NgZone
) {
this.form = this.fb.group({
id: '',
title: '',
text: '',
page: ''
});
}
ngOnInit() {
}
}
.html
<section>
<form [formGroup]="form">
<mat-card>
<mat-card-header>
<mat-card-title-group>
<mat-card-title>Try This...</mat-card-title>
<mat-card-subtitle>
<mat-form-field>
<mat-placeholder>Title</mat-placeholder>
<input matInput formControlName="title">
</mat-form-field>
</mat-card-subtitle>
</mat-card-title-group>
<mat-card-title-group>
<button mat-icon-button (click)="cancel.emit()">
<mat-icon>cancel</mat-icon>
</button>
<button mat-icon-button (click)="save.emit(form.value)">
<mat-icon>done</mat-icon>
</button>
</mat-card-title-group>
</mat-card-header>
<mat-card-content>
<mat-form-field id="text">
<mat-placeholder>Text</mat-placeholder>
<textarea matInput formControlName="text"></textarea>
</mat-form-field>
</mat-card-content>
<mat-card-actions>
</mat-card-actions>
<div class="footer">
<span class="mat-body-1">#{{form.get('id').value}}</span>
<span class="mat-body-1">
<mat-form-field id="chapter">
<mat-placeholder>Chapter</mat-placeholder>
<input matInput [value]="chapter" disabled>
</mat-form-field>,
<mat-form-field id="page">
<mat-placeholder>Page</mat-placeholder>
<input matInput formControlName="page">
</mat-form-field>
</span>
</div>
</mat-card>
</form>
</section>
Try Model
.ts
import { Chapter } from './chapter.model';
export interface Try {
id: string;
page: string;
title: string;
text: string;
chapter: Chapter;
createdAt?: string;
updatedAt?: string;
}
Try Effects
.ts
import { Injectable } from '@angular/core';
import { Actions, Effect, ofType } from '@ngrx/effects';
import { Action } from '@ngrx/store';
import { ApiService } from '../api.service';
import { TryActionTypes, Get, GetComplete, Put, PutComplete, SetRandom } from '../actions/try.actions';
import {
map,
mergeMap,
switchMap,
skip,
takeUntil,
catchError,
} from 'rxjs/operators';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class TryEffects {
@Effect()
getTry$: Observable<Action> = this.actions$.pipe(
ofType<Get>(TryActionTypes.GET),
switchMap(() => {
return this.api.getTry()
.pipe(
mergeMap((tries) => [
new GetComplete(tries),
new SetRandom()
])
);
})
);
@Effect()
putTry$: Observable<Action> = this.actions$.pipe(
ofType<Put>(TryActionTypes.PUT),
switchMap((action) => {
return this.api.putTry(action.payload)
.pipe(
map(data => new PutComplete(data))
);
})
);
constructor(
private actions$: Actions,
private api: ApiService,
) { }
}
Try Reducer
.ts
import { Action } from '@ngrx/store';
import { createEntityAdapter, EntityAdapter, EntityState } from '@ngrx/entity';
import { Try } from '../models/try.model';
import { TryActionTypes, TryActions } from '../actions/try.actions';
import { ChapterActionTypes, ChapterActions } from '../actions/chapter.actions';
export interface State extends EntityState<Try> {
selectedId: string | number;
editing: boolean;
}
export const adapter: EntityAdapter<Try> = createEntityAdapter<Try>({
});
export const initialState: State = adapter.getInitialState({
selectedId: null,
editing: false
});
export function reducer(state = initialState, action: TryActions | ChapterActions): State {
switch (action.type) {
case TryActionTypes.GET_COMPLETE:
return adapter.addMany(action.payload, state);
case TryActionTypes.SET_RANDOM:
const randomIndex = Math.floor(Math.random() * (state.ids.length));
const randomId = state.ids[randomIndex];
return {
...state,
selectedId: randomId
};
case TryActionTypes.EDIT:
return {
...state,
editing: !state.editing
};
case TryActionTypes.PUT_COMPLETE:
const changes = {
id: action.payload.id,
changes: {
text: action.payload.text,
title: action.payload.title,
page: action.payload.page,
}
};
return adapter.updateOne(changes, {
...state,
editing: false
});
default:
return state;
}
}
export const {
selectIds: selectTryIds,
selectEntities: selectTryEntities
} = adapter.getSelectors();
export const selectedId = (state: State) => state.selectedId;
export const editing = (state: State) => state.editing;
Selectors
.ts
export const selectTry = (state: State) => state.try;
export const selectTryIds = createSelector(
selectTry,
fromTry.selectTryIds
);
export const selectTryEntities = createSelector(
selectTry,
fromTry.selectTryEntities
);
export const selectTrySelectedId = createSelector(
selectTry,
fromTry.selectedId
);
export const selectTryEditing = createSelector(
selectTry,
fromTry.editing
);
export const getSelectedTry = createSelector(
selectTryEntities,
selectTrySelectedId,
(entities, id) => entities[id]
);