首页 > 解决方案 > 在 Angular 中发出 http 请求时,图像闪烁并且页面滚动到顶部 - 内部视频

问题描述

视频:https ://vimeo.com/341988489

我有一个包含多个警报的警报页面。每个警报都有一个布尔值“isRead”,指示是否读取警报。橙色信封表示未读取警报。当单击这个橙色信封时,它会变成灰色,并且会向服务器发送一个 http 请求,以将此布尔值从 false 更新为 true。

发生这种情况时,应用程序会出现一些问题: 1. 警报中显示的图像头像“闪烁”,页面滚动到顶部。2. UI 冻结几秒钟。

每当在警报页面组件中向服务器发出 http 请求时,这似乎都会发生。在单击警报组件“信封”并调用 toggleIsRead 时,以及在单击“全部标记为已读”时,如视频所示。

我不知道是什么导致了这个问题。但是,如果这可以提供任何线索,我已经在开发者工具中显示了网络选项卡。

在alerts-page.component.ts 中,我从服务器获取当前用户的警报,并在alert-messages.service.ts 中初始化一个名为messages 的Observable。然后使用它来填充 alertsCreatedToday 和 alertsCreatedAWeekAgo。然后将这些 observable 设置为 alertsCreatedToday$ 和 alertsCreatedAWeekAgo$。

然后用来自这些 Observable 的数据填充每个警报。一张 mat-card 显示每个警报的信息。

当单击警报上的信封以切换布尔值“IsRead”时 - 此布尔值使用乐观更新方法首先更改“alertRecipient-model”上的布尔值,然后通过 http 调用服务器以更新数据库。这是通过 alerts.service.ts 在 users-endpoint.service.ts 中完成的。

我不知道是否需要所有这些信息。也许这个问题有一个简单的解决方案,但我想我不妨提供尽可能多的信息。

我不知道如何找到解决此问题的方法,因为我对可能导致此问题的原因一无所知。

警报-page.component.ts


@Component({
    selector: 'alerts-page',
    templateUrl: './alerts-page.component.html',
    styleUrls: ['./alerts-page.component.scss'],
})
export class AlertsPageComponent implements OnInit {

    alertMessages$: Observable<AlertMessage[]>;
    alertsCreatedToday$: Observable<Alert[]>;
    alertsCreatedAWeekAgo$: Observable<Alert[]>

    alertMessagesFromServer: AlertMessage[];
    alertMessagesFromClient: AlertMessage[];
    alertRecipients: AlertRecipient[];
    currentUser: User = new User();
    groups: Group[] = [];
    users: User[] = [];
    newMessages: AlertMessage[];

    alertMessages: AlertMessage[];

    constructor(private alertMessagesService: AlertMessagesService,
        private alertsService: AlertsService,
        private notificationMessagesService: NotificationMessagesService,
        private dialog: MatDialog,
        private usersService: UsersService,
        private groupService: GroupsService) { }

    ngOnInit() {
        this.loadData();
        this.initializeObservables();
    }

    private initializeObservables() {
        this.alertMessages$ = this.alertMessagesService.messages;
        this.alertsCreatedToday$ = this.alertMessagesService.alertsCreatedToday;
        this.alertsCreatedAWeekAgo$ = this.alertMessagesService.alertsCreatedAWeekAgo;
    }


    private loadData() {

        this.currentUser = this.usersService.currentUser;

        forkJoin(
            this.alertsService.getAlertMessagesForUser(this.currentUser.id),
            this.groupService.getGroups(),
            this.usersService.getUsers()
        ).subscribe(
            result => this.onDataLoadSuccessful(result[0], result[1], result[2]),
            error => this.onDataLoadFailed(error)
        );
    }

    private onDataLoadSuccessful(alertMessagesFromServer: AlertMessage[], groups: Group[], users: User[]) {
        this.alertMessagesFromServer = alertMessagesFromServer;
        this.groups = groups;
        this.users = users;

        this.alertMessagesService.messages.subscribe(
            (alertMessagesFromClient: AlertMessage[]) => this.alertMessagesFromClient = alertMessagesFromClient
        );

        if (this.newMessagesFromServer()) {
            this.newMessages = _.differenceBy(this.alertMessagesFromServer, this.alertMessagesFromClient, 'id');
            this.newMessages.map((message: AlertMessage) => this.alertMessagesService.addMessage(message));
        }


    }

