Conduit应用中,我们能够对文章留下评论。要建立这个功能,我们首先需要创建一个评论模型。

创建评论模型

src/app/shared/models/comment.model.ts

  1. import { Profile } from './profile.model';
  2. export class Comment {
  3. id: number;
  4. body: string;
  5. createdAt: string;
  6. author: Profile;
  7. }

src/app/shared/models/index.ts

  1. export * from './article.model';
  2. +export * from './comment.model';
  3. export * from './errors.model';
  4. export * from './profile.model';
  5. export * from './user.model';

我们需要一种创建、获取和删除评论的方法,所以让我们创建一个服务来处理这个问题。

创建评论服务

src/app/shared/services/comments.service.ts

  1. import { Injectable } from '@angular/core';
  2. import { Observable } from 'rxjs/Rx';
  3. import 'rxjs/add/operator/map';
  4. import 'rxjs/add/operator/catch';
  5. import { ApiService } from './api.service';
  6. import { Comment } from '../models';
  7. @Injectable()
  8. export class CommentsService {
  9. constructor (
  10. private apiService: ApiService
  11. ) {}
  12. add(slug, payload): Observable<Comment> {
  13. return this.apiService.post(`/articles/${slug}/comments`, { comment: { body: payload } })
  14. .map(data => data.comment);
  15. }
  16. getAll(slug): Observable<Comment[]> {
  17. return this.apiService.get(`/articles/${slug}/comments`)
  18. .map(data => data.comments);
  19. }
  20. destroy(commentId, articleSlug) {
  21. return this.apiService.delete(`/articles/${articleSlug}/comments/${commentId}`);
  22. }
  23. }

将我们的评论服务导出到我们的应用程序

src/app/shared/services/index.ts

  1. export * from './api.service';
  2. export * from './articles.service';
  3. export * from './auth-guard.service';
  4. +export * from './comments.service';
  5. export * from './jwt.service';
  6. export * from './profiles.service';
  7. export * from './user.service';

而我们需要将其包含在app模块中。

src/app/app.module.ts

  1. [...]
  2. import { RouterModule } from '@angular/router';
  3. import { AppComponent } from './app.component';
  4. import { ArticleModule } from './article/article.module';
  5. import { AuthModule } from './auth/auth.module';
  6. import { EditorModule } from './editor/editor.module';
  7. import { HomeModule } from './home/home.module';
  8. [...]
  9. ApiService,
  10. ArticlesService,
  11. AuthGuard,
  12. + CommentsService,
  13. FooterComponent,
  14. HeaderComponent,
  15. JwtService,
  16. [...]
  17. ApiService,
  18. ArticlesService,
  19. AuthGuard,
  20. + CommentsService,
  21. JwtService,
  22. ProfilesService,
  23. UserService
  24. [...]

让我们来创建一个用于显示单个评论的组件

创建ArticleComment组件

src/app/article/article-comment.component.html

  1. <div class="card">
  2. <div class="card-block">
  3. <p class="card-text">
  4. {{ comment.body }}
  5. </p>
  6. </div>
  7. <div class="card-footer">
  8. <a class="comment-author" [routerLink]="['/profile', comment.author.username]">
  9. <img [src]="comment.author.image" class="comment-author-img" />
  10. </a>
  11. &nbsp;
  12. <a class="comment-author" [routerLink]="['/profile', comment.author.username]">
  13. {{ comment.author.username }}
  14. </a>
  15. <span class="date-posted">
  16. {{ comment.createdAt | date: 'longDate' }}
  17. </span>
  18. <span class="mod-options" [hidden]="!canModify">
  19. <i class="ion-trash-a" (click)="deleteClicked()"></i>
  20. </span>
  21. </div>
  22. </div>

src/app/article/article-comment.component.ts

  1. import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core';
  2. import { Comment, User, UserService } from '../shared';
  3. @Component({
  4. selector: 'article-comment',
  5. templateUrl: './article-comment.component.html'
  6. })
  7. export class ArticleCommentComponent implements OnInit {
  8. constructor(
  9. private userService: UserService
  10. ) {}
  11. @Input() comment: Comment;
  12. @Output() deleteComment = new EventEmitter<boolean>();
  13. canModify: boolean;
  14. ngOnInit() {
  15. // Load the current user's data
  16. this.userService.currentUser.subscribe(
  17. (userData: User) => {
  18. this.canModify = (userData.username === this.comment.author.username);
  19. }
  20. );
  21. }
  22. deleteClicked() {
  23. this.deleteComment.emit(true);
  24. }
  25. }

我们需要在ArticleModule中声明这个组件才能使用它。

src/app/article/article.module.ts

  1. import { ModuleWithProviders, NgModule } from '@angular/core';
  2. import { RouterModule } from '@angular/router';
  3. import { ArticleComponent } from './article.component';
  4. +import { ArticleCommentComponent } from './article-comment.component';
  5. import { ArticleResolver } from './article-resolver.service';
  6. import { MarkdownPipe } from './markdown.pipe';
  7. import { SharedModule } from '../shared';
  8. const articleRouting: ModuleWithProviders = RouterModule.forChild([
  9. {
  10. path: 'article/:slug',
  11. component: ArticleComponent,
  12. resolve: {
  13. article: ArticleResolver
  14. }
  15. }
  16. ]);
  17. @NgModule({
  18. imports: [
  19. articleRouting,
  20. SharedModule
  21. ],
  22. declarations: [
  23. ArticleComponent,
  24. + ArticleCommentComponent,
  25. MarkdownPipe
  26. ],
  27. providers: [
  28. ArticleResolver
  29. ]
  30. })
  31. export class ArticleModule {}

