angular2滚动到底部(聊天风格)

时间:2016-02-05 20:14:15

标签: scroll typescript angular

我在ng-for循环中有一组单细胞成分。 我已经掌握了一切,但我似乎无法找到合适的

目前我有一个

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

但是这不会一直有效,因为图像会异步地向下推动视口。

在angular2中滚动到聊天窗口底部的适当方法是什么?

16 个答案:

答案 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();
    }

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'

答案 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 中的列表项发生变化,它就会滚动到底部。这是我个人一直在寻找的行为。此指令假定您已经在处理列表元素上的 heightoverflow 规则。