Support for loading, deleting, tag joining, and
This commit is contained in:
@@ -2,4 +2,8 @@
|
|||||||
<mat-toolbar-row>
|
<mat-toolbar-row>
|
||||||
<span>Material Comments</span>
|
<span>Material Comments</span>
|
||||||
</mat-toolbar-row>
|
</mat-toolbar-row>
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
|
|
||||||
|
<div class="app-body">
|
||||||
|
<app-comments-list></app-comments-list>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.app-body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
@@ -4,22 +4,37 @@ import { EffectsModule } from '@ngrx/effects';
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { StoreModule } from '@ngrx/store';
|
import { StoreModule } from '@ngrx/store';
|
||||||
|
|
||||||
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
|
||||||
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 { CommentCardComponent } from './comment-card/comment-card.component';
|
import { CommentCardComponent } from './comment-card/comment-card.component';
|
||||||
|
import { CommentsListComponent } from './comments-list/comments-list.component';
|
||||||
|
import { PipesModule } from './pipes/pipes.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent
|
AppComponent,
|
||||||
|
CommentCardComponent,
|
||||||
|
CommentsListComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
StoreModule.forRoot(reducers, {
|
StoreModule.forRoot(reducers, {
|
||||||
metaReducers
|
metaReducers
|
||||||
}),
|
}),
|
||||||
EffectsModule.forRoot([]),
|
EffectsModule.forRoot([]),
|
||||||
BrowserAnimationsModule,
|
CommentStoreModule,
|
||||||
MatToolbarModule
|
MatToolbarModule,
|
||||||
|
MatCardModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatIconModule,
|
||||||
|
PipesModule
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
|
|||||||
17
src/app/comment-card/comment-card.component.html
Normal file
17
src/app/comment-card/comment-card.component.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<mat-card class="comment-card">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>{{ data.title }}</mat-card-title>
|
||||||
|
<mat-card-subtitle>{{ data.tags | join }}</mat-card-subtitle>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<div [innerHTML]="data.text | safeHTML"></div>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions align="end">
|
||||||
|
<button mat-icon-button color="primary" (click)="toggleEdit()">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button color="warn" (click)="delete()">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
11
src/app/comment-card/comment-card.component.scss
Normal file
11
src/app/comment-card/comment-card.component.scss
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host:not(:first-of-type) {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-card {
|
||||||
|
max-width: 420px;
|
||||||
|
}
|
||||||
25
src/app/comment-card/comment-card.component.spec.ts
Normal file
25
src/app/comment-card/comment-card.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CommentCardComponent } from './comment-card.component';
|
||||||
|
|
||||||
|
describe('CommentCardComponent', () => {
|
||||||
|
let component: CommentCardComponent;
|
||||||
|
let fixture: ComponentFixture<CommentCardComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ CommentCardComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CommentCardComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
25
src/app/comment-card/comment-card.component.ts
Normal file
25
src/app/comment-card/comment-card.component.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
|
import { CommentFacade, Comment } from '../store/comment';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-comment-card',
|
||||||
|
templateUrl: './comment-card.component.html',
|
||||||
|
styleUrls: ['./comment-card.component.scss']
|
||||||
|
})
|
||||||
|
export class CommentCardComponent {
|
||||||
|
@Input() data: Comment;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly commentFacade: CommentFacade
|
||||||
|
) { }
|
||||||
|
|
||||||
|
toggleEdit(): void {
|
||||||
|
// todo
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(): void {
|
||||||
|
this.commentFacade.delete(this.data.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
9
src/app/comments-list/comments-list.component.html
Normal file
9
src/app/comments-list/comments-list.component.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<section class="comment-list" *ngIf="list$ | async as list; else loading;">
|
||||||
|
<ng-container *ngFor="let item of list">
|
||||||
|
<app-comment-card [data]="item"></app-comment-card>
|
||||||
|
</ng-container>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<ng-template #loading>
|
||||||
|
<p>Loading...</p>
|
||||||
|
</ng-template>
|
||||||
0
src/app/comments-list/comments-list.component.scss
Normal file
0
src/app/comments-list/comments-list.component.scss
Normal file
25
src/app/comments-list/comments-list.component.spec.ts
Normal file
25
src/app/comments-list/comments-list.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CommentsListComponent } from './comments-list.component';
|
||||||
|
|
||||||
|
describe('CommentsListComponent', () => {
|
||||||
|
let component: CommentsListComponent;
|
||||||
|
let fixture: ComponentFixture<CommentsListComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ CommentsListComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CommentsListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
23
src/app/comments-list/comments-list.component.ts
Normal file
23
src/app/comments-list/comments-list.component.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
import { CommentFacade } from '../store/comment';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-comments-list',
|
||||||
|
templateUrl: './comments-list.component.html',
|
||||||
|
styleUrls: ['./comments-list.component.scss']
|
||||||
|
})
|
||||||
|
export class CommentsListComponent implements OnInit {
|
||||||
|
list$ = this.commentFacade.list$;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly commentFacade: CommentFacade
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
// load comments, typically would do this with an effect watching the route,
|
||||||
|
// but for simplicity I'm just calling it here.
|
||||||
|
this.commentFacade.getAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
23
src/app/pipes/join.pipe.spec.ts
Normal file
23
src/app/pipes/join.pipe.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { JoinPipe } from './join.pipe';
|
||||||
|
|
||||||
|
describe('JoinPipe', () => {
|
||||||
|
let pipe: JoinPipe;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
pipe = new JoinPipe();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create an instance', () => {
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should transform array into string with \',\' separator', () => {
|
||||||
|
const array = ['one', 'two', 'three', 'four'];
|
||||||
|
expect(pipe.transform(array)).toBe('one, two, three, four');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should transform array into string with \'::\' separator', () => {
|
||||||
|
const array = ['one', 'two', 'three', 'four'];
|
||||||
|
expect(pipe.transform(array, '::')).toBe('one::two::three::four');
|
||||||
|
});
|
||||||
|
});
|
||||||
12
src/app/pipes/join.pipe.ts
Normal file
12
src/app/pipes/join.pipe.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'join'
|
||||||
|
})
|
||||||
|
export class JoinPipe implements PipeTransform {
|
||||||
|
|
||||||
|
transform(value: any[], separator = ', '): any {
|
||||||
|
return Array.isArray(value) ? value.join(separator) : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
22
src/app/pipes/pipes.module.ts
Normal file
22
src/app/pipes/pipes.module.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { JoinPipe } from './join.pipe';
|
||||||
|
import { SafeHTMLPipe } from './safehtml.pipe';
|
||||||
|
|
||||||
|
const exportedPipes = [
|
||||||
|
JoinPipe,
|
||||||
|
SafeHTMLPipe
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
...exportedPipes,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
...exportedPipes
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
...exportedPipes
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class PipesModule { }
|
||||||
17
src/app/pipes/safehtml.pipe.ts
Normal file
17
src/app/pipes/safehtml.pipe.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'safeHTML'
|
||||||
|
})
|
||||||
|
export class SafeHTMLPipe implements PipeTransform {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly _sanitizer: DomSanitizer
|
||||||
|
) { }
|
||||||
|
|
||||||
|
transform(value: string): SafeHtml {
|
||||||
|
return this._sanitizer.bypassSecurityTrustHtml(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,18 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
ActionReducer,
|
|
||||||
ActionReducerMap,
|
ActionReducerMap,
|
||||||
createFeatureSelector,
|
|
||||||
createSelector,
|
|
||||||
MetaReducer
|
MetaReducer
|
||||||
} from '@ngrx/store';
|
} from '@ngrx/store';
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const reducers: ActionReducerMap<State> = {
|
export const reducers: ActionReducerMap<State> = {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
37
src/app/store/comment/comment.actions.ts
Normal file
37
src/app/store/comment/comment.actions.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { createAction, props } from '@ngrx/store';
|
||||||
|
import { Update } from '@ngrx/entity';
|
||||||
|
|
||||||
|
import { Comment } from './comment.model';
|
||||||
|
|
||||||
|
export const loadComments = createAction(
|
||||||
|
'[Comment/API] Load Comments'
|
||||||
|
);
|
||||||
|
|
||||||
|
export const loadCommentsSuccess = createAction(
|
||||||
|
'[Comment/API] Load Comments Success',
|
||||||
|
props<{ comments: Comment[] }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const loadCommentsFailure = createAction(
|
||||||
|
'[Comment/API] Load Comments Failure'
|
||||||
|
);
|
||||||
|
|
||||||
|
export const addComment = createAction(
|
||||||
|
'[Comment/API] Add Comment',
|
||||||
|
props<{ comment: Comment }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const upsertComment = createAction(
|
||||||
|
'[Comment/API] Upsert Comment',
|
||||||
|
props<{ comment: Comment }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateComment = createAction(
|
||||||
|
'[Comment/API] Update Comment',
|
||||||
|
props<{ comment: Update<Comment> }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const deleteComment = createAction(
|
||||||
|
'[Comment/API] Delete Comment',
|
||||||
|
props<{ id: number }>()
|
||||||
|
);
|
||||||
30
src/app/store/comment/comment.effects.ts
Normal file
30
src/app/store/comment/comment.effects.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { map, switchMap, catchError } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { CommentService } from './comment.service';
|
||||||
|
import * as CommentActions from './comment.actions';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CommentEffects {
|
||||||
|
|
||||||
|
loadComments$ = createEffect(() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(CommentActions.loadComments),
|
||||||
|
switchMap(_ =>
|
||||||
|
this.api.getAll().pipe(
|
||||||
|
map(data => CommentActions.loadCommentsSuccess({ comments: data as any })), // debug type issue here
|
||||||
|
catchError(_ => of(CommentActions.loadCommentsFailure()))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private api: CommentService,
|
||||||
|
private actions$: Actions
|
||||||
|
) { }
|
||||||
|
|
||||||
|
}
|
||||||
23
src/app/store/comment/comment.facade.ts
Normal file
23
src/app/store/comment/comment.facade.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Store, select } from '@ngrx/store';
|
||||||
|
|
||||||
|
import * as commentActions from './comment.actions';
|
||||||
|
import * as query from './comment.selectors'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CommentFacade {
|
||||||
|
list$ = this.store.pipe(select(query.getAll))
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly store: Store
|
||||||
|
) { }
|
||||||
|
|
||||||
|
getAll(): void {
|
||||||
|
this.store.dispatch(commentActions.loadComments());
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(id: number): void {
|
||||||
|
this.store.dispatch(commentActions.deleteComment({ id }))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
19
src/app/store/comment/comment.model.ts
Normal file
19
src/app/store/comment/comment.model.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* from spec
|
||||||
|
{
|
||||||
|
id: “1”,
|
||||||
|
title: “This is an item”,
|
||||||
|
text: “This is a description of the item, it might describe a bug/task/comment, it
|
||||||
|
can also display <a href=”www.google.com”>Links</a>”,
|
||||||
|
tags: [“bug”, “issue”, “etc”]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface Comment {
|
||||||
|
id: number;
|
||||||
|
// created: Date;
|
||||||
|
// updated: Date;
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
22
src/app/store/comment/comment.module.ts
Normal file
22
src/app/store/comment/comment.module.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
import { StoreModule } from '@ngrx/store';
|
||||||
|
import { EffectsModule } from '@ngrx/effects';
|
||||||
|
|
||||||
|
import { commentsFeatureKey, reducer } from './comment.reducer';
|
||||||
|
import { CommentService } from './comment.service';
|
||||||
|
import { CommentEffects } from './comment.effects';
|
||||||
|
import { CommentFacade } from './comment.facade';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
HttpClientModule,
|
||||||
|
StoreModule.forFeature(commentsFeatureKey, reducer),
|
||||||
|
EffectsModule.forFeature([CommentEffects])
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
CommentService,
|
||||||
|
CommentFacade
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CommentStoreModule { }
|
||||||
13
src/app/store/comment/comment.reducer.spec.ts
Normal file
13
src/app/store/comment/comment.reducer.spec.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
44
src/app/store/comment/comment.reducer.ts
Normal file
44
src/app/store/comment/comment.reducer.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { Action, createReducer, on } from '@ngrx/store';
|
||||||
|
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
|
||||||
|
import { Comment } from './comment.model';
|
||||||
|
import * as CommentActions from './comment.actions';
|
||||||
|
|
||||||
|
export const commentsFeatureKey = 'comments';
|
||||||
|
|
||||||
|
export interface State extends EntityState<Comment> {
|
||||||
|
// additional entities state properties
|
||||||
|
}
|
||||||
|
|
||||||
|
export const adapter: EntityAdapter<Comment> = createEntityAdapter<Comment>();
|
||||||
|
|
||||||
|
export const initialState: State = adapter.getInitialState({
|
||||||
|
// additional entity state properties
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export const reducer = createReducer(
|
||||||
|
initialState,
|
||||||
|
on(CommentActions.addComment,
|
||||||
|
(state, action) => adapter.addOne(action.comment, state)
|
||||||
|
),
|
||||||
|
on(CommentActions.upsertComment,
|
||||||
|
(state, action) => adapter.upsertOne(action.comment, state)
|
||||||
|
),
|
||||||
|
on(CommentActions.updateComment,
|
||||||
|
(state, action) => adapter.updateOne(action.comment, state)
|
||||||
|
),
|
||||||
|
on(CommentActions.deleteComment,
|
||||||
|
(state, action) => adapter.removeOne(action.id, state)
|
||||||
|
),
|
||||||
|
on(CommentActions.loadCommentsSuccess,
|
||||||
|
(state, action) => adapter.setAll(action.comments, state)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
export const {
|
||||||
|
selectIds,
|
||||||
|
selectEntities,
|
||||||
|
selectAll,
|
||||||
|
selectTotal,
|
||||||
|
} = adapter.getSelectors();
|
||||||
8
src/app/store/comment/comment.selectors.ts
Normal file
8
src/app/store/comment/comment.selectors.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { createFeatureSelector, createSelector } from '@ngrx/store';
|
||||||
|
|
||||||
|
import { Comment } from './comment.model';
|
||||||
|
import * as fromComment from './comment.reducer';
|
||||||
|
|
||||||
|
export const getState = createFeatureSelector<fromComment.State>(fromComment.commentsFeatureKey);
|
||||||
|
export const getAll = createSelector(getState, fromComment.selectAll);
|
||||||
|
export const getEntities = createSelector(getState, fromComment.selectEntities);
|
||||||
16
src/app/store/comment/comment.service.ts
Normal file
16
src/app/store/comment/comment.service.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
|
import { map } from 'rxjs/operators'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CommentService {
|
||||||
|
|
||||||
|
getAll() {
|
||||||
|
return this.http.get('./assets/data.json').pipe(
|
||||||
|
map(response => response as Comment[])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) { }
|
||||||
|
}
|
||||||
2
src/app/store/comment/index.ts
Normal file
2
src/app/store/comment/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './comment.facade';
|
||||||
|
export * from './comment.model';
|
||||||
1
src/app/store/index.ts
Normal file
1
src/app/store/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './comment/comment.module'
|
||||||
32
src/assets/data.json
Normal file
32
src/assets/data.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Comment One",
|
||||||
|
"text": "This is a description of the item, it might describe a bug/task/comment, it can also display <a href=\"http://www.google.com\">Links</a>",
|
||||||
|
"tags": [
|
||||||
|
"bug",
|
||||||
|
"issue",
|
||||||
|
"etc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "Comment Two",
|
||||||
|
"text": "This is a description of the item, it might describe a bug/task/comment, it can also display <a href=\"http://www.google.com\">Links</a>",
|
||||||
|
"tags": [
|
||||||
|
"bug",
|
||||||
|
"issue",
|
||||||
|
"etc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"title": "Comment Three",
|
||||||
|
"text": "This is a description of the item, it might describe a bug/task/comment, it can also display <a href=\"http://www.google.com\">Links</a>",
|
||||||
|
"tags": [
|
||||||
|
"bug",
|
||||||
|
"issue",
|
||||||
|
"etc"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user