Angular2 прокрутите вниз (стиль чата)
У меня есть набор отдельных элементов ячейки в цикле ng-for.
У меня есть все на своем месте, но я не могу понять, как правильно
В настоящее время у меня есть
setTimeout(() => {
scrollToBottom();
});
Но это не работает все время, так как изображения асинхронно толкают окно просмотра.
Каков подходящий способ прокрутки до нижней части окна чата в angular2?
Ответы
Ответ 1
У меня была та же проблема, я использую 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
запускается каждый раз при просмотре представления:
Внедрите этот интерфейс, чтобы получать уведомления после каждой проверки вашего представления компонентов.
Если у вас есть поле ввода для отправки сообщений, например, это событие запускается после каждой клавиатуры (просто для примера). Но если вы сохраните, будет ли пользователь прокручиваться вручную, а затем пропустите scrollToBottom()
, все будет в порядке.
Ответ 2
Самое простое и лучшее решение для этого:
Добавьте эту простую вещь #scrollMe [scrollTop]="scrollMe.scrollHeight"
на стороне шаблона
<div style="overflow: scroll; height: xyz;" #scrollMe [scrollTop]="scrollMe.scrollHeight">
<div class="..."
*ngFor="..."
...>
</div>
</div>
Вот ссылка на WORKING DEMO (с приложением для чата) и полный код
Будет работать с Angular2, а также до 5, как показано выше, демонстрация сделана в Angular5.
Замечания:
Для ошибки: ExpressionChangedAfterItHasBeenCheckedError
Пожалуйста, проверьте ваш css, это проблема css, а не Angular, один из пользователей @KHAN решил это, удалив overflow:auto; height: 100%;
overflow:auto; height: 100%;
от div
. (пожалуйста, проверьте разговоры для деталей)
Ответ 3
Рассмотреть возможность использования
.scrollIntoView()
См. Https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView.
Ответ 4
Я добавил чек, чтобы проверить, пытался ли пользователь прокручиваться.
Я просто собираюсь оставить это здесь, если кто-то захочет:)
<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) { }
}
}
Ответ 5
Принятый ответ срабатывает при прокрутке сообщений, этого избежать.
Вы хотите такой шаблон.
<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) {}
}
Ответ 6
Если вы хотите быть уверены, что прокручиваете до конца после завершения * 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"
Ответ 7
this.contentList.nativeElement.scrollTo({left: 0 , top: this.contentList.nativeElement.scrollHeight, behavior: 'smooth'});
Ответ 8
Поделиться своим решением, потому что я не был полностью удовлетворен остальным. Моя проблема с AfterViewChecked
заключается в том, что иногда я прокручиваю вверх, и по какой-то причине 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%
Ответ 9
В angular с использованием материального дизайна sidenav мне пришлось использовать следующее:
let ele = document.getElementsByClassName('md-sidenav-content');
let eleArray = <Element[]>Array.prototype.slice.call(ele);
eleArray.map( val => {
val.scrollTop = val.scrollHeight;
});
Ответ 10
const element = document.getElementById('box');
element.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });
Ответ 11
Ответ Vivek сработал для меня, но в результате выражение изменилось после того, как была проверена ошибка. Ни один из комментариев не работал для меня, но я изменил стратегию обнаружения изменений.
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'page1',
templateUrl: 'page1.html',
})