    private onDataLoadFailed(error: any): void {

        this.notificationMessagesService.showStickyMessage('Load Error', `Unable to retrieve alerts from the server.\r\nErrors: "${Utilities.getHttpResponseMessage(error)}"`,
            MessageSeverity.error, error);
    }

    private newMessagesFromServer(): boolean {
        if (this.alertMessagesFromClient == null && this.alertMessagesFromServer != null) {
            return true;
        } else if (this.alertMessagesFromServer.length > this.alertMessagesFromClient.length) {
            return true;
        } else {
            return false;
        }
    }

    markAllAsRead() {
        this.alertsService.markAlertsAsRead(this.currentUser.id).subscribe(
            (alertRecipients: AlertRecipient[]) => {

                alertRecipients.map((alertRecipient: AlertRecipient) =>
                    this.alertMessagesService.markRead(alertRecipient));
            }

        );
    }

}

警报-page.component.html

<button (click)="markAllAsRead()">Mark all as read</button>

    <h2>Today</h2>
        <ng-container *ngFor="let alert of alertsCreatedToday$ | async">
            <alert [alertRecipient]="alert.alertRecipient"[alertMessage]="alert.alertMessage">
            </alert>
        </ng-container>


    <h2>Last Week</h2>
        <ng-container *ngFor="let alert of alertsCreatedAWeekAgo$ | async">
            <alert [alertRecipient]="alert.alertRecipient"[alertMessage]="alert.alertMessage">
            </alert>
        </ng-container>

alert.component.ts


@Component({
    selector: 'alert',
    templateUrl: './alert.component.html',
    styleUrls: ['./alert.component.scss'],
})
export class AlertComponent implements OnInit {
    @Input() alertRecipient: AlertRecipient;
    @Input() alertMessage: AlertMessage;
    currentUser: User = new User();

    constructor(private dialog: MatDialog,
        private alertsService: AlertsService,
        private usersService: UsersService,
        private alertMessagesService: AlertMessagesService) {
    }

    ngOnInit() {
        this.currentUser = this.usersService.currentUser;
    }

    getAvatarForAlert(alertMessage: AlertMessage): string {
        return require('../../../assets/images/Avatars/' + 'default-avatar.png');
    }


    toggleIsRead(alertRecipient: AlertRecipient) {
        this.alertRecipient.isRead = !alertRecipient.isRead;
        this.alertsService.toggleIsRead(alertRecipient)
            .subscribe(alertRecipient => {
                this.alertMessagesService.toggleRead(alertRecipient);
            }, error => {
                this.notificationMessagesService.showStickyMessage('Update Error', `An error occured while attempting to mark the alert-message as read.`, MessageSeverity.error, error);
                this.alertRecipient.isRead = !alertRecipient.isRead;
            });
    }
}

alert.component.html

<mat-card>
    <mat-card-header>
        <div [ngSwitch]="alertRecipient.isRead" (click)="toggleIsRead(alertRecipient)">
            <mat-icon *ngSwitchCase="true">drafts</mat-icon>
            <mat-icon *ngSwitchCase="false">markunread</mat-icon>
        </div>
    </mat-card-header>

    <mat-card-content>
        <div class="avatar-wrapper" fxFlex="25">
            <img [src]="getAvatarForAlert(alertMessage)" alt="User Avatar">
        </div>

            <h3>{{alertMessage.title}}</h3>
            <p>{{alertMessage.body}}</p>
    </mat-card-content>

    <mat-card-actions>
        <button>DELETE</button>
        <button>DETAILS</button>
    </mat-card-actions>
</mat-card>

警报消息.service.ts

const initialMessages: AlertMessage[] = [];

interface IMessagesOperation extends Function {
    // tslint:disable-next-line:callable-types
    (messages: AlertMessage[]): AlertMessage[];
}

@Injectable()
export class AlertMessagesService {

    _hubConnection: HubConnection;

    newMessages: Subject<AlertMessage> = new Subject<AlertMessage>();
    messages: Observable<AlertMessage[]>;
    updates: Subject<any> = new Subject<any>();
    create: Subject<AlertMessage> = new Subject<AlertMessage>();
    markRecipientAsRead: Subject<any> = new Subject<any>();
    toggleReadForRecipient: Subject<any> = new Subject<any>();

    alertsCreatedToday: Observable<Alert[]>;
    alertsCreatedAWeekAgo: Observable<Alert[]>;

    constructor() {
        this.initializeStreams();
    }

