角度2滚动到底(聊天风格)

ng-for循环中有一组单细胞组件。

我把一切都安排妥当了,但似乎还是搞不清楚该怎么做

现在有了

setTimeout(() => {
scrollToBottom();
});

但是,这并不总是工作,因为图像异步推动视窗下降。

什么是合适的方式滚动到底部的聊天窗口在角度2?

242813 次浏览

我有同样的问题,我使用的是 AfterViewChecked@ViewChild的组合(Angular2beta。3)。

组件:

import {..., AfterViewChecked, ElementRef, ViewChild, OnInit} from 'angular2/core'
@Component({
...
})
export class ChannelComponent implements OnInit, AfterViewChecked {
@ViewChild('scrollMe') private myScrollContainer: ElementRef;


ngOnInit() {
this.scrollToBottom();
}


ngAfterViewChecked() {
this.scrollToBottom();
}


scrollToBottom(): void {
try {
this.myScrollContainer.nativeElement.scrollTop = this.myScrollContainer.nativeElement.scrollHeight;
} catch(err) { }
}
}

模板:

<div #scrollMe style="overflow: scroll; height: xyz;">
<div class="..."
*ngFor="..."
...>
</div>
</div>

当然这是非常基本的,每次检查视图时 AfterViewChecked都会触发:

实现此接口,以便在每次检查组件的视图后获得通知。

例如,如果您有一个用于发送消息的输入字段,那么在每次键入之后都会触发此事件(仅举一个例子)。但是如果你保存用户是否手动滚动,然后跳过 scrollToBottom(),你应该没问题。

在角使用材料设计 Sidenav 我必须使用以下:

let ele = document.getElementsByClassName('md-sidenav-content');
let eleArray = <Element[]>Array.prototype.slice.call(ele);
eleArray.map( val => {
val.scrollTop = val.scrollHeight;
});

我添加了一个检查,看看用户是否尝试向上滚动。

如果有人要的话,我就把这个放在这里了:)

<div class="jumbotron">
<div class="messages-box" #scrollMe (scroll)="onScroll()">
<app-message [message]="message" [userId]="profile.userId" *ngFor="let message of messages.slice().reverse()"></app-message>
</div>


<textarea [(ngModel)]="newMessage" (keyup.enter)="submitMessage()"></textarea>
</div>

还有密码:

import { AfterViewChecked, ElementRef, ViewChild, Component, OnInit } from '@angular/core';
import {AuthService} from "../auth.service";
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/concatAll';
import {Observable} from 'rxjs/Rx';


import { Router, ActivatedRoute } from '@angular/router';


@Component({
selector: 'app-messages',
templateUrl: './messages.component.html',
styleUrls: ['./messages.component.scss']
})
export class MessagesComponent implements OnInit {
@ViewChild('scrollMe') private myScrollContainer: ElementRef;
messages:Array<MessageModel>
newMessage = ''
id = ''
conversations: Array<ConversationModel>
profile: ViewMyProfileModel
disableScrollDown = false


constructor(private authService:AuthService,
private route:ActivatedRoute,
private router:Router,
private conversationsApi:ConversationsApi) {
}


ngOnInit() {


}


public submitMessage() {


}


ngAfterViewChecked() {
this.scrollToBottom();
}


private onScroll() {
let element = this.myScrollContainer.nativeElement
let atBottom = element.scrollHeight - element.scrollTop === element.clientHeight
if (this.disableScrollDown && atBottom) {
this.disableScrollDown = false
} else {
this.disableScrollDown = true
}
}




private scrollToBottom(): void {
if (this.disableScrollDown) {
return
}
try {
this.myScrollContainer.nativeElement.scrollTop = this.myScrollContainer.nativeElement.scrollHeight;
} catch(err) { }
}


}

最简单也是最好的解决方案是:

模板端上添加这个 #scrollMe [scrollTop]="scrollMe.scrollHeight"简单的东西

<div style="overflow: scroll; height: xyz;" #scrollMe [scrollTop]="scrollMe.scrollHeight">
<div class="..."
*ngFor="..."
...>
</div>
</div>

下面是 < strong > WorkingDEMO (使用虚拟聊天应用程序)和 完整代码的链接

将与 Angular2工作,也高达5,因为上面的演示是做在 Angular5.


注:

错误: ExpressionChangedAfterItHasBeenCheckedError

