Skip to content

Commit

Permalink
refactor forms part 3
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanoslig committed Jul 14, 2024
1 parent 1c95da0 commit a7d2f3c
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 127 deletions.
30 changes: 20 additions & 10 deletions libs/articles/data-access/src/lib/article.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { tapResponse } from '@ngrx/operators';
import { ActionsService } from './services/actions.service';
import { Router } from '@angular/router';
import { Store } from '@ngrx/store';
import { concatLatestFrom } from '@ngrx/operators';
import { formsActions, ngrxFormsQuery } from '@realworld/core/forms';
import { formsActions } from '@realworld/core/forms';
import { NewArticle } from '@realworld/core/api-types';

export const ArticleStore = signalStore(
{ providedIn: 'root' },
Expand Down Expand Up @@ -96,22 +96,32 @@ export const ArticleStore = signalStore(
),
addComment: rxMethod<string>(
pipe(
concatLatestFrom(() => reduxStore.select(ngrxFormsQuery.selectData)),
switchMap(([slug, data]) =>
articlesService.addComment(slug, data.comment).pipe(
switchMap((addedComment) =>
articlesService.addComment(store.data.slug(), addedComment).pipe(
tapResponse({
next: () => patchState(store, { comments: [data.comment, ...store.comments()] }),
next: ({ comment }) => patchState(store, { comments: [comment, ...store.comments()] }),
error: ({ error }) => reduxStore.dispatch(formsActions.setErrors({ errors: error.errors })),
}),
),
),
),
),
publishArticle: rxMethod<void>(
publishArticle: rxMethod<NewArticle>(
pipe(
concatLatestFrom(() => reduxStore.select(ngrxFormsQuery.selectData)),
switchMap(([_, data]) =>
articlesService.publishArticle(data).pipe(
switchMap((article) =>
articlesService.publishArticle(article).pipe(
tapResponse({
next: ({ article }) => router.navigate(['article', article.slug]),
error: ({ error }) => reduxStore.dispatch(formsActions.setErrors({ errors: error.errors })),
}),
),
),
),
),
editArticle: rxMethod<any>(
pipe(
switchMap((article) =>
articlesService.editArticle(article).pipe(
tapResponse({
next: ({ article }) => router.navigate(['article', article.slug]),
error: ({ error }) => reduxStore.dispatch(formsActions.setErrors({ errors: error.errors })),
Expand Down
27 changes: 17 additions & 10 deletions libs/articles/data-access/src/lib/services/articles.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { ApiService } from '@realworld/core/http-client';
import { Article, ArticleResponse, MultipleCommentsResponse, SingleCommentResponse } from '@realworld/core/api-types';
import {
Article,
ArticleResponse,
MultipleCommentsResponse,
NewArticle,
SingleCommentResponse,
} from '@realworld/core/api-types';
import { HttpParams } from '@angular/common/http';
import { ArticlesListConfig } from '../models/articles-list.model';

Expand All @@ -25,9 +31,9 @@ export class ArticlesService {
return this.apiService.delete<void>(`/articles/${slug}/comments/${commentId}`);
}

addComment(slug: string, payload = ''): Observable<SingleCommentResponse> {
addComment(slug: string, comment: string): Observable<SingleCommentResponse> {
return this.apiService.post<SingleCommentResponse, { comment: { body: string } }>(`/articles/${slug}/comments`, {
comment: { body: payload },
comment: { body: comment },
});
}

Expand All @@ -38,13 +44,14 @@ export class ArticlesService {
);
}

publishArticle(article: Article): Observable<ArticleResponse> {
if (article.slug) {
return this.apiService.put<ArticleResponse, ArticleResponse>('/articles/' + article.slug, {
article: article,
});
}
return this.apiService.post<ArticleResponse, ArticleResponse>('/articles/', { article: article });
publishArticle(article: NewArticle): Observable<ArticleResponse> {
return this.apiService.post<ArticleResponse, NewArticle>('/articles/', article);
}

editArticle(article: Article): Observable<ArticleResponse> {
return this.apiService.put<ArticleResponse, ArticleResponse>('/articles/' + article.slug, {
article: article,
});
}

// TODO: remove any
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.form-group.required {
position: relative;
}
.form-group.required::after {
content: '*';
position: absolute;
top: 5px;
left: 8px;
color: #f00;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,53 @@
<div class="row">
<div class="col-md-10 offset-md-1 col-xs-12">
<cdt-list-errors></cdt-list-errors>
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<fieldset class="form-group">
<input
formControlName="title"
type="text"
placeholder="Article Title"
class="form-control form-control-lg"
/>
<cdt-input-errors [control]="form.controls.title" />
</fieldset>
<fieldset class="form-group">
<input
formControlName="description"
type="text"
placeholder="What's this article about?"
class="form-control form-control-lg"
/>
<cdt-input-errors [control]="form.controls.description" />
</fieldset>
<fieldset class="form-group">
<textarea
formControlName="body"
rows="5"
type="text"
placeholder="Write your article (in markdown)"
class="form-control form-control-lg"
></textarea>
<cdt-input-errors [control]="form.controls.body" />
</fieldset>
<fieldset class="form-group">
<input
formControlName="tagList"
type="text"
placeholder="Enter Tags"
class="form-control form-control-lg"
/>
</fieldset>

<cdt-dynamic-form (updateForm)="updateForm($event)" [data$]="data$" [structure$]="structure$">
</cdt-dynamic-form>

<button class="btn btn-lg pull-xs-right btn-primary" type="button" (click)="submit()">Publish Article</button>
<button
data-e2e-id="edit-button-container"
class="btn btn-lg btn-primary"
[disabled]="form.invalid || form.pending"
type="submit"
>
Publish Article
</button>
</form>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,72 +1,51 @@
import { DynamicFormComponent, Field, formsActions, ListErrorsComponent, ngrxFormsQuery } from '@realworld/core/forms';
import { ChangeDetectionStrategy, Component, DestroyRef, effect, inject, OnInit, untracked } from '@angular/core';
import { InputErrorsComponent, ListErrorsComponent } from '@realworld/core/forms';
import { ChangeDetectionStrategy, Component, effect, inject } from '@angular/core';
import { OnDestroy } from '@angular/core';
import { Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { ArticleStore } from '@realworld/articles/data-access';

const structure: Field[] = [
{
type: 'INPUT',
name: 'title',
placeholder: 'Article Title',
validator: [Validators.required],
},
{
type: 'INPUT',
name: 'description',
placeholder: "What's this article about?",
validator: [Validators.required],
},
{
type: 'TEXTAREA',
name: 'body',
placeholder: 'Write your article (in markdown)',
validator: [Validators.required],
},
{
type: 'INPUT',
name: 'tagList',
placeholder: 'Enter Tags',
validator: [],
},
];

@Component({
selector: 'cdt-article-edit',
standalone: true,
templateUrl: './article-edit.component.html',
styleUrls: ['./article-edit.component.css'],
imports: [DynamicFormComponent, ListErrorsComponent],
imports: [ListErrorsComponent, ReactiveFormsModule, InputErrorsComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ArticleEditComponent implements OnInit, OnDestroy {
private readonly store = inject(Store);
export class ArticleEditComponent implements OnDestroy {
private readonly articleStore = inject(ArticleStore);
private readonly fb = inject(FormBuilder);

structure$ = this.store.select(ngrxFormsQuery.selectStructure);
data$ = this.store.select(ngrxFormsQuery.selectData);
form = this.fb.nonNullable.group({
title: ['', [Validators.required]],
description: ['', [Validators.required]],
body: ['', [Validators.required]],
tagList: [''],
});

readonly setArticleDataToForm = effect(() => {
const articleLoaded = this.articleStore.getArticleLoaded();
if (articleLoaded) {
untracked(() => this.store.dispatch(formsActions.setData({ data: this.articleStore.data() })));
this.form.patchValue({
title: this.articleStore.data.title(),
description: this.articleStore.data.description(),
body: this.articleStore.data.body(),
tagList: this.articleStore.data.tagList().join(', '),
});
}
});

ngOnInit() {
this.store.dispatch(formsActions.setStructure({ structure }));
}

updateForm(changes: any) {
this.store.dispatch(formsActions.updateData({ data: changes }));
}

submit() {
this.articleStore.publishArticle();
onSubmit() {
if (this.articleStore.data.slug()) {
this.articleStore.editArticle(this.form.getRawValue());
} else {
this.articleStore.publishArticle({
article: { ...this.form.getRawValue(), tagList: this.form.controls.tagList.value.split(',') },
});
}
}

ngOnDestroy() {
this.store.dispatch(formsActions.initializeForm());
this.form.reset();
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
<cdt-list-errors> </cdt-list-errors>

<div class="card comment-form">
<cdt-dynamic-form
class="card-block"
(updateForm)="updateForm.emit($event)"
[data$]="data$"
[structure$]="structure$"
[touchedForm$]="touchedForm$"
>
</cdt-dynamic-form>
<div class="card-footer">
<img [src]="currentUser().image" class="comment-author-img" />
<button (click)="submitComment.emit(article().slug)" class="btn btn-sm btn-primary" type="submit">
Post Comment
</button>
</div>
<form [formGroup]="form" (ngSubmit)="submitComment.emit(form.controls.comment.value)">
<fieldset class="form-group required">
<textarea
formControlName="comment"
rows="5"
type="text"
placeholder="Write a comment..."
class="form-control form-control-lg"
>
</textarea>
<cdt-input-errors [control]="form.controls.comment" />
</fieldset>
<div class="card-footer">
<img [src]="currentUser().image" class="comment-author-img" />
<button (click)="submitComment.emit(form.controls.comment.value)" class="btn btn-sm btn-primary" type="submit">
Post Comment
</button>
</div>
</form>
</div>
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { Article, User } from '@realworld/core/api-types';
import { DynamicFormComponent, Field, ListErrorsComponent } from '@realworld/core/forms';
import { ChangeDetectionStrategy, Component, Input, input, output } from '@angular/core';
import { Observable } from 'rxjs';
import { InputErrorsComponent, ListErrorsComponent } from '@realworld/core/forms';
import { ChangeDetectionStrategy, Component, inject, input, output } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';

@Component({
selector: 'cdt-add-comment',
standalone: true,
templateUrl: './add-comment.component.html',
imports: [ListErrorsComponent, DynamicFormComponent],
imports: [ListErrorsComponent, ReactiveFormsModule, InputErrorsComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AddCommentComponent {
private readonly fb = inject(FormBuilder);

article = input.required<Article>();
currentUser = input.required<User>();
@Input() data$!: Observable<any>;
@Input() structure$!: Observable<Field[]>;
@Input() touchedForm$!: Observable<boolean>;
submitComment = output<string>();
updateForm = output<string>();

form = this.fb.nonNullable.group({
comment: [''],
});
}
11 changes: 1 addition & 10 deletions libs/articles/feature-article/src/lib/article.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,7 @@ <h1 data-e2e-id="article-title">{{ article.title }}</h1>
<div class="row">
<div class="col-xs-12 col-md-8 offset-md-2">
@if ($isAuthenticated()) {
<cdt-add-comment
[article]="article"
[data$]="data$"
[structure$]="structure$"
[currentUser]="$currentUser()"
[touchedForm$]="touchedForm$"
(submitComment)="submit($event)"
(updateForm)="updateForm($event)"
>
</cdt-add-comment>
<cdt-add-comment [article]="article" [currentUser]="$currentUser()" (submitComment)="submit($event)" />
} @else {
<a [routerLink]="['/login']">Sign in</a> or <a [routerLink]="['/register']">sign up</a> to add comments on
this article.
Expand Down
Loading

0 comments on commit a7d2f3c

Please sign in to comment.