Answers:
我遇到了同样的问题,我使用的是AfterViewChecked
and @ViewChild
组合(Angular2 beta.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()
应该没问题。
最简单,最好的解决方案是:
#scrollMe [scrollTop]="scrollMe.scrollHeight"
在模板一侧添加此简单的内容
<div style="overflow: scroll; height: xyz;" #scrollMe [scrollTop]="scrollMe.scrollHeight">
<div class="..."
*ngFor="..."
...>
</div>
</div>
可以与Angular2一起使用,最多也可以与Angular5一起使用,因为上面的演示是在Angular5中完成的。
注意 :
对于错误:
ExpressionChangedAfterItHasBeenCheckedError
请检查您的css,这是css方面的问题,而不是Angular方面,@KHAN用户之一已通过
overflow:auto; height: 100%;
从中删除来解决了此问题div
。(请检查对话以了解详细信息)
Expression has changed after it was checked. Previous value: 'scrollTop: 1758'. Current value: 'scrollTop: 1734'
。解决了吗
我添加了一个检查以查看用户是否尝试向上滚动。
如果有人要的话,我就把它留在这里:)
<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) { }
}
}
滚动浏览消息时会触发接受的答案,这避免了这种情况。
您需要这样的模板。
<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) {}
}
如果要确保在完成* 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;
}
重要的是,“ last”变量定义您当前是否位于最后一项,因此可以触发“ scrollToBottom”方法
this.contentList.nativeElement.scrollTo({left: 0 , top: this.contentList.nativeElement.scrollHeight, behavior: 'smooth'});
分享我的解决方案,因为我对其余的部分并不完全满意。我的问题AfterViewChecked
是,有时我会向上滚动,并且由于某种原因,这个生命挂钩被调用,即使没有新消息,它也会使我向下滚动。我尝试使用OnChanges
,但这个是一个问题,这使我这个解决方案。不幸的是,仅使用DoCheck
,它就在呈现消息之前向下滚动,这也没有用,因此我将它们组合在一起,以便DoCheck基本上指示AfterViewChecked
它是否应该调用scrollToBottom
。
很高兴收到反馈。
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>
chat.component.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%
const isScrolledDown = Math.abs(this.scrollable.nativeElement.scrollHeight - this.scrollable.nativeElement.scrollTop - this.scrollable.nativeElement.clientHeight) <= 3.0;
其中3.0为像素公差)。如果用户手动向上滚动,则可以使用它ngDoCheck
有条件地不设置shouldScrollDown
为true
。
ngAfterViewChecked
与另一个布尔值一起使用。我更改了shouldScrollDown
名称,numberOfMessagesChanged
以使此布尔值所指的
if (this.numberOfMessagesChanged && !isScrolledDown)
,但我认为您不理解我的建议的意图。我的真实意图是即使用户手动向上滚动,即使添加了新消息也不会自动向下滚动。没有此功能,如果您向上滚动以查看历史记录,则聊天消息将在收到新消息后立即向下滚动。这可能是非常令人讨厌的行为。:)因此,我的建议是在将新消息添加到DOM 之前检查聊天是否向上滚动,如果不是,则不要自动滚动。为时已晚,因为DOM已经更改。ngAfterViewChecked
阅读完其他解决方案后,我能想到的最佳解决方案是,您仅运行所需的内容:使用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) { }
}