在上一篇博客中,我们开发了评论系统的前端部分,介绍了angular中模板的概念。在这篇博客中,我们将继续开发评论系统的前端部分,并介绍组件间通信的相关内容。
打开comments.component.ts文件,输入以下内容:
//comments.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { CommentService } from 'src/app/service/comment.service';
@Component({
selector: 'app-comments',
templateUrl: './comments.component.html',
styleUrls: ['./comments.component.scss']
})
export class CommentsComponent implements OnInit {
showComment:boolean[]
@Input() commentTree:any;
@Input() storyId:number;
constructor() {
this.showComment = [false];
this.storyId = 0;
}
ngOnInit(): void {
}
showCommentForm(storyId:number){
this.showComment[storyId] = !this.showComment[storyId];
}
}
这个组件中有两个属性:commentTree和storyId,前者用于存放评论树内容,而后者表示当前评论树属于哪个故事。
由于我们这个控件要作为storyList的子控件使用,即需要由storyList指定comments组件要放在哪个故事下面,因此我们使用@Input()修饰器来修饰commentTree和storyId参数,表明storyList组件可以指定comments组件中这两个属性的值:
底下的showCommentForm函数用来反转showComment数组中对应元素的值,控制评论树中评论表单的隐现。
这样,我们这个comments的前端部分就开发完了,下面让我们实现另一个组件commentForm,
这个组件用于让用户发表评论。
打开commentform/commentform.component.html,输入以下代码:
<!--commentform.component.html-->
<form nz-form [formGroup]="commentForm" (ngSubmit)="publishComment()">
<nz-form-item>
<nz-form-control>
<input formControlName="content" nz-input placeholder="说点什么" />
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-control>
<button nz-button nzType="primary">发表</button>
</nz-form-control>
</nz-form-item>
</form>
这个组件没什么说的,就是一个简单的表单。
下面打开commentform.component.ts文件,输入以下代码:
//commentform.component.ts
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Router } from '@angular/router';
import { NzMessageService } from 'ng-zorro-antd/message';
import { CookieService } from 'ngx-cookie-service';
import { CommentService } from 'src/app/service/comment.service';
import { commentData } from '../storylist/commentData';
import { refreshCommentInfo } from './refreshcommentinfo';
@Component({
selector: 'app-commentform',
templateUrl: './commentform.component.html',
styleUrls: ['./commentform.component.scss']
})
export class CommentformComponent implements OnInit {
newComment:commentData;
author:string;
@Input() commentBody:string;
@Input() storyId:number;
@Output() refreshCommentEvent: EventEmitter<refreshCommentInfo>;
commentForm = new FormGroup({
content:new FormControl('')
});
constructor(private cookie:CookieService,private commentService:CommentService,private message:NzMessageService,private router:Router) {
this.newComment = {
author:'',
content:'',
commentBody:''
}
this.author = this.cookie.get('currentuser');
this.commentBody = ''
this.storyId = 0
this.refreshCommentEvent = new EventEmitter();
}
ngOnInit(): void {
console.log('comment form ' + this.commentBody)
console.log('storyid:' + this.storyId)
}
publishComment(){
this.newComment = this.commentForm.value;
this.newComment.author = this.author;
this.newComment.commentBody = this.commentBody;
this.commentService.writeComment(this.newComment).subscribe((data:any)=>{
if (data){
if (data['result'] == 'Success'){
console.log('Success comment');
console.log('storyid:' + this.storyId)
console.log('external storyId:' + this.newComment.commentBody)
this.message.create('success','评论成功')
this.commentForm.setValue({'content':''});
setTimeout(() => {
this.refreshCommentEvent.emit({'storyId':this.storyId,'externalStoryId':this.newComment.commentBody});
}, 800);
}
}
})
}
}
我们来看一下这个组件中用到了哪些变量:
- newComment:类型为之前定义的commentData,用于接收用户输入的新评论;
- author:评论作者;
- commentBody:表示这条评论的主体是谁,可以是故事也可以是别的评论;
- storyId:评论所属的故事主体;
- refreshCommentEvent:当评论完成时触发的事件,这里暂时不讲;
- commentForm:FormGroup,用于处理表单。
因为我们的这个表单控件也是作为storyList和comments组件的子组件使用的,我们同样使用@Input()来修饰commentBody和storyId变量;然而,对于refreshCommentEvent我们使用了@Output()修饰器来装饰它,表示它是一个向父组件传递信息的变量:
这里用@Output()的目的是,当用户在评论树中发表了评论后,通知storyList重新抓取当前故事中的评论,实现立即显示新评论的功能。
这个commentForm只有一个函数:publishComment,用于让用户发布新评论,并在成功发布评论后向父组件发送消息,让父组件重新获取当前故事的评论树。
接下来,让我们修改storylist.component.html,调用我们刚刚写好的commentform和comments组件:
<!--storylist.component.html-->
<div *ngFor="let story of storyList" >
<nz-card nzTitle="{{story.title}}" >
<!--...-->
<span *nzSpaceItem ><i nz-icon nzType="comment" nzTheme="outline"></i><a (click)="showAllComment(story.id,story.externalId)"> 评论</a> </span>
<!--...-->
</nz-space>
</nz-card>
<div *ngIf="showComment[story.id]">
<nz-card nzTitle="评论" >
<app-commentform [commentBody]="story.externalId" [storyId]="story.id" (refreshCommentEvent)="refreshComment($event)"></app-commentform>
<app-comments [commentTree] = "commentTree" [storyId]="story.id"></app-comments>
</nz-card>
</div>
<br>
</div>
我们为之前的评论标签添加一个a标签,让它调用我们稍后要实现的showAllComment函数来控制评论栏的隐现。这里我们用一个存有storyId的布尔型数组showComment来控制评论栏的隐现,如果对应storyId的值是true,则显示该故事的评论栏以及底下的评论树。
在commentform控件中,我们用**[commentBody]和[storyId]的语法向commentForm中的同名属性传值,表明评论框的评论是给这个故事的。类似的,我们在comments控件中也会传入[commentTree]和[storyId]**的属性,以便显示故事的评论树。
我们打开storylist.component.ts文件,向其中添加以下代码:
//storylist.component.ts
import { Component, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { StoryService } from 'src/app/service/story.service';
import { storyDetail } from './storeDetail';
import { FormControl,FormGroup } from '@angular/forms';
import { CommentService } from 'src/app/service/comment.service';
import { refreshCommentInfo } from '../commentform/refreshcommentinfo';
@Component({
selector: 'app-storylist',
templateUrl: './storylist.component.html',
styleUrls: ['./storylist.component.scss']
})
export class StorylistComponent implements OnInit {
storyList:storyDetail[];
showComment:boolean[];
public commentTree:any;
commentForm = new FormGroup({
content:new FormControl('')
});
constructor(private storyService:StoryService,private commentService:CommentService) {
//...
this.showComment = [false];
}
ngOnInit(): void {
}
//...
showAllComment(storyId:number,externalStoryId:string){
this.showComment[storyId] = !this.showComment[storyId];
this.getAllComment(storyId,externalStoryId);
}
getAllComment(storyId:number,externalStoryId:string){
const commentowner = {'storyId':externalStoryId}
this.commentService.getComment(commentowner).subscribe((data:any) =>{
console.log(data['result']);
//this.data = data;
this.commentTree[storyId] = data['result'];
})
}
refreshComment(info:refreshCommentInfo){
this.showComment[info['storyId']] = true;
this.getAllComment(info['storyId'],info['externalStoryId']);
}
}
这里我们定义了两个新变量:showComment和commentTree,前者用于控制评论栏的隐现,而后者用于显示故事的评论树。在构造函数中,我们将showComment设为false,即在默认情况下不显示评论。
然后,我们实现一个showAllComment函数,这个函数的功能之一是反转showComment数组的指定元素的值,从而实现点击评论按钮来隐藏显示评论栏;功能之二是稍后要实现的获取该故事的评论树getAllComment函数;而refreshComment函数用于用户发表评论后重新获取当前故事的评论。
下面再让我们实现评论的service部分,打开cmd窗口,输入以下命令:
ng g s service/comment
然后打开comment.service.ts文件,输入以下代码:
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { commentOwner } from '../story/comments/commentowner';
import { commentData } from '../story/storylist/commentData';
@Injectable({
providedIn: 'root'
})
export class CommentService {
constructor(private http:HttpClient) { }
writeComment(comment:commentData):Observable<commentData>{
return this.http.post<commentData>('http://localhost:8000/writecomment',comment)
}
getComment(storyId:commentOwner):Observable<commentOwner>{
return this.http.post<commentOwner>('http://localhost:8000/getcomment',storyId)
}
}
这里我们实现两个service:writeComment和getComment。顾名思义,前者用于发表评论,后者用于获取评论。
这样,我们就完成了评论系统所有的前端开发部分。我们来总结一下在这两篇博客中用到的知识点:
-
<ng-template>
:angular模板,可以通过#语法来为模板指定模板名,以便之后的复用。 -
ngTemplateOutlet
:用在<ng-template>
标签内部,表示当前的模板要复用哪个模板,值为上一条中通过#定义的模板名 -
ngTemplateOutletContext
:决定复用的模板中的数据,其值为一个key-value对象。 -
ngFor let-xxx [ngForOf]
:模板中使用的*ngFor语法形式。 - @Input()修饰器:表明这个属性可以被父组件访问、赋值,在html代码中通过[属性名]为该属性赋值。
- @Output()修饰器:表明这个属性可以传递数据给父组件,与@Input()作用相反。
以上就是我们开发评论系统的前端部分目前用到的所有知识点,可以说模板的相关概念理解起来还是有点困难的,思想可能会有一点绕。
在下一篇博客中,我们将继续实现评论系统的后端部分,希望大家继续关注~