Видео: https://vimeo.com/341988489
У меня есть страница предупреждений, которая содержит несколько предупреждений.Каждое предупреждение имеет логическое значение «isRead», которое указывает, прочитано или нет предупреждение.Оранжевый конверт означает, что предупреждение не прочитано.При щелчке по оранжевому конверту он становится серым, и на сервер отправляется http-запрос на обновление этого логического значения с ложного на истинное.
В этом случае в приложении возникает несколько проблем: 1.Аватар изображения, который отображается в предупреждении, «мерцает», и страница прокручивается вверх.2. Пользовательский интерфейс зависает на несколько секунд.
Это происходит, когда HTTP-запрос выполняется на сервере в компоненте alert-page-page.Как при щелчке по компоненту alert «envelope» и при вызове toggleIsRead, так и при нажатии «mark all as read», как показано на видео.
Я не знаю, что является причиной этой проблемы.Но я показал вкладку сети в инструментах разработчика, если это может дать какие-либо подсказки.
В alerts-page.component.ts я получаю оповещения для текущего пользователя с сервера и инициализирую Observable с именемсообщения в alert-messages.service.ts.Это затем используется для заполнения alertsCreatedToday и alertsCreatedAWeekAgo.Этим наблюдаемым затем устанавливаются значения alertsCreatedToday $ и alertsCreatedAWeekAgo $.
Каждое предупреждение затем заполняется данными из этих наблюдаемых.Матричная карта отображает информацию для каждого предупреждения.
Когда щелкается конверт в предупреждении, чтобы переключить логическое значение «IsRead» - это логическое значение использует оптимистический подход обновления, чтобы сначала изменить логическое значение на «alertRecipient-model ", а затем вызывает сервер через http для обновления базы данных.Это делается в users-endpoint.service.ts через alerts.service.ts.
Я не знаю, нужна ли вся эта информация.Возможно, есть простое решение проблемы, но я подумал, что мог бы также предоставить как можно больше информации.
Я не знаю, как найти решение этой проблемы, потому что у меня нет никакогоподсказка о том, что может быть причиной.
alerts-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));
}
);
}
}
alerts-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>
alert-messages.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);
}
}
users-endpoint.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));
}));
}
}