请检查您的 css,这是一个 css 方面的问题,而不是角的一面 ,其中一个用户@KHAN 通过从 div中删除 overflow:auto; height: 100%;解决了这个问题(请查看对话的细节)

接受的答案在滚动消息时触发,这可以避免这种情况。

你想要一个这样的模板。

<div #content>
<div #messages *ngFor="let message of messages">
\{\{message}}
</div>
</div>

然后,您需要使用 ViewChildren 注释来订阅添加到页面中的新消息元素。

@ViewChildren('messages') messages: QueryList<any>;
@ViewChild('content') content: ElementRef;


ngAfterViewInit() {
this.scrollToBottom();
this.messages.changes.subscribe(this.scrollToBottom);
}


scrollToBottom = () => {
try {
this.content.nativeElement.scrollTop = this.content.nativeElement.scrollHeight;
} catch (err) {}
}
this.contentList.nativeElement.scrollTo({left: 0 , top: this.contentList.nativeElement.scrollHeight, behavior: 'smooth'});

如果希望确保在 * ngFor 完成后滚动到结束,可以使用以下方法。

<div #myList>
<div *ngFor="let item of items; let last = last">
\{\{item.title}}
\{\{last ? scrollToBottom() : ''}}
</div>
</div>


scrollToBottom() {
this.myList.nativeElement.scrollTop = this.myList.nativeElement.scrollHeight;
}

这里重要的是,“最后”变量定义当前是否在最后一项,因此可以触发 “ scrollTobottom”方法

const element = document.getElementById('box');
element.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });

分享我的解决方案,因为我不完全满意其余的。我使用 AfterViewChecked的问题是,有时我向上滚动,出于某种原因,这个 life hook 被调用,即使没有新消息,它也会向下滚动。我尝试使用 OnChanges,但 这个是一个问题,这导致我的 这个解决方案。不幸的是,它只使用 DoCheck,在呈现消息之前向下滚动,这也是没有用的,所以我将它们组合起来,如果 DoCheck 应该调用 scrollToBottom,那么它基本上指示 AfterViewChecked

很高兴收到反馈。

export class ChatComponent implements DoCheck, AfterViewChecked {


@Input() public messages: Message[] = [];
@ViewChild('scrollable') private scrollable: ElementRef;


private shouldScrollDown: boolean;
private iterableDiffer;


constructor(private iterableDiffers: IterableDiffers) {
this.iterableDiffer = this.iterableDiffers.find([]).create(null);
}


ngDoCheck(): void {
if (this.iterableDiffer.diff(this.messages)) {
this.numberOfMessagesChanged = true;
}
}


ngAfterViewChecked(): void {
const isScrolledDown = Math.abs(this.scrollable.nativeElement.scrollHeight - this.scrollable.nativeElement.scrollTop - this.scrollable.nativeElement.clientHeight) <= 3.0;


if (this.numberOfMessagesChanged && !isScrolledDown) {
this.scrollToBottom();
this.numberOfMessagesChanged = false;
}
}


scrollToBottom() {
try {
this.scrollable.nativeElement.scrollTop = this.scrollable.nativeElement.scrollHeight;
} catch (e) {
console.error(e);
}
}


}

Chat. Component. html

<div class="chat-wrapper">


<div class="chat-messages-holder" #scrollable>


<app-chat-message *ngFor="let message of messages" [message]="message">
</app-chat-message>


</div>


<div class="chat-input-holder">
<app-chat-input (send)="onSend($event)"></app-chat-input>
</div>


</div>

聊天组件 Sass

.chat-wrapper
display: flex
justify-content: center
align-items: center
flex-direction: column
height: 100%


.chat-messages-holder
overflow-y: scroll !important
overflow-x: hidden
width: 100%
height: 100%

Vivek 的回答对我很有用,但是结果是表达式在检查错误后发生了变化。这些评论对我来说都不起作用,但我做的是改变了变化检测策略。

import {  Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'page1',
templateUrl: 'page1.html',
})

在阅读了其他解决方案之后,我能想到的最好的解决方案是,所以你只运行你需要的如下: 您可以使用 ngOnChanges 来检测适当的更改

ngOnChanges() {
if (changes.messages) {
let chng = changes.messages;
let cur  = chng.currentValue;
let prev = chng.previousValue;
if(cur && prev) {
// lazy load case
if (cur[0].id != prev[0].id) {
this.lazyLoadHappened = true;
}
// new message
if (cur[cur.length -1].id != prev[prev.length -1].id) {
this.newMessageHappened = true;
}
}
}


}

