web-dev-qa-db-fra.com

angular2 défiler vers le bas (style de discussion)

J'ai un ensemble de composants de cellules individuelles dans une boucle ng-for . J'ai tout en place mais je n'arrive pas à comprendre le bon

Actuellement j'ai un

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

Mais cela ne fonctionne pas toujours comme des images de manière asynchrone. Poussez la fenêtre d'affichage vers le bas.

Quel est le moyen approprié de faire défiler jusqu'au bas d'une fenêtre de discussion dans angular2?

72
Max Alexander

J'ai eu le même problème, j'utilise une combinaison AfterViewChecked et @ViewChild (Angular2 beta.3).

Le composant:

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

Le gabarit:

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

Bien sûr, c'est assez basique. La AfterViewChecked se déclenche chaque fois que la vue est vérifiée:

Implémentez cette interface pour être averti après chaque vérification de la vue de votre composant.

Si vous avez un champ de saisie pour envoyer des messages, par exemple, cet événement est déclenché après chaque augmentation de touche (juste pour donner un exemple). Mais si vous enregistrez si l'utilisateur a défilé manuellement puis ignoré la scrollToBottom(), tout devrait bien se passer.

139
letmejustfixthat

La plus simple et la meilleure solution pour cela est:

Ajoutez ce #scrollMe [scrollTop]="scrollMe.scrollHeight" chose simple sur le côté Modèle 

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

Voici le lien pour WORKING DEMO (avec l'application de chat factice) ET CODE COMPLET

Est-ce que travailler avec Angular2 et aussi jusqu'à 5, comme ci-dessus démo est fait dans Angulaire5.


Remarque :

Pour erreur: ExpressionChangedAfterItHasBeenCheckedError 

S'il vous plaît vérifier votre css, c'est une question de côté css, pas le côté angulaire , L’un des utilisateurs @KHAN a résolu ce problème en supprimant overflow:auto; height: 100%; de div. (veuillez vérifier les conversations pour plus de détails)

97
Vivek Doshi
16
Boris Yakubchik

J'ai ajouté une vérification pour voir si l'utilisateur a essayé de faire défiler vers le haut.

Je vais juste laisser ça ici si quelqu'un le veut :)

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

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

Aucune de ces réponses n'est même à moitié décente. La réponse acceptée est déclenchée lors du défilement des messages.

Vous voulez un modèle comme celui-ci.

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

Ensuite, vous souhaitez utiliser une annotation ViewChildren pour vous abonner aux nouveaux éléments de message ajoutés à la page.

@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

Si vous voulez être sûr de faire défiler l'écran jusqu'au bout après que * ngFor soit terminé, vous pouvez l'utiliser.

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

Important ici, la variable "last" définit si vous êtes actuellement au dernier élément, vous pouvez donc déclencher la méthode "scrollToBottom"

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

Dans l’utilisation angulaire de matériaux sidenav, je devais utiliser les éléments suivants:

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

La réponse de Vivek a fonctionné pour moi, mais a abouti à une expression a changé après avoir été vérifié erreur. Aucun des commentaires n'a fonctionné pour moi, mais j'ai changé la stratégie de détection des changements.

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

Partageant ma solution, car je n'étais pas complètement satisfait du reste. Mon problème avec AfterViewChecked est que parfois je défile vers le haut, et pour une raison quelconque, ce crochet de vie s'appelle et il me fait défiler même s'il n'y a pas de nouveaux messages. J'ai essayé d'utiliser OnChanges mais ceci était un problème qui m'a amené à ceci solution. Malheureusement, n'utilisant que DoCheck, il faisait défiler l'écran avant le rendu des messages, ce qui n'était d'aucune utilité. Je les ai donc combinés pour que DoCheck indique fondamentalement AfterViewChecked s'il doit appeler scrollToBottom.

Heureux de recevoir des commentaires.

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