webentwicklung-frage-antwort-db.com.de

angle2 nach unten scrollen (Chat-Stil)

Ich habe eine Menge einzelner Zellkomponenten in einer ng-for-Schleife ... Ich habe alles an Ort und Stelle, aber ich kann nicht das richtige herausfinden

Momentan habe ich eine

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

Dies funktioniert jedoch nicht immer, da Bilder asynchron den Viewport nach unten drücken.

Was ist der geeignete Weg, um zum unteren Rand eines Chat-Fensters in angle2 zu scrollen?

72
Max Alexander

Ich hatte das gleiche Problem, ich verwende eine Kombination aus AfterViewChecked und @ViewChild (Angular2 beta.3).

Die Komponente:

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) { }                 
    }
}

Die Vorlage:

<div #scrollMe style="overflow: scroll; height: xyz;">
    <div class="..." 
        *ngFor="..."
        ...>  
    </div>
</div>

Natürlich ist das ziemlich einfach. Die Variable AfterViewChecked wird jedes Mal ausgelöst, wenn die Ansicht geprüft wurde:

Implementieren Sie diese Schnittstelle, um nach jeder Überprüfung der Ansicht Ihrer Komponente benachrichtigt zu werden.

Wenn Sie beispielsweise ein Eingabefeld zum Senden von Nachrichten haben, wird dieses Ereignis nach jedem Keyup ausgelöst (nur um ein Beispiel zu geben). Wenn Sie jedoch speichern, ob der Benutzer manuell gescrollt wurde, und dann die scrollToBottom() überspringen, ist dies in Ordnung.

139

Simplest und die beste Lösung dafür ist:

Fügen Sie diese #scrollMe [scrollTop]="scrollMe.scrollHeight" einfache Sache auf Template-Seite hinzu. 

<div style="overflow: scroll; height: xyz;" #scrollMe [scrollTop]="scrollMe.scrollHeight">
    <div class="..." 
        *ngFor="..."
        ...>  
    </div>
</div>

Hier ist der Link für WORKING DEMO (Mit Dummy-Chat-App) UND FULL CODE

Funktioniert mit Angular2 und auch bis zu 5, Wie oben beschrieben, erfolgt die Demo in Angular5.


Hinweis :

Für fehler: ExpressionChangedAfterItHasBeenCheckedError 

Bitte überprüfen Sie Ihre CSS, es ist ein Problem der CSS-Seite, nicht der Angular-Seite , Einer der Benutzer @KHAN hat dies gelöst, indem er overflow:auto; height: 100%; aus div entfernt hat. (Bitte überprüfen Sie die Gespräche für Details)

97
Vivek Doshi

Erwägen Sie die Verwendung 

.scrollIntoView()

Siehe https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView

16
Boris Yakubchik

Ich habe einen Check hinzugefügt, um zu sehen, ob der Benutzer versucht hat, nach oben zu scrollen.

Ich werde das hier einfach lassen, wenn es jemand will :)

<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>

und der Code:

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) { }
    }

}
11
robert king

Keine dieser Antworten ist nur halb anständig. Die akzeptierte Antwort wird ausgelöst, während die Nachrichten gescrollt werden.

Sie möchten eine Vorlage wie diese.

<div #content>
  <div #messages *ngFor="let message of messages">
    {{message}}
  </div>
</div>

Dann möchten Sie eine ViewChildren-Anmerkung verwenden, um neue Nachrichtenelemente zu abonnieren, die der Seite hinzugefügt werden.

@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) {}
}
8
denixtry

Wenn Sie sicher sein möchten, dass Sie nach dem Ausführen von * ngFor bis zum Ende blättern, können Sie dies verwenden.

<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;
}

Wichtig hierbei ist, dass die Variable "last" definiert, ob Sie sich gerade am letzten Element befinden, sodass Sie die "scrollToBottom" - Methode auslösen können

6
Mert
this.contentList.nativeElement.scrollTo({left: 0 , top: this.contentList.nativeElement.scrollHeight, behavior: 'smooth'});
4
Stas Kozlov

Im eckigen Materialdesign sidenav musste ich folgendes verwenden:

let ele = document.getElementsByClassName('md-sidenav-content');
    let eleArray = <Element[]>Array.prototype.slice.call(ele);
    eleArray.map( val => {
        val.scrollTop = val.scrollHeight;
    });
0
Helzgate
const element = document.getElementById('box');
  element.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });
0
KANOMDOOK

Die Antwort von Vivek hat für mich funktioniert, hat aber dazu geführt, dass sich ein Ausdruck geändert hat, nachdem ein Fehler überprüft wurde. Keiner der Kommentare hat für mich funktioniert, aber ich habe die Änderungserkennungsstrategie geändert.

import {  Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'page1',
  templateUrl: 'page1.html',
})
0
user3610386

Ich teile meine Lösung, weil ich mit dem Rest nicht ganz zufrieden war. Mein Problem mit AfterViewChecked ist, dass ich manchmal nach oben scrolle, und aus irgendeinem Grund wird dieser Life Hook aufgerufen und es rollt mich herunter, auch wenn keine neuen Nachrichten vorhanden waren. Ich habe versucht, OnChanges zu verwenden, aber this war ein Problem, das mich zu this solution führte. Leider wurde nur mit DoCheck gescrollt, bevor die Nachrichten gerendert wurden, was auch nicht nützlich war. Daher habe ich sie so kombiniert, dass DoCheck AfterViewChecked anzeigt, wenn scrollToBottom aufgerufen werden soll.

Freut mich über Feedback.

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.shouldScrollDown = true;
        }
    }

    ngAfterViewChecked(): void {
        if (this.shouldScrollDown) {
            this.scrollToBottom();
            this.shouldScrollDown = 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%
0
RTYX