使用 ngAfterViewChecked 可以在呈现之前但在计算完整高度之后实际强制执行更改

ngAfterViewChecked(): void {
if(this.newMessageHappened) {
this.scrollToBottom();
this.newMessageHappened = false;
}
else if(this.lazyLoadHappened) {
// keep the same scroll
this.lazyLoadHappened = false
}
}

如果您想知道如何实现 scrollTobottom

@ViewChild('scrollWrapper') private scrollWrapper: ElementRef;
scrollToBottom(){
try {
this.scrollWrapper.nativeElement.scrollTop = this.scrollWrapper.nativeElement.scrollHeight;
} catch(err) { }
}

这是 Stackblitz上另一个好的解决方案。

或者 :

接受的答案是一个很好的解决方案,但它可以得到改进,因为考虑到 NgAfterViewChecked ()生命周期钩子的工作方式,您的内容/聊天可能经常不自觉地滚动到底部。

这是一个改进版本..。

组件

import {..., AfterViewChecked, ElementRef, ViewChild, OnInit} from 'angular2/core'
@Component({
...
})
export class ChannelComponent implements OnInit, AfterViewChecked {
@ViewChild('scrollMe') private myScrollContainer: ElementRef;


/**Add the variable**/
scrolledToBottom = false;


ngAfterViewChecked() {
this.scrollToBottom();
}


scrollToBottom(): void {
try {
/**Add the condition**/
if(!this.scrolledToBottom){
this.myScrollContainer.nativeElement.scrollTop = this.myScrollContainer.nativeElement.scrollHeight;
}
} catch(err) { }
}


/**Add the method**/
onScroll(){
this.scrolledToBottom = true;
}
}

模板

<!--Add a scroll event listener-->
<div #scrollMe
style="overflow: scroll; height: xyz;"
(scroll)="onScroll()">
<div class="..."
*ngFor="..."
...>
</div>
</div>

如果有人有这个问题的角度9,这是我如何设法解决它。

我从 #scrollMe [scrollTop]="scrollMe.scrollHeight"的解决方案开始,正如人们提到的,我得到了 ExpressionChangedAfterItHasBeenCheckedError错误。

为了解决这个问题,我只需要添加 ts 组件:

@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
...})


constructor(private cdref: ChangeDetectorRef) {}


ngAfterContentChecked() {
this.cdref.detectChanges();
}

ExpressionChangedAfterItHasBeenCheckedError: 表达式在检查后发生了更改

问题的标题提到“聊天风格”滚动到底部,这也是我所需要的。没有一个答案真正让我满意,因为我 真的想要做的是在添加或销毁子元素时滚动到 div 的底部。我最终用这个非常简单的指令做到了这一点,它利用了 MutationObserver API

@Directive({
selector: '[pinScroll]',
})
export class PinScrollDirective implements OnInit, OnDestroy {
private observer = new MutationObserver(() => {
this.scrollToPin();
});


constructor(private el: ElementRef) {}


ngOnInit() {
this.observer.observe(this.el.nativeElement, {
childList: true,
});
}


ngOnDestroy() {
this.observer.disconnect();
}


private scrollToPin() {
this.el.nativeElement.scrollTop = this.el.nativeElement.scrollHeight;
}
}

只需将此指令附加到 list 元素,当 DOM 中的列表项发生更改时,它就会滚动到底部。这是我个人一直在寻找的行为。本指令假定您已经在处理 list 元素上的 heightoverflow规则。

如果你使用的是最新版本的 Angular,以下内容就足够了:

<div #scrollMe style="overflow: scroll; height: xyz;" [scrollTop]="scrollMe.scrollHeight>
<div class="..."
*ngFor="..."
...>
</div>
</div>

要添加平滑的滚动这样做

#scrollMe [scrollTop]="scrollMe.scrollHeight" style="scroll-behavior: smooth;"

还有

this.cdref.detectChanges();

以防有人用爱奥尼亚和安格拉, 这里有一个链接,它使用一个非常简单的代码来完成从底部(或顶部)滚动到底部(或顶部)的操作:

Https://forum.ionicframework.com/t/scroll-content-to-top-bottom-using-ionic-4-solution/163048