接下来,我们需要更新文章组件,使用我们新创建的ArticleComment服务来处理检索、创建和删除评论。

用检索、创建和删除注释来更新ArticleComponent

src/app/article/article.component.ts

  1. import { Component, OnInit } from '@angular/core';
  2. import { FormControl } from '@angular/forms';
  3. import { ActivatedRoute, Router } from '@angular/router';
  4. import {
  5. Article,
  6. ArticlesService,
  7. + Comment,
  8. + CommentsService,
  9. User,
  10. UserService
  11. } from '../shared';
  12. @Component({
  13. selector: 'article-page',
  14. templateUrl: './article.component.html'
  15. })
  16. export class ArticleComponent implements OnInit {
  17. article: Article;
  18. currentUser: User;
  19. canModify: boolean;
  20. + comments: Comment[];
  21. + commentControl = new FormControl();
  22. + commentFormErrors = {};
  23. isSubmitting = false;
  24. isDeleting = false;
  25. constructor(
  26. private route: ActivatedRoute,
  27. private articlesService: ArticlesService,
  28. + private commentsService: CommentsService,
  29. private router: Router,
  30. private userService: UserService,
  31. ) { }
  32. ngOnInit() {
  33. // Retreive the prefetched article
  34. this.route.data.subscribe(
  35. (data: { article: Article }) => {
  36. this.article = data.article;
  37. +
  38. + // Load the comments on this article
  39. + this.populateComments();
  40. }
  41. );
  42. // Load the current user's data
  43. this.userService.currentUser.subscribe(
  44. (userData: User) => {
  45. this.currentUser = userData;
  46. this.canModify = (this.currentUser.username === this.article.author.username);
  47. }
  48. );
  49. }
  50. onToggleFavorite(favorited: boolean) {
  51. this.article.favorited = favorited;
  52. if (favorited) {
  53. this.article.favoritesCount++;
  54. } else {
  55. this.article.favoritesCount--;
  56. }
  57. }
  58. onToggleFollowing(following: boolean) {
  59. this.article.author.following = following;
  60. }
  61. deleteArticle() {
  62. this.isDeleting = true;
  63. this.articlesService.destroy(this.article.slug)
  64. .subscribe(
  65. success => {
  66. this.router.navigateByUrl('/');
  67. }
  68. );
  69. }
  70. + populateComments() {
  71. + this.commentsService.getAll(this.article.slug)
  72. + .subscribe(comments => this.comments = comments);
  73. + }
  74. +
  75. + addComment() {
  76. + this.isSubmitting = true;
  77. + this.commentFormErrors = {};
  78. +
  79. + let commentBody = this.commentControl.value;
  80. + this.commentsService
  81. + .add(this.article.slug, commentBody)
  82. + .subscribe(
  83. + comment => {
  84. + this.comments.unshift(comment);
  85. + this.commentControl.reset('');
  86. + this.isSubmitting = false;
  87. + },
  88. + errors => {
  89. + this.isSubmitting = false;
  90. + this.commentFormErrors = errors;
  91. + }
  92. + );
  93. + }
  94. + onDeleteComment(comment) {
  95. + this.commentsService.destroy(comment.id, this.article.slug)
  96. + .subscribe(
  97. + success => {
  98. + this.comments = this.comments.filter((item) => item !== comment);
  99. + }
  100. + );
  101. + }
  102. }

然后我们就可以更新文章模板来挂入组件的新方法。

更新ArticleComponent模板

src/app/article/article.component.html

  1. [...]
  2. <div class="row">
  3. <div class="col-xs-12 col-md-8 offset-md-2">
  4. + <div *showAuthed="true">
  5. + <list-errors [errors]="commentFormErrors"></list-errors>
  6. + <form class="card comment-form" (ngSubmit)="addComment()">
  7. + <fieldset [disabled]="isSubmitting">
  8. + <div class="card-block">
  9. + <textarea class="form-control"
  10. + placeholder="Write a comment..."
  11. + rows="3"
  12. + [formControl]="commentControl"
  13. + ></textarea>
  14. + </div>
  15. + <div class="card-footer">
  16. + <img [src]="currentUser.image" class="comment-author-img" />
  17. + <button class="btn btn-sm btn-primary" type="submit">
  18. + Post Comment
  19. + </button>
  20. + </div>
  21. + </fieldset>
  22. + </form>
  23. + </div>
  24. <div *showAuthed="false">
  25. <a [routerLink]="['/login']">Sign in</a> or <a [routerLink]="['/register']">sign up</a> to add comments on this article.
  26. </div>
  27. + <article-comment
  28. + *ngFor="let comment of comments"
  29. + [comment]="comment"
  30. + (deleteComment)="onDeleteComment(comment)">
  31. + </article-comment>
  32. </div>
  33. </div>
  34. [...]

我们现在有了对文章的评论功能!

你可以将你的代码与Github上的有效代码进行比较,或者在本地检查分支:

  1. git checkout m-1