Added Edit + Create comment funtionality
This commit is contained in:
@@ -1,40 +1,61 @@
|
|||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { EffectsModule } from '@ngrx/effects';
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
import { EffectsModule } from '@ngrx/effects';
|
||||||
import { StoreModule } from '@ngrx/store';
|
import { StoreModule } from '@ngrx/store';
|
||||||
|
|
||||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { MatChipsModule } from '@angular/material/chips';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { reducers, metaReducers } from './reducers';
|
import { reducers, metaReducers } from './reducers';
|
||||||
import { CommentStoreModule } from './store';
|
import { CommentStoreModule } from './store';
|
||||||
|
import { PipesModule } from './pipes/pipes.module';
|
||||||
|
import { DirectivesModule } from './directives/directives.module';
|
||||||
|
|
||||||
import { CommentCardComponent } from './comment-card/comment-card.component';
|
import { CommentCardComponent } from './comment-card/comment-card.component';
|
||||||
import { CommentsListComponent } from './comments-list/comments-list.component';
|
import { CommentsListComponent } from './comments-list/comments-list.component';
|
||||||
import { PipesModule } from './pipes/pipes.module';
|
import { CommentCardEditComponent } from './comment-card-edit/comment-card-edit.component';
|
||||||
|
|
||||||
|
const materialModules = [
|
||||||
|
MatAutocompleteModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatCardModule,
|
||||||
|
MatChipsModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatToolbarModule,
|
||||||
|
MatInputModule
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
CommentCardComponent,
|
CommentCardComponent,
|
||||||
CommentsListComponent
|
CommentsListComponent,
|
||||||
|
CommentCardEditComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FormsModule,
|
||||||
StoreModule.forRoot(reducers, {
|
StoreModule.forRoot(reducers, {
|
||||||
metaReducers
|
metaReducers
|
||||||
}),
|
}),
|
||||||
EffectsModule.forRoot([]),
|
EffectsModule.forRoot([]),
|
||||||
CommentStoreModule,
|
CommentStoreModule,
|
||||||
MatToolbarModule,
|
...materialModules,
|
||||||
MatCardModule,
|
PipesModule,
|
||||||
MatButtonModule,
|
DirectivesModule
|
||||||
MatIconModule,
|
|
||||||
PipesModule
|
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
|
|||||||
36
src/app/comment-card-edit/comment-card-edit.component.html
Normal file
36
src/app/comment-card-edit/comment-card-edit.component.html
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<mat-card class="comment-card">
|
||||||
|
<form [formGroup]="form" #commentForm="ngForm" (ngSubmit)="submit($event, commentForm)" novalidate>
|
||||||
|
<mat-form-field class="full-width-field">
|
||||||
|
<mat-label>Comment Title</mat-label>
|
||||||
|
<input matInput formControlName="title">
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field class="full-width-field">
|
||||||
|
<mat-chip-list #chipList>
|
||||||
|
<mat-chip *ngFor="let tag of form.value.tags" selectable="true" removable="true" (removed)="removeTag(tag)">
|
||||||
|
{{ tag }}
|
||||||
|
<mat-icon matChipRemove>cancel</mat-icon>
|
||||||
|
</mat-chip>
|
||||||
|
<input placeholder="New tag..." #tagInput [formControl]="tagCtrl" [matAutocomplete]="auto"
|
||||||
|
[matChipInputFor]="chipList" [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||||
|
(matChipInputTokenEnd)="addTag($event)">
|
||||||
|
</mat-chip-list>
|
||||||
|
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
|
||||||
|
<mat-option *ngFor="let tag of filteredTags | async" [value]="tag">
|
||||||
|
{{ tag }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-autocomplete>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field class="full-width-field">
|
||||||
|
<mat-label>Comment Body</mat-label>
|
||||||
|
<textarea matInput rows="6" formControlName="text"></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-card-actions align="end">
|
||||||
|
<button mat-button (click)="cancel()">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button mat-button color="primary" type="submit">
|
||||||
|
{{ !!data?.id ? 'Save' : 'Add' }}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</form>
|
||||||
|
</mat-card>
|
||||||
11
src/app/comment-card-edit/comment-card-edit.component.scss
Normal file
11
src/app/comment-card-edit/comment-card-edit.component.scss
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-card {
|
||||||
|
max-width: 420px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-width-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CommentCardEditComponent } from './comment-card-edit.component';
|
||||||
|
|
||||||
|
describe('CommentCardEditComponent', () => {
|
||||||
|
let component: CommentCardEditComponent;
|
||||||
|
let fixture: ComponentFixture<CommentCardEditComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ CommentCardEditComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CommentCardEditComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
105
src/app/comment-card-edit/comment-card-edit.component.ts
Normal file
105
src/app/comment-card-edit/comment-card-edit.component.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { COMMA, ENTER } from '@angular/cdk/keycodes';
|
||||||
|
import { Component, ElementRef, ViewChild, OnInit, Input } from '@angular/core';
|
||||||
|
import { FormBuilder, Validators, FormGroup } from '@angular/forms';
|
||||||
|
import { MatAutocompleteSelectedEvent, MatAutocomplete } from '@angular/material/autocomplete';
|
||||||
|
import { MatChipInputEvent } from '@angular/material/chips';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { map, startWith, withLatestFrom } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { Comment, CommentFacade } from '../store/comment'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-comment-card-edit',
|
||||||
|
templateUrl: './comment-card-edit.component.html',
|
||||||
|
styleUrls: ['./comment-card-edit.component.scss']
|
||||||
|
})
|
||||||
|
export class CommentCardEditComponent implements OnInit {
|
||||||
|
@ViewChild('tagInput') tagInput: ElementRef<HTMLInputElement>;
|
||||||
|
@ViewChild('auto') matAutocomplete: MatAutocomplete;
|
||||||
|
@Input() data: Comment;
|
||||||
|
form: FormGroup;
|
||||||
|
tagCtrl = this.fb.control([]);
|
||||||
|
filteredTags: Observable<string[]>;
|
||||||
|
separatorKeysCodes: number[] = [ENTER, COMMA];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly fb: FormBuilder,
|
||||||
|
private readonly commentFacade: CommentFacade
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (!!this.data) {
|
||||||
|
this.form = this.fb.group({
|
||||||
|
id: [this.data.id],
|
||||||
|
title: [this.data.title, [Validators.required]],
|
||||||
|
tags: [this.data.tags],
|
||||||
|
text: [this.data.text, [Validators.required]],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.form = this.fb.group({
|
||||||
|
id: [null],
|
||||||
|
title: [null, [Validators.required]],
|
||||||
|
tags: [[]],
|
||||||
|
text: [null, [Validators.required]],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.filteredTags = this.tagCtrl.valueChanges.pipe(
|
||||||
|
startWith(null),
|
||||||
|
withLatestFrom(this.commentFacade.tags$),
|
||||||
|
map(([tag, allTags]) => {
|
||||||
|
if (tag === null) {
|
||||||
|
return [...allTags];
|
||||||
|
}
|
||||||
|
const value = tag.toLowerCase()
|
||||||
|
return allTags.filter(t => t.toLowerCase().includes(value));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
submit(event, commentForm): void {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!!this.form.value.id) {
|
||||||
|
this.commentFacade.edit(this.form.value);
|
||||||
|
} else {
|
||||||
|
this.commentFacade.add(this.form.value);
|
||||||
|
}
|
||||||
|
this.form.reset();
|
||||||
|
commentForm.resetForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel(): void {
|
||||||
|
this.commentFacade.toggleEdit(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
selected(event: MatAutocompleteSelectedEvent): void {
|
||||||
|
const tags = [...this.form.value.tags];
|
||||||
|
this.form.get('tags').setValue([...tags, event.option.viewValue]);
|
||||||
|
this.tagInput.nativeElement.value = '';
|
||||||
|
this.tagCtrl.setValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
addTag(event: MatChipInputEvent): void {
|
||||||
|
const input = event.input;
|
||||||
|
const value = event.value;
|
||||||
|
|
||||||
|
if ((value || '').trim()) {
|
||||||
|
const tags = [...this.form.value.tags];
|
||||||
|
this.form.get('tags').setValue([...tags, value.trim()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input) {
|
||||||
|
input.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tagCtrl.setValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTag(tag: string): void {
|
||||||
|
const tags = [...this.form.value.tags];
|
||||||
|
|
||||||
|
this.form.get('tags').setValue([...tags.filter(t => t !== tag)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
<mat-card class="comment-card">
|
<mat-card class="comment-card">
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title>{{ data.title }}</mat-card-title>
|
<mat-card-title>{{ data.title }}</mat-card-title>
|
||||||
<mat-card-subtitle>{{ data.tags | join }}</mat-card-subtitle>
|
<mat-card-subtitle>
|
||||||
|
<mat-chip-list>
|
||||||
|
<mat-chip *ngFor="let tag of data.tags" color="primary" selected>{{ tag }}</mat-chip>
|
||||||
|
</mat-chip-list>
|
||||||
|
</mat-card-subtitle>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<div [innerHTML]="data.text | safeHTML"></div>
|
<div [innerHTML]="data.text | safeHTML"></div>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
<mat-card-actions align="end">
|
<mat-card-actions align="end">
|
||||||
<button mat-icon-button color="primary" (click)="toggleEdit()">
|
<button mat-button color="primary" (click)="toggleEdit()">
|
||||||
<mat-icon>edit</mat-icon>
|
<mat-icon>edit</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<button mat-icon-button color="warn" (click)="delete()">
|
<button mat-icon-button color="warn" (click)="delete()">
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host:not(:first-of-type) {
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-card {
|
.comment-card {
|
||||||
max-width: 420px;
|
max-width: 420px;
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ export class CommentCardComponent {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
toggleEdit(): void {
|
toggleEdit(): void {
|
||||||
// todo
|
this.commentFacade.toggleEdit(this.data.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(): void {
|
delete(): void {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<section class="comment-list" *ngIf="list$ | async as list; else loading;">
|
<section class="comment-list" *ngIf="list$ | async as list; else loading;">
|
||||||
<ng-container *ngFor="let item of list">
|
<ng-container *ngLet="activeEditId$ | async as activeEditId">
|
||||||
<app-comment-card [data]="item"></app-comment-card>
|
<ng-container *ngFor="let item of list">
|
||||||
|
<app-comment-card *ngIf="activeEditId !== item.id" [data]="item"></app-comment-card>
|
||||||
|
<app-comment-card-edit *ngIf="activeEditId === item.id" [data]="item"></app-comment-card-edit>
|
||||||
|
</ng-container>
|
||||||
|
<app-comment-card-edit *ngIf="!activeEditId"></app-comment-card-edit>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.comment-list {
|
||||||
|
*:not(:first-child) {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import { CommentFacade } from '../store/comment';
|
|||||||
})
|
})
|
||||||
export class CommentsListComponent implements OnInit {
|
export class CommentsListComponent implements OnInit {
|
||||||
list$ = this.commentFacade.list$;
|
list$ = this.commentFacade.list$;
|
||||||
|
activeEditId$ = this.commentFacade.activeEditId$;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly commentFacade: CommentFacade
|
private readonly commentFacade: CommentFacade
|
||||||
|
|||||||
17
src/app/directives/directives.module.ts
Normal file
17
src/app/directives/directives.module.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { NgLetDirective } from './ng-let-directive';
|
||||||
|
|
||||||
|
const exportedDirectives = [
|
||||||
|
NgLetDirective
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
...exportedDirectives
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
...exportedDirectives
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class DirectivesModule { }
|
||||||
34
src/app/directives/ng-let-directive.ts
Normal file
34
src/app/directives/ng-let-directive.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import {
|
||||||
|
Directive,
|
||||||
|
Input,
|
||||||
|
OnInit,
|
||||||
|
TemplateRef,
|
||||||
|
ViewContainerRef
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
export class NgLetContext {
|
||||||
|
$implicit: any = undefined;
|
||||||
|
ngLet: any = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
// tslint:disable-next-line:directive-selector
|
||||||
|
selector: '[ngLet]'
|
||||||
|
})
|
||||||
|
export class NgLetDirective implements OnInit {
|
||||||
|
private readonly _context = new NgLetContext();
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set ngLet(value: any) {
|
||||||
|
this._context.$implicit = this._context.ngLet = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly _vcr: ViewContainerRef,
|
||||||
|
private readonly _templateRef: TemplateRef<NgLetContext>
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this._vcr.createEmbeddedView(this._templateRef, this._context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,9 +16,14 @@ export const loadCommentsFailure = createAction(
|
|||||||
'[Comment/API] Load Comments Failure'
|
'[Comment/API] Load Comments Failure'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const setCommentEditMode = createAction(
|
||||||
|
'[Comment] Set Comment Edit Mode',
|
||||||
|
props<{ id: number }>()
|
||||||
|
);
|
||||||
|
|
||||||
export const addComment = createAction(
|
export const addComment = createAction(
|
||||||
'[Comment/API] Add Comment',
|
'[Comment/API] Add Comment',
|
||||||
props<{ comment: Comment }>()
|
props<{ comment: Partial<Comment> }>()
|
||||||
);
|
);
|
||||||
|
|
||||||
export const upsertComment = createAction(
|
export const upsertComment = createAction(
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class CommentEffects {
|
|||||||
ofType(CommentActions.loadComments),
|
ofType(CommentActions.loadComments),
|
||||||
switchMap(_ =>
|
switchMap(_ =>
|
||||||
this.api.getAll().pipe(
|
this.api.getAll().pipe(
|
||||||
map(data => CommentActions.loadCommentsSuccess({ comments: data as any })), // debug type issue here
|
map(comments => CommentActions.loadCommentsSuccess({ comments })),
|
||||||
catchError(_ => of(CommentActions.loadCommentsFailure()))
|
catchError(_ => of(CommentActions.loadCommentsFailure()))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,23 +1,38 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Store, select } from '@ngrx/store';
|
import { Store, select } from '@ngrx/store';
|
||||||
|
|
||||||
import * as commentActions from './comment.actions';
|
import * as CommentActions from './comment.actions';
|
||||||
import * as query from './comment.selectors'
|
import * as query from './comment.selectors'
|
||||||
|
import { Comment } from './comment.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CommentFacade {
|
export class CommentFacade {
|
||||||
list$ = this.store.pipe(select(query.getAll))
|
list$ = this.store.pipe(select(query.getAll));
|
||||||
|
activeEditId$ = this.store.pipe(select(query.getActiveEditId));
|
||||||
|
tags$ = this.store.pipe(select(query.getAllTags));
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly store: Store
|
private readonly store: Store
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
getAll(): void {
|
getAll(): void {
|
||||||
this.store.dispatch(commentActions.loadComments());
|
this.store.dispatch(CommentActions.loadComments());
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleEdit(id: number): void {
|
||||||
|
this.store.dispatch(CommentActions.setCommentEditMode({ id }));
|
||||||
|
}
|
||||||
|
|
||||||
|
add(comment: Partial<Comment>): void {
|
||||||
|
this.store.dispatch(CommentActions.addComment({ comment }))
|
||||||
|
}
|
||||||
|
|
||||||
|
edit(comment: Comment): void {
|
||||||
|
this.store.dispatch(CommentActions.updateComment({ comment: { id: comment.id, changes: comment } }));
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(id: number): void {
|
delete(id: number): void {
|
||||||
this.store.dispatch(commentActions.deleteComment({ id }))
|
this.store.dispatch(CommentActions.deleteComment({ id }))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { reducer, initialState } from './comment.reducer';
|
|
||||||
|
|
||||||
describe('Comment Reducer', () => {
|
|
||||||
describe('unknown action', () => {
|
|
||||||
it('should return the previous state', () => {
|
|
||||||
const action = {} as any;
|
|
||||||
|
|
||||||
const result = reducer(initialState, action);
|
|
||||||
|
|
||||||
expect(result).toBe(initialState);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,31 +1,44 @@
|
|||||||
import { Action, createReducer, on } from '@ngrx/store';
|
import { createReducer, on } from '@ngrx/store';
|
||||||
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
|
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
|
||||||
|
|
||||||
import { Comment } from './comment.model';
|
import { Comment } from './comment.model';
|
||||||
import * as CommentActions from './comment.actions';
|
import * as CommentActions from './comment.actions';
|
||||||
|
|
||||||
export const commentsFeatureKey = 'comments';
|
export const commentsFeatureKey = 'comments';
|
||||||
|
|
||||||
export interface State extends EntityState<Comment> {
|
export interface State extends EntityState<Comment> {
|
||||||
// additional entities state properties
|
activeEditId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const adapter: EntityAdapter<Comment> = createEntityAdapter<Comment>();
|
export const adapter: EntityAdapter<Comment> = createEntityAdapter<Comment>();
|
||||||
|
|
||||||
export const initialState: State = adapter.getInitialState({
|
export const initialState: State = adapter.getInitialState({
|
||||||
// additional entity state properties
|
activeEditId: undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export const reducer = createReducer(
|
export const reducer = createReducer(
|
||||||
initialState,
|
initialState,
|
||||||
|
on(CommentActions.setCommentEditMode,
|
||||||
|
(state, action) => ({ ...state, activeEditId: action.id })
|
||||||
|
),
|
||||||
on(CommentActions.addComment,
|
on(CommentActions.addComment,
|
||||||
(state, action) => adapter.addOne(action.comment, state)
|
(state, action) => {
|
||||||
|
const newComment = {
|
||||||
|
...action.comment,
|
||||||
|
id: Object.keys(state.entities).length + 1
|
||||||
|
} as Comment;
|
||||||
|
return adapter.addOne(newComment, state)
|
||||||
|
}
|
||||||
),
|
),
|
||||||
on(CommentActions.upsertComment,
|
on(CommentActions.upsertComment,
|
||||||
(state, action) => adapter.upsertOne(action.comment, state)
|
(state, action) => adapter.upsertOne(action.comment, state)
|
||||||
),
|
),
|
||||||
on(CommentActions.updateComment,
|
on(CommentActions.updateComment,
|
||||||
(state, action) => adapter.updateOne(action.comment, state)
|
(state, action) => ({
|
||||||
|
...adapter.updateOne(action.comment, state),
|
||||||
|
activeEditId: undefined
|
||||||
|
})
|
||||||
),
|
),
|
||||||
on(CommentActions.deleteComment,
|
on(CommentActions.deleteComment,
|
||||||
(state, action) => adapter.removeOne(action.id, state)
|
(state, action) => adapter.removeOne(action.id, state)
|
||||||
@@ -42,3 +55,4 @@ export const {
|
|||||||
selectAll,
|
selectAll,
|
||||||
selectTotal,
|
selectTotal,
|
||||||
} = adapter.getSelectors();
|
} = adapter.getSelectors();
|
||||||
|
export const selectActiveEditId = (state: State) => state.activeEditId;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createFeatureSelector, createSelector } from '@ngrx/store';
|
import { createFeatureSelector, createSelector } from '@ngrx/store';
|
||||||
|
|
||||||
import { Comment } from './comment.model';
|
|
||||||
import * as fromComment from './comment.reducer';
|
import * as fromComment from './comment.reducer';
|
||||||
|
|
||||||
export const getState = createFeatureSelector<fromComment.State>(fromComment.commentsFeatureKey);
|
export const getState = createFeatureSelector<fromComment.State>(fromComment.commentsFeatureKey);
|
||||||
export const getAll = createSelector(getState, fromComment.selectAll);
|
export const getAll = createSelector(getState, fromComment.selectAll);
|
||||||
export const getEntities = createSelector(getState, fromComment.selectEntities);
|
export const getEntities = createSelector(getState, fromComment.selectEntities);
|
||||||
|
export const getActiveEditId = createSelector(getState, fromComment.selectActiveEditId);
|
||||||
|
export const getAllTags = createSelector(getAll, comments => Array.from(new Set(comments.map(c => c.tags).flat()).values()));
|
||||||
@@ -3,6 +3,8 @@ import { HttpClient } from '@angular/common/http';
|
|||||||
|
|
||||||
import { map } from 'rxjs/operators'
|
import { map } from 'rxjs/operators'
|
||||||
|
|
||||||
|
import { Comment } from './comment.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CommentService {
|
export class CommentService {
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"target": "es2015",
|
"target": "es2015",
|
||||||
"lib": [
|
"lib": [
|
||||||
"es2018",
|
"es2018",
|
||||||
|
"ESNext",
|
||||||
"dom"
|
"dom"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -20,4 +21,4 @@
|
|||||||
"fullTemplateTypeCheck": true,
|
"fullTemplateTypeCheck": true,
|
||||||
"strictInjectionParameters": true
|
"strictInjectionParameters": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user