NativeScript / nativescript-angular

Integrating NativeScript with Angular
http://docs.nativescript.org/angular/tutorial/ng-chapter-0
Apache License 2.0
1.21k stars 241 forks source link

Slow rendering of UI #1161

Closed idoadse closed 6 years ago

idoadse commented 6 years ago

Hi guys, Im having some performance issues with rendering a chat view. The first loading of the component (loaded with 30 last messages) takes about 4 seconds which is a lot of time. The issue is not related to loading time of the chats from the server - when im bringing data to the memory of the device, it still takes a lot of time to render (about 150 messages take more than 10 seconds) I have already implemented the optimizations in this optimization guide and it seems to not significally improve performance.

Im working with nativescript 3.4.0, and I encounter the slowness both with android and iOS real devices.

I have removed most of my code that is irrelevant for reproducing the slowness and it is attached below:

My chat html implementation is attached:

<GridLayout class="full-stretch light-background" columns="*" rows="*, auto">
    <ScrollView #scrollview col="0" row="0">
        <StackLayout>
            <StackLayout #list *ngFor="let message of chats">
                <StackLayout class="message-item" orientation="horizontal">
                    <StackLayout class="sender-info-item">
                        <StackLayout *ngIf="shouldShowSenderInfo(message)">
                            <Label class="absolute-center sender-name" [text]="getSenderName(message)"></Label>
                        </StackLayout>
                    </StackLayout>
                    <StackLayout class="message-info">
                        <Label class="message-text absolute-center" [text]="message.text" textWrap="true"></Label>
                    </StackLayout>
                </StackLayout>
            </StackLayout>
        </StackLayout>
    </ScrollView>

    <StackLayout col="0" row="1">
        <GridLayout columns="*, auto, auto" rows="*" class="input-message-container">
            <StackLayout row="0" col="0" class="message-text-field-container">
                <TextView #textfield class="message-text-field" hint="Type a message" [(ngModel)]="messageToSend" (tap)="onTextInputTap()"
                    textWrap="true"></TextView>
            </StackLayout>
            <StackLayout row="0" col="1" class="absolute-center send-icon">
                <floating-action-button (tap)="sendMessage()" text="&#xf1d8;" textSize="18" buttonBackgroundColor="#2d2a3d" radiusSize="24"
                    buttonTapColor="#464163"></floating-action-button>
            </StackLayout>
            <StackLayout row="0" col="2" class="absolute-center send-icon">
                <floating-action-button (tap)="loadMoreChats()" text="&#xf1da;" textSize="18" buttonBackgroundColor="#2d2a3d" radiusSize="24"
                    buttonTapColor="#464163"></floating-action-button>
            </StackLayout>
        </GridLayout>
    </StackLayout>
</GridLayout>

My component code:

import { Component, ElementRef, Input, OnInit, ViewChild, AfterViewInit } from '@angular/core';
import { Observable } from "rxjs/Observable";
import { ChatService, MessageItem, ChatType } from "../../../services/chatService";
import { ActiveUserService } from "../../../services/activeUserService";
import { UsersProvider } from "../../../services/modelsProviders/usersProvider";
import { ScrollView } from 'tns-core-modules/ui/scroll-view';
import { CommonUtils } from '../../../utils/commonUtils';
import { GestureEventData } from 'tns-core-modules/ui/gestures/gestures';
import { StackLayout } from 'tns-core-modules/ui/layouts/stack-layout/stack-layout';
import { Label } from 'tns-core-modules/ui/label/label';
import * as Toast from 'nativescript-toast';
import * as clipboardModule from "nativescript-clipboard";

@Component({
    moduleId: module.id,
    selector: "chat",
    templateUrl: "./chat.component.html",
    styleUrls: [
        './chat.component.css',
        '../../../sharedCss.css'
    ]
})
export class ChatComponent implements OnInit, AfterViewInit {
    @Input() recipientId: Array<string>;
    @Input() chatType: ChatType;

    @ViewChild("scrollview") sv: ElementRef;