    initializeStreams() {
        this.messages = this.updates.pipe(
            scan((messages: AlertMessage[],
                operation: IMessagesOperation) => {
                return operation(messages);
            }, initialMessages),
            map((messages: AlertMessage[]) => messages.sort((m1: AlertMessage, m2: AlertMessage) => m1.sentAt > m2.sentAt ? -1 : 1)),
            publishReplay(1),
            refCount()
        );

        this.create.pipe(map(function (message: AlertMessage): IMessagesOperation {
            return (messages: AlertMessage[]) => {
                return messages.concat(message);
            };
        }))
            .subscribe(this.updates);

        this.newMessages
            .subscribe(this.create);


        this.markRecipientAsDeleted.pipe(
            map((recipient: AlertRecipient) => {
                return (messages: AlertMessage[]) => {
                    return messages.map((message: AlertMessage) => {
                        message.recipients.map((alertRecipient: AlertRecipient) => {
                            if (alertRecipient.recipientId === recipient.recipientId
                                && alertRecipient.alertId === recipient.alertId) {
                                alertRecipient.isDeleted = recipient.isDeleted;
                            }
                        });
                        return message;
                    });
                };
            })
        ).subscribe(this.updates);

        this.markRecipientAsRead.pipe(
            map((recipient: AlertRecipient) => {
                return (messages: AlertMessage[]) => {
                    return messages.map((message: AlertMessage) => {
                        message.recipients.map((alertRecipient: AlertRecipient) => {
                            if (alertRecipient.recipientId === recipient.recipientId
                                && alertRecipient.alertId === recipient.alertId) {
                                alertRecipient.isRead = true;
                            }
                        });
                        return message;
                    });
                };
            })
        ).subscribe(this.updates);

        this.toggleReadForRecipient.pipe(
            map((recipient: AlertRecipient) => {
                return (messages: AlertMessage[]) => {
                    return messages.map((message: AlertMessage) => {
                        message.recipients.map((alertRecipient: AlertRecipient) => {
                            if (alertRecipient.recipientId === recipient.recipientId
                                && alertRecipient.alertId === recipient.alertId) {
                                alertRecipient.isRead = recipient.isRead;
                            }
                        });
                        return message;
                    });
                };
            })
        ).subscribe(this.updates);

        this.alertsCreatedToday = this.messages.pipe(
            map((alertMessages: AlertMessage[]) => {
                const alerts: Alert[] = [];
                alertMessages.map((alertMessage: AlertMessage) => {
                    alertMessage.recipients.map((alertRecipient: AlertRecipient) => {
                        if (this.wasCreatedToday(alertMessage)) {
                            const alert = new Alert(alertRecipient, alertMessage);
                            alerts.push(alert);
                        }
                    });
                });
                return alerts;
            })
        );

        this.alertsCreatedAWeekAgo = this.messages.pipe(
            map((alertMessages: AlertMessage[]) => {
                const alerts: Alert[] = [];
                alertMessages.map((alertMessage: AlertMessage) => {
                    alertMessage.recipients.map((alertRecipient: AlertRecipient) => {
                        if (this.wasCreatedBetweenTodayAndAWeekAgo(alertMessage)) {
                            const alert = new Alert(alertRecipient, alertMessage);
                            alerts.push(alert);
                        }
                    });
                });
                return alerts;
            })
        );

    }


    addMessage(message: AlertMessage): void {
        this.newMessages.next(message);
    }


    toggleRead(alertRecipient: AlertRecipient): void {
        this.toggleReadForRecipient.next(alertRecipient);
    }

    markRead(recipient: AlertRecipient): void {
        this.markRecipientAsRead.next(recipient);
    }

    wasCreatedToday(alertMessage: AlertMessage): boolean {
        const today = moment();
        const alertSentAt = moment(alertMessage.sentAt);

        return moment(alertSentAt).isSame(today, 'day');

    }

    wasCreatedBetweenTodayAndAWeekAgo(alertMessage: AlertMessage): boolean {
        const today = moment();
        const alertSentAt = moment(alertMessage.sentAt);
        const oneWeekAgo = moment(moment().subtract(7, 'days'));

        return moment(alertSentAt).isBetween(oneWeekAgo, today, 'day');
    }

}

export const alertMessagesServiceInjectables: Array<any> = [
    AlertMessagesService
];

alerts.service.ts


@Injectable()
export class AlertsService {

