I have a component like this
export class NotificationPostComponent extends PostComponent implements OnInit, AfterViewInit {
commentsDto: IComment[] = [];
commentId = '';
ngOnInit(): void {
this.route.data.subscribe(data=>{
this.post = data.post;
});
this.route.params.subscribe((param : Params) => {
const slug = param['slug'];
this.commentId = param['commentid'];
this.notifiService.loadCommentsOfPost(slug, this.commentId).subscribe(data=>{
this.commentsDto = data.comments;
})
});
super.ngOnInit();
}
ngAfterViewInit(): void {
var idDiv = document.getElementById(this.commentId)
console.log(idDiv)
}
}
This is a html with id is Guid:
<div id="{{cmt.id}}" *ngFor="let cmt of commentsDto">
<div>comment</div>
</div>
the problem is that after view is initialized its document.getElementById is always null. Please tell me where did I go wrong?
CodePudding user response:
<!--see the tempalte reference variable "#comment"-->
<div #comment id="{{cmt.id}}" *ngFor="let cmt of commentsDto">
<div>comment</div>
</div>
//In commentsyou has the "QueryList" (an specie of array)
@ViewChildren('comment') comments:QueryList<ElementRef>
this.notifiService.loadCommentsOfPost(slug, this.commentId).subscribe(data=>{
this.commentsDto = data.comments;
//you neeed wait Angular "render" the elements
setTimeout(()=>{
const comment=this.comments
.find(x=>x.nativeElement.getAttribute('id')==myId)
if (comment)
comment.nativeElement.scrollIntoView(
{Behaivor:"Smooth",Block:Start,inline:"Nearest"})
})
})
Well, you can find using "nativeElement.getAttribute" or find the index of the comment and find the element at this index. some like
const index=this.commentsDto.findIndex(x=>x.id==myInd)
const comment=this.comments
.find((_,i)=>i==index)
CodePudding user response:
Instead of using document.getElementById
you can use Angular's built-in API: @ViewChildren.
Then, the inline Template could be extracted into a new component.
Template
<button (click)="scrollToBottom()">Scroll to last comment</button>
<app-comment [title]="comment.title" *ngFor="let comment of comments$ | async">
</app-comment>
<button (click)="scrollToTop()">Scroll to 1st Comment</button>
In the Component, use ViewChildren to access the collection of your CommentComponents represented by a QueryList.
If you need to access the HTML-Element directly, you need to configure @ViewChildren to read the ElementRefence (@ViewChildren(CommentComponent, { read: ElementRef })
).
Component
export class NotificationPostComponent extends PostComponent
implements OnInit, AfterViewInit {
comments$ = /* Stream of Comments */
@ViewChildren(CommentComponent, { read: ElementRef })
commentElementReferences: QueryList<ElementRef<HTMLElement>> | undefined;
}
Next, if you need to scroll to a specific element by a ID or something like that, you can mark each Comment Component with an attr-Binding
.
Template
<app-comment
[title]="comment.title"
[attr.data-anchor]="comment.id"
*ngFor="let comment of comments$ | async"
>
</app-comment>
Now, you can add some logic to your component finding the Element you need, allowing you to scroll to it.
Component
private scrollToCommentById(id: string) {
if (!this.commentElementReferences) return;
const commentToScrollTo = this.commentElementReferences
.map((reference) => reference.nativeElement)
.find((element) => element.getAttribute('data-anchor') === id);
commentToScrollTo.scrollIntoView({ behavior: 'smooth' });
}
Finally, you can connect to the activated route, triggering your scrolling mechanism when navigation kicks in.
ngAfterViewInit(): void {
// Listen to parameter changes in route
this.activatedRoute.paramMap
.pipe(
map((paramMap) => paramMap.get('id')),
filter((id) => !!id),
tap((id) => this.scrollToCommentById(id))
)
.subscribe();
}
Demo
This StackBlitz @ViewChild Demo shows how everything can be set up. There you also see how the button is set up that scrolls to a specific Comment by its ID.