    public messageToSend: string;
    public chats: Array<MessageItem> = [];

    private firstTime: boolean = true;
    private clickedMessage: MessageItem;
    private scrollView: ScrollView;
    private copyTextToClipboard = Toast.makeText("Copied to Clipboard");

    constructor(private _commonUtils: CommonUtils, private _activeUserService: ActiveUserService, private _usersProvider: UsersProvider, private _chatService: ChatService) {
    }

    ngOnInit(): void {
        this.scrollView = <ScrollView>this.sv.nativeElement;
        if (this.recipientId && this.chatType) {
            this._chatService.messages(this.recipientId, this.chatType).subscribe(data => {
                this._chatService.markChatsAsRead(this.recipientId);
                this.chats = data;
                setTimeout(() => { // timeout for letting the chat messages to be set over the view first
                    if (this.shouldScroll()) {
                        this.scroll();
                    }
                }, 50);
            });
        }
    }

    ngAfterViewInit(): void {
        setTimeout(() => {
            this.scroll();
        }, 500);
    }

    private scroll(): void {
        const offset = this.scrollView.scrollableHeight;
        this.scrollView.scrollToVerticalOffset(offset, true)
    }

    private shouldScroll(): boolean {
        return !(this.scrollView.verticalOffset < this.scrollView.scrollableHeight - 200);
    }

    public sendMessage(): void {
        if (!this.messageToSend) {
            return;
        }
        console.log("messageToSend", this.messageToSend);
        this._chatService.sendMessage({ recipientId: this.recipientId, text: this.messageToSend });
        this.messageToSend = "";
        this.scroll();
    }

    public loadMoreChats(): void {
        if (this.chatType == 1) {
            this._chatService.getMoreChats({ recipientId: this.recipientId, lastSeenId: this.chats[0]._id });
        }
    }

    public getSenderName(messageItem: MessageItem) {
        return this._usersProvider.getModelById(messageItem.senderId).firstName;
    }

    public getMessagePrettyDate(message: MessageItem): string {
        return message.date && `${this._commonUtils.prettyDate(message.date, true)} at ${this._commonUtils.getStringForTime(message.date)}`;
    }

    public shouldShowSenderInfo(messageItem: MessageItem): boolean {
        const nextIndex = this.chats.indexOf(messageItem) + 1;
        return nextIndex === this.chats.length || (this.chats[nextIndex].senderId !== messageItem.senderId);
    }

}

The underline implementation of chatService holds an Observable<Array> that holds the messages (haven't posted it to spare some space)

I would really appreciate if someone could help tackle the issue / improve my UI view.

Thanks,

NickIliev commented 6 years ago

Hey, @idoadse thank you for contacting us but please notice that this repository is for logging issue, bugs and feature request related to nativescript-angular. For how-to related questions please use the community channels like the NativeScript forum, SO or the community slack channel.

Regarding your issue

The provided code base is not enough to recreate the whole scenario. However, one thing I am noticing is the usage of setTimeOut for each message received from your chat service.

setTimeout(() => { // timeout for letting the chat messages to be set over the view first
   if (this.shouldScroll()) {
      this.scroll();
   }
}, 50);

I would expect the above code to be obsolete by design. Still, that might nothing to do with your performance issues and providing a working demo to debug would help a lot.

I can also suggest taking a look at this blog post about implementing live chat in NativeScript + Angular.

There is also a created plugin for implementing chat view which can also be of help.

Closing the issue as how-to related.

NickIliev commented 6 years ago

Additional note: I noticed using a ngFor structural directive where you can use ListView (with out-of-the-box recycling and virtualization performance optimizations) =- this might improve your UI performance greatly (as implemented here).

idoadse commented 6 years ago

Hey Nick, thank for the clarification and the assistance! I will try your suggestions and continue the thread (if ill still need assistance) in one of the channels you have suggested. Thanks again

vforv commented 5 years ago

Same problem. I am using ngrx as well. 50 items it taks few seconds to render.