    constructor(private usersEndpoint: UsersEndpoint) { }

    getAlertMessagesForUser(userId: string): Observable<AlertMessage[]> {
        return this.usersEndpoint.getAlertMessagesForUserEndpoint<AlertMessage[]>(userId);
    }

    markAlertsAsRead(userId: string) {
        return this.usersEndpoint.getMarkAlertsAsReadEndpoint<AlertRecipient[]>(userId);
    }

    toggleIsRead(alertRecipient: AlertRecipient) {
        return this.usersEndpoint.getToggleIsReadEndpoint<AlertRecipient>(alertRecipient);
    }

}

用户端点.service.ts

@Injectable()
export class UsersEndpoint extends EndpointFactory {

    private readonly _usersUrl: string = '/api/users';

    get usersUrl() { return this.configurations.baseUrl + this._usersUrl; }

    constructor(http: HttpClient, configurations: ConfigurationService, injector: Injector) {

        super(http, configurations, injector);
    }

    getAlertMessagesForUserEndpoint<T>(userId: string): Observable<T> {
        const endpointUrl = `${this.usersUrl}/${userId}/alertmessages`;

        return this.http.get<T>(endpointUrl, this.getRequestHeaders()).pipe<T>(
            catchError(error => {
                return this.handleError('Unable to get alert-messages for user with id: ' + userId, error, () => this.getAlertMessagesForUserEndpoint(userId));
            }));
    }

    getMarkAlertsAsReadEndpoint<T>(userId: string): Observable<T> {
        const endpointUrl = `${this.usersUrl}/${userId}/alertmessages/markallread`;

        return this.http.put<T>(endpointUrl, null, this.getRequestHeaders()).pipe<T>(
            catchError(error => {
                return this.handleError('Unable to mark alertmessages as read for user with id: ' + userId, error, () => this.getMarkAlertsAsReadEndpoint(userId));
            }));
    }

    getToggleIsReadEndpoint<T>(alertRecipient: AlertRecipient): Observable<T> {
        const endpointUrl = `${this.usersUrl}/${alertRecipient.recipientId}/alertmessages/${alertRecipient.alertId}/toggleread`;

        return this.http.patch<T>(endpointUrl, JSON.stringify(alertRecipient), this.getRequestHeaders()).pipe<T>(
            catchError(error => {
                return this.handleError('Unable to toggle isRead-status for alert-message to user with id: ' + alertRecipient.recipientId, error, () => this.getToggleIsReadEndpoint(alertRecipient));
            }));
    }

    getMarkAlertRecipientAsDeletedEndpoint<T>(alertRecipient: AlertRecipient): Observable<T> {
        const endpointUrl = `${this.usersUrl}/${alertRecipient.recipientId}/alertmessages/${alertRecipient.alertId}/markdeleted`;

        return this.http.patch<T>(endpointUrl, JSON.stringify(alertRecipient), this.getRequestHeaders()).pipe<T>(
            catchError(error => {
                return this.handleError('Unable to mark alert-message as deleted', error, () => this.getMarkAlertRecipientAsDeletedEndpoint(alertRecipient));
            }));
    }

}

标签: angularhttprxjsangular-material

解决方案


我认为它正在发生,因为您的消息被重新呈现是因为每次发出新值时alertsCreatedToday$,可观察对象都会发出新值。您希望刷新警报组件上的数据,而不是重新渲染。为此,您应该使用. 让我们像这样更改代码 -alertsCreatedAWeekAgo$AlertMessagesService.update SubjecttrackBy

今天

    <ng-container *ngFor="let alert of alertsCreatedToday$ | async; trackBy: trackByFn1">
        <alert [alertRecipient]="alert.alertRecipient"[alertMessage]="alert.alertMessage">
        </alert>
    </ng-container>


<h2>Last Week</h2>
    <ng-container *ngFor="let alert of alertsCreatedAWeekAgo$ | async; trackBy: trackByFn2">
        <alert [alertRecipient]="alert.alertRecipient"[alertMessage]="alert.alertMessage">
        </alert>
    </ng-container>

在你export class AlertsPageComponent有两个这样的功能 -

trackByFn1(item, index) {
  //return the id of your alert
  return item.alertId;
}

trackByFn2(item, index) {
  //return the id of your alert
  return item.alertId;
}

有关更多详细信息,请参阅 - https://netbasal.com/angular-2-improve-performance-with-trackby-cc147b5104e5


推荐阅读