我在ng-for循环中有一组单细胞成分。 我已经掌握了一切,但我似乎无法找到合适的
目前我有一个
setTimeout(() => {
scrollToBottom();
});
但是这不会一直有效,因为图像会异步地向下推动视口。
在angular2中滚动到聊天窗口底部的适当方法是什么?
答案 0 :(得分:153)
我遇到了同样的问题,我正在使用AfterViewChecked
和@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
:
实施此界面,以便在每次检查组件的视图后收到通知。
如果你有一个用于发送消息的输入字段,例如在每个keyup之后触发这个事件(仅举一个例子)。但是如果你保存用户是否手动滚动然后跳过scrollToBottom()
你应该没问题。
答案 1 :(得分:109)
最简单,最佳解决方案是:
在模板方
上添加此#scrollMe [scrollTop]="scrollMe.scrollHeight"
简单内容
<div style="overflow: scroll; height: xyz;" #scrollMe [scrollTop]="scrollMe.scrollHeight">
<div class="..."
*ngFor="..."
...>
</div>
</div>
以下是WORKING DEMO(使用虚拟聊天应用)和 FULL CODE
的链接将使用Angular2以及最多5个,如上所述演示完成 Angular5。
注意:
错误:
ExpressionChangedAfterItHasBeenCheckedError
请检查你的CSS,这是css方面的问题,而不是Angular方面 ,@KHAN用户之一通过从
overflow:auto; height: 100%;
删除div
来解决这个问题。 (请查看对话以获取详细信息)
答案 2 :(得分:17)
答案 3 :(得分:13)
我添加了一项检查,看看用户是否尝试向上滚动。
如果有人想要,我会在这里离开这里:)
<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) { }
}
}
答案 4 :(得分:10)
滚动浏览消息时会触发接受的答案,这可以避免这种情况。
你想要一个像这样的模板。
<div #content>
<div #messages *ngFor="let message of messages">
{{message}}
</div>
</div>
然后,您希望使用ViewChildren批注来订阅要添加到页面的新消息元素。
@ViewChildren('messages') messages: QueryList<any>;
@ViewChild('content') content: ElementRef;
ngAfterViewInit() {
this.messages.changes.subscribe(this.scrollToBottom);
}
scrollToBottom = () => {
try {
this.content.nativeElement.scrollTop = this.content.nativeElement.scrollHeight;
} catch (err) {}
}
答案 5 :(得分:7)
如果你想确定,在完成* ngFor后滚动到最后,你可以使用它。
public interface IEmailAttachment
{
string Name { get; }
byte[] FileData { get; }
}
public static void Send(MailMessage mailMessage, IEnumerable<IEmailAttachment> attachments)
{
try
{
// Get the configuration data
string server = ConfigReader.EmailServer;
int port = ConfigReader.EmailPort;
string username = ConfigReader.SendGridUserName;
string password = ConfigReader.SendGridPassword;
smtpClient.EnableSsl = false;
smtpClient.Credentials = new NetworkCredential(username, password);
// Create the SMTP Client
SmtpClient smtpClient = new SmtpClient(server, port);
// Prepare the MailMessage
mailMessage.From = new MailAddress(ConfigReader.FromEmail);
var toEmails = ConfigReader.ToEmail.Split(',');
foreach (var toEmail in toEmails)
{
mailMessage.To.Add(toEmail);
}
var ccEmails = ConfigReader.EmailCc.Split(',');
foreach (var ccEmail in ccEmails)
{
mailMessage.CC.Add(ccEmail);
}
// Add attachments
List<MemoryStream> files = new List<MemoryStream>();
if (attachments != null)
{
foreach (IEmailAttachment file in attachments)
{
MemoryStream bufferStream = new MemoryStream(file.FileData);
files.Add(bufferStream);
Attachment attachment = new Attachment(bufferStream, file.Name);
mailMessage.Attachments.Add(attachment);
}
}
mailMessage.IsBodyHtml = true;
// Send the email
smtpClient.Send(mailMessage);
foreach (MemoryStream stream in files)
{
stream.Dispose();
}
}
catch (Exception)
{
throw;
}
}
重要的是,“last”变量定义您当前是否在最后一项,因此您可以触发“scrollToBottom”方法
答案 6 :(得分:6)
f()
答案 7 :(得分:1)
分享我的解决方案,因为我对其余部分并不完全满意。我对AfterViewChecked
的问题是,有时我会向上滚动,由于某种原因,这个生命挂钩被调用,即使没有新消息,它也会使我向下滚动。我尝试使用OnChanges
,但是this是一个问题,导致我找到了this解决方案。不幸的是,只使用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%
答案 8 :(得分:1)
如果您使用的是最新版本的 Angular,以下就足够了:
<div #scrollMe style="overflow: scroll; height: xyz;" [scrollTop]="scrollMe.scrollHeight>
<div class="..."
*ngFor="..."
...>
</div>
</div>
答案 9 :(得分:0)
在角度使用材料设计sidenav我必须使用以下:
let ele = document.getElementsByClassName('md-sidenav-content');
let eleArray = <Element[]>Array.prototype.slice.call(ele);
eleArray.map( val => {
val.scrollTop = val.scrollHeight;
});
答案 10 :(得分:0)
const element = document.getElementById('box');
element.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });
答案 11 :(得分:0)
Vivek的答案对我有用,但导致表达式被检查错误后已更改。没有任何评论对我有用,但是我所做的是更改更改检测策略。
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'page1',
templateUrl: 'page1.html',
})
答案 12 :(得分:0)
在阅读了其他解决方案之后,我能想到的最好的解决方案是,您只需运行所需的内容即可: 您使用ngOnChanges来检测正确的更改
L
然后您使用ngAfterViewChecked在更改呈现之前但在计算出整个高度之后实际实施更改
all_numbers
如果您想知道如何实现scrollToBottom
all_numbers <- as.numeric(all_numbers)
答案 13 :(得分:0)
这是stackblitz上的另一个很好的解决方案。
或者:
可接受的答案是一个很好的解决方案,但由于{/ {3}}生命周期挂钩的工作原理,您的内容/聊天可能经常不由自主地滚动到底部,因此可以改进它。
这是改进版本...
组件
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>
答案 14 :(得分:0)
如果有人在使用 Angular 9 时遇到这个问题,我会设法解决这个问题。
我从 #scrollMe [scrollTop]="scrollMe.scrollHeight"
的解决方案开始,我得到了人们提到的 ExpressionChangedAfterItHasBeenCheckedError 错误。
为了解决这个问题,我只添加了我的 ts 组件:
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
...})
constructor(private cdref: ChangeDetectorRef) {}
ngAfterContentChecked() {
this.cdref.detectChanges();
}
答案 15 :(得分:0)
问题的标题提到“聊天风格”滚动到底部,我也需要。这些答案都没有真正让我满意,因为我真正想做的是在添加或销毁子元素时滚动到 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;
}
}
您只需将此指令附加到您的列表元素,只要 DOM 中的列表项发生变化,它就会滚动到底部。这是我个人一直在寻找的行为。此指令假定您已经在处理列表元素上的 height
和 overflow
规则。