scroll - component - angular2 desplazarse hacia abajo(estilo de chat)
scroll to element angular 5 (11)
Agregué un cheque para ver si el usuario intentó desplazarse hacia arriba.
Voy a dejar esto aquí si alguien lo quiere :)
<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>
y el código:
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) { }
}
}
Tengo un conjunto de componentes de celda única dentro de un bucle ng-for. Tengo todo en su lugar pero parece que no puedo entender lo correcto
Actualmente tengo un
setTimeout(() => {
scrollToBottom();
});
Pero esto no funciona todo el tiempo ya que las imágenes empujan asincrónicamente la ventana hacia abajo.
¿Cuál es la forma adecuada de desplazarse hasta la parte inferior de una ventana de chat en angular2?
Compartiendo mi solución, porque no estaba completamente satisfecho con el resto.
Mi problema con
AfterViewChecked
es que a veces me
AfterViewChecked
hacia arriba y, por alguna razón, se llama a este gancho de vida y me desplaza hacia abajo incluso si no había mensajes nuevos.
Intenté usar
OnChanges
pero
this
fue un problema que me llevó a
this
solución.
Desafortunadamente, usando solo
DoCheck
, se desplazaba hacia abajo antes de que se
DoCheck
los mensajes, lo que tampoco fue útil, por lo que los combiné para que DoCheck básicamente indique
AfterViewChecked
si debería llamar a
scrollToBottom
.
Feliz de recibir comentarios.
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%
Considere usar
.scrollIntoView()
Ver https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
En angular usando material design sidenav tuve que usar lo siguiente:
let ele = document.getElementsByClassName(''md-sidenav-content'');
let eleArray = <Element[]>Array.prototype.slice.call(ele);
eleArray.map( val => {
val.scrollTop = val.scrollHeight;
});
La respuesta aceptada se dispara mientras se desplaza por los mensajes, esto evita eso.
Quieres una plantilla como esta.
<div #content>
<div #messages *ngFor="let message of messages">
{{message}}
</div>
</div>
Luego, desea utilizar una anotación ViewChildren para suscribirse a los nuevos elementos de mensaje que se agreguen a la página.
@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) {}
}
La respuesta de Vivek me funcionó, pero resultó en una expresión que cambió después de que se verificó el error. Ninguno de los comentarios funcionó para mí, pero lo que hice fue cambiar la estrategia de detección de cambios.
import { Component, ChangeDetectionStrategy } from ''@angular/core'';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: ''page1'',
templateUrl: ''page1.html'',
})
Si quiere estar seguro de que se desplazará hasta el final después de * ngFor, puede usar esto.
<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;
}
Importante aquí, la variable "último" define si está actualmente en el último elemento, por lo que puede activar el método "scrollToBottom"
Tuve el mismo problema, estoy usando una combinación
AfterViewChecked
y
@ViewChild
(Angular2 beta.3).
El componente:
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) { }
}
}
La plantilla:
<div #scrollMe style="overflow: scroll; height: xyz;">
<div class="..."
*ngFor="..."
...>
</div>
</div>
Por supuesto, esto es bastante básico.
AfterViewChecked
activa cada vez que se verifica la vista:
Implemente esta interfaz para recibir notificaciones después de cada verificación de la vista de su componente.
Si tiene un campo de entrada para enviar mensajes, por ejemplo, este evento se activa después de cada tecla (solo para dar un ejemplo).
Pero si guarda si el usuario se desplazó manualmente y luego omite
scrollToBottom()
, debería estar bien.
La solución más simple y mejor para esto es:
Agregue esta cosa simple
#scrollMe [scrollTop]="scrollMe.scrollHeight"
en el
lado de la Plantilla
<div style="overflow: scroll; height: xyz;" #scrollMe [scrollTop]="scrollMe.scrollHeight">
<div class="..."
*ngFor="..."
...>
</div>
</div>
Aquí está el enlace para la DEMOSTRACIÓN DE TRABAJO (con aplicación de chat ficticio) Y CÓDIGO COMPLETO
Funcionará con Angular2 y también hasta 5, como la demostración anterior se realiza en Angular5.
Nota :
Por error:
ExpressionChangedAfterItHasBeenCheckedError
Verifique su css, es un problema del lado css, no del lado angular, uno de los usuarios @KHAN lo resolvió eliminando el
overflow:auto; height: 100%;
overflow:auto; height: 100%;
dediv
. (por favor revise las conversaciones para más detalles)
const element = document.getElementById(''box'');
element.scrollIntoView({ behavior: ''smooth'', block: ''end'', inline: ''nearest'' });
this.contentList.nativeElement.scrollTo({left: 0 , top: this.contentList.nativeElement.scrollHeight, behavior: ''smooth''});