How to add a class to just right added comment, not all of them
Tags: angular, typescript, css, programming
Published: 2022-07-04 09:00:45

Hello, I need to add bootstrap effect fadeInDown to a newly added comment in my comments section. It works but unfortunately it affects all of the comments when I reload the page and the effect is also added when I edit a comment. Is it possible to add this specific class only to newly created comment? not affecting the rest of comments nor the other application functionalities? I will be glad for any advice.
These are the components I'm working on: Comment section HTML

```
<h6>Comments: ({{commentsCount}})</h6>
<app-new-comment class="d-block mt-3" [requestId]="requestId" (update)="updateComments()"> 
</app-new-comment>
<app-comment *ngFor="let comment of comments; let i = first"
         [ngClass]="{'fadeInDown': i}"
         class="d-block mt-3 animated"
         [comment]="comment"
         (update)="updateComments()"></app-comment>
```


Comment section TS

```
export class CommentSectionComponent implements OnInit, OnDestroy {

@Input() requestId: string
comments: CommentDetailsDto[]
commentsCount = 0

private subscription: Subscription

constructor(
    private commentsService: CommentsService,
    private alert: AlertService
) {
}

ngOnInit(): void {
    this.updateComments()
}

ngOnDestroy(): void {
    this.unsubscribe()
}

updateComments(): void {
    this.unsubscribe()
    this.subscription = this.commentsService.getComments(this.requestId)
        .subscribe({
            next: (comments: CommentDetailsDto[]) => {
                this.comments = comments.reverse()
                this.commentsCount = this.getCommentsCount(comments)
            },
            error: (error: HttpErrorResponse) => {
                this.alert.handleHttpError(error)
            }
        })
}

private getCommentsCount(comments: CommentDetailsDto[]): number {
    return comments.reduce(
        (cnt, comment) => cnt + 1 + (comment.replies ? this.getCommentsCount(comment.replies) : 0),
        0
    )
}

private unsubscribe(): void {
    if (this.subscription instanceof Subscription) {
        this.subscription.unsubscribe()
    }
}

}
```
Comment section SCSS

```
@keyframes fadeInDown {
from {
    opacity: 0;
    transform: translate3d(0, -100%, 0);
}
to {
    opacity: 1;
    transform: translate3d(0, 0, 0);
}
}

.fadeInDown {
    animation-name: fadeInDown;
}
```
New comment component HTML

```
<div class="d-flex">
<app-user-image class="mr-3"></app-user-image>
<textarea #textArea class="d-block" placeholder="Add comment" [(ngModel)]="newComment"> 
 </textarea>
 </div>
 <div class="text-right mt-3">
    <button class="btn btn-primary font-weight-bold px-5" type="button" 
 [disabled]="!newComment" (click)="submit()">
       Post
     </button>
 </div>
```
New comment TS

```
export class NewCommentComponent implements OnInit {

@ViewChild('textArea') textArea: ElementRef<HTMLTextAreaElement>
@Input() requestId?: string
@Input() comment?: CommentDetailsDto
@Output() update = new EventEmitter()
newComment: string

private readonly commentResponseObserver = {
    error: (error: HttpErrorResponse) => {
        this.alert.handleHttpError(error)
    },
    complete: () => {
        delete this.newComment
        this.update.emit()
        this.alert.showSuccess('Comment submitted successfully')
    }
}

constructor(
    private commentsService: CommentsService,
    private alert: AlertService,
    private route: ActivatedRoute
) {
}

ngOnInit(): void {
    if (this.comment) {
        this.textArea.nativeElement.focus()
    }
}

submit(): void {
    if (this.requestId) {
        this.addComment()
    } else if (this.comment) {
        this.addReply()
    }
}

addComment(): void {
    if (this.newComment) {
        this.commentsService.addComment(this.requestId, this.newComment)
            .subscribe(this.commentResponseObserver)
    }
}

addReply(): void {
    if (this.newComment) {
        this.commentsService.addReply(this.route.snapshot.queryParamMap.get('requestId'), this.comment, this.newComment)
            .subscribe(this.commentResponseObserver)
    }
}
}
```
Comment component HTML

```
<div class="comment">
<app-user-image></app-user-image>
<div class="position-relative d-inline-block flex-fill">
    <div class="d-flex justify-content-between align-items-center">
        <div><strong>{{comment.author.name}} / {{comment.author.id}}</strong><span
            class="date">{{comment.created | krakenDateTime}}</span></div>
        <div class="actions">
            <button *ngIf="this.hateoas.supports(comment, 'update') && !edit"
                    type="button" class="bg-transparent border-0" title="Edit"
                    (click)="toggleEdit()"><i class="icon-kraken icon-kraken-edit"></i></button>
            <button *ngIf="this.hateoas.supports(comment, 'delete')"
                    type="button" class="bg-transparent border-0" title="Delete"
                    (click)="displayDeletionConfirmation()"><i class="icon-kraken icon-kraken-trash"></i></button>
        </div>
    </div>
    <textarea *ngIf="edit; else readonlyComment"
              #textarea
              class="d-block w-100"
              style="min-height: 7rem;"
              [rows]="rows()"
              [(ngModel)]="commentContent"></textarea>
    <ng-template #readonlyComment>
        <div [innerHTML]="commentContentHtml()"></div>
    </ng-template>
    <strong *ngIf="showReplyButton"
            class="reply-button"
            (click)="toggleReplyComponent()"><a href="#temporaryLastRow">Reply</a></strong>
    </div>
 </div>
 <div *ngIf="edit" class="animated fadeIn text-right mt-3">
    <button class="btn btn-sm discard" (click)="cancelEdit()">Discard</button>
    <button class="btn btn-sm ml-2 update" (click)="updateComment()">Update</button>
 </div>
 <div class="replies">
     <app-new-comment *ngIf="showNewReplyWindow"
                 class="d-block mt-3"
                 [comment]="comment"
                 (update)="this.update.emit()"></app-new-comment>
    <app-comment *ngIf="firstReply"
             class="d-block my-3"
             [comment]="firstReply"
             (update)="update.emit()"></app-comment>
     <div *ngIf="moreReplies.length" class="replies-toggle" (click)="showMoreReplies = 
 !showMoreReplies">
    <div class="horizontal-bar"></div>
    <span class="mx-3">{{showMoreReplies ? 'Hide' : 'See'}} {{moreReplies.length}} more 
 comments</span>
    <div class="horizontal-bar"></div>
</div>
<div *ngIf="showMoreReplies">
    <app-comment *ngFor="let reply of moreReplies"
                 class="d-block my-3"
                 [comment]="reply"
                 (update)="update.emit()"></app-comment>
</div>
<span id="temporaryLastRow"></span>
```
Comment TS

```
export class CommentComponent implements OnInit {

@ViewChild('textarea', {static: true}) textarea: ElementRef<HTMLTextAreaElement>

@Input() comment: CommentDetailsDto
@Output() update = new EventEmitter()

edit = false
showReplyButton = false
showNewReplyWindow = false
showMoreReplies = false
commentContent: string
firstReply: CommentDetailsDto
moreReplies: CommentDetailsDto[]

constructor(
    private commentsService: CommentsService,
    private modalFactoryService: ModalFactoryService,
    public hateoas: HateoasService,
    private alert: AlertService
) {
}

ngOnInit(): void {
    this.commentContent = this.comment.content
    this.showReplyButton = this.hateoas.supports(this.comment, 'reply')
    this.moreReplies = this.comment.replies.reverse().slice(1)
    this.firstReply = this.comment.replies[0]
}

toggleEdit(): void {
    this.edit = !this.edit
}

updateComment(): void {
    this.commentsService.updateComment(this.comment, this.commentContent)
        .subscribe({
            error: (error: HttpErrorResponse) => {
                this.alert.handleHttpError(error)
            },
            complete: () => {
                this.alert.showSuccess('Comment updated successfully')
                this.update.emit()
            }
        })
}

cancelEdit(): void {
    this.edit = false
    this.commentContent = this.comment.content
}

displayDeletionConfirmation(): void {
    this.modalFactoryService.openConfirmationModal(
        'Delete Comment',
        {
            message: 'Are you sure you want to delete this comment?',
            yesOptionButton: 'Yes',
            noOptionButton: 'No'
        },
        () => {
            this.hateoas.execute(this.comment, 'delete')
                .subscribe({
                    error: (error: HttpErrorResponse) => {
                        this.alert.handleHttpError(error)
                    },
                    complete: () => {
                        this.alert.showSuccess('Comment deleted successfully')
                        this.update.emit()
                    }
                })
        }
    )
}

toggleReplyComponent(): void {
    this.showNewReplyWindow = !this.showNewReplyWindow
}

commentContentHtml(): string {
    return this.commentContent.replace(/\n/g, '<br>')
}

rows(): number {
    const newLines = this.commentContent.match(/\n/g) || []
    return newLines.length + 1
}
}

 export interface Author {
id: string
name: string
}

 export interface CommentDetailsDto extends Resource {
content: string
author: Author
replies: CommentDetailsDto[]
created: string
 }
```