我的角度组件性能下降。有什么我可以更改以提高性能的东西吗?

时间:2019-06-13 15:24:24

标签: angular performance

我的一个组件遇到性能问题。这是显示这些性能问题的视频的链接:https://vimeo.com/342044142

在视频中,我注意到从选择显示已删除/已读警报到实际显示之间有几秒钟的延迟。用户界面冻结了几秒钟。

使用搜索框搜索警报时,我还会遇到性能问题。当我输入搜索条件时,与其说是当我删除当前的搜索条件,以便显示所有警报,不如说是。在视频中,我以恒定的速度擦除,但是当擦除最后1-2个字母时,出现冻结。

我正在使用自定义管道进行搜索,如alerts-filter.pipe.ts所示。

对于要呈现的每组警报,我正在遍历一个包含ObservableCreatedToday,alertsCreatedAWeekAgo,alertsCreatedAMonthAgo和AlertsCreatedMoreThanAMonthAgo的Observable,并使用alerts-filter-pipe来呈现与搜索条件匹配的结果。

我的模板中包含很多逻辑。我不知道这是导致上述问题的原因还是其他原因。我是Angular的新手,我实现此组件的方法是我能够弄清楚如何实现所需逻辑的唯一方法。警报应基于某些参数来呈现,如方法“ parametersAreValid()”中所示。如果存在满足参数的警报,则标题也应呈现,以便在标题下方没有任何警报的情况下不会出现标题。

如果有人能为我指出导致这些问题的原因以及为改进此组件的性能而可以进行哪些更改的正确方向,我将不胜感激。

alerts-page.component.html

<div class="page-content">

    <div class="top" fxLayout.xs="column" fxLayout.gt-xs="row">
        <div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="start center" fxLayoutAlign.xs="start start" fxFlex="50">
            <app-page-header title="Alerts" icon="notification_important" class="mr-4"></app-page-header>
            <button  *ngIf="!loadingIndicator" 
                  (click)="showCreateDialog(groups, users)">
                Create new alert...
            </button>
        </div>
        <div *ngIf="!loadingIndicator">
            <mat-form-field class="mr-3">
                <input [(ngModel)]="searchTerm" matInput>
            </mat-form-field>
        </div>
    </div>



<mat-selection-list #alertSettings>
  <mat-list-option
    [selected]="showDeleted" (click)="showDeletedClicked()">Show deleted
  </mat-list-option>
  <mat-list-option 
    [selected]="showRead" (click)="showReadClicked()">Show read
  </mat-list-option>
</mat-selection-list>

<h2 *ngIf="(!loadingIndicator) && (headingShouldBeRendered(alertsCreatedToday$ | async) > 0)">Today</h2>
<ng-container *ngFor="let alert of alertsCreatedToday$ | async | 
                alertsFilter:searchTerm; trackBy: trackByFn1">
  <alert *ngIf="parametersAreValid(alert.alertRecipient) && !loadingIndicator"
         [alertRecipient]="alert.alertRecipient" 
         [alertMessage]="alert.alertMessage">
  </alert>
</ng-container>

    <h2 *ngIf="(!loadingIndicator) && (headingShouldBeRendered(alertsCreatedAWeekAgo$ | async) > 0)">Last Week</h2>
    <ng-container *ngFor="let alert of alertsCreatedAWeekAgo$ | async | 
                alertsFilter:searchTerm; trackBy: trackByFn2">
  <alert *ngIf="parametersAreValid(alert.alertRecipient) && !loadingIndicator"
         [alertRecipient]="alert.alertRecipient" 
         [alertMessage]="alert.alertMessage">
  </alert>
</ng-container>


    <h2 *ngIf="(!loadingIndicator) && (headingShouldBeRendered(alertsCreatedAMonthAgo$ | async) > 0)">Last Month</h2>
       <ng-container *ngFor="let alert of alertsCreatedAMonthAgo$ | async | 
                alertsFilter:searchTerm; trackBy: trackByFn3">
  <alert *ngIf="parametersAreValid(alert.alertRecipient) && !loadingIndicator"
         [alertRecipient]="alert.alertRecipient" 
         [alertMessage]="alert.alertMessage">
  </alert>
</ng-container>

        <h2 *ngIf="(!loadingIndicator) && (headingShouldBeRendered(alertsCreatedMoreThanAMonthAgo$ | async) > 0)">Earlier</h2>
    <ng-container *ngFor="let alert of alertsCreatedMoreThanAMonthAgo$ | async | 
                alertsFilter:searchTerm; trackBy: trackByFn4">
  <alert *ngIf="parametersAreValid(alert.alertRecipient) && !loadingIndicator"
         [alertRecipient]="alert.alertRecipient" 
         [alertMessage]="alert.alertMessage">
  </alert>
</ng-container>

</div>

alerts-page.component.ts

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

    @ViewChild('alertSettings') alertSettings: MatSelectionList;

    loadingIndicator: boolean;

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

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

    searchTerm: string;

    showDeleted = false;
    showRead = true;
    alertMessages: AlertMessage[];

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

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

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

    private loadData() {
        this.notificationMessagesService.startLoadingMessage();
        this.loadingIndicator = true;

        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.notificationMessagesService.stopLoadingMessage();
        this.loadingIndicator = false;
        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.stopLoadingMessage();
        this.loadingIndicator = false;

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

    getAlertMessageForRecipient(alertRecipient: AlertRecipient): AlertMessage {
        for (const alert of this.alertMessagesFromServer) {
            if (alert.id === alertRecipient.alertId) {
                return alert;
            }
        }
    }

    parametersAreValid(alertRecipient: AlertRecipient): boolean {
        const isForCurrentUser = alertRecipient.recipientId === this.currentUser.id;
        const isNotMarkedAsDeleted = !alertRecipient.isDeleted;
        const isNotMarkedAsRead = !alertRecipient.isRead;
        const isShownWhenShowDeletedIsSetToTrue = (alertRecipient.isDeleted && this.showDeleted);
        const isShownWhenShowReadIsSetToTrue = (alertRecipient.isRead && this.showRead);

        return (isForCurrentUser) && (isShownWhenShowDeletedIsSetToTrue || isNotMarkedAsDeleted) &&
            (isNotMarkedAsRead || isShownWhenShowReadIsSetToTrue);
    }

    headingShouldBeRendered(alerts: Alert[]): number {
        let count = 0;
        if (alerts) {
            for (const alert of alerts) {
                if (this.parametersAreValid(alert.alertRecipient)) {
                    count++;
                }
            }
        }
        return count;
    }

    showDeletedClicked() {
        this.showDeleted = this.alertSettings.options.first.selected;
    }

    showReadClicked() {
        this.showRead = this.alertSettings.options.last.selected;
    }

    trackByFn1(item, index) {
        return item.alertId;
    }
    trackByFn2(item, index) {
        return item.alertId;
    }
    trackByFn3(item, index) {
        return item.alertId;
    }
    trackByFn4(item, index) {
        return item.alertId;
    }

}

alerts-filter.pipe.ts

@Pipe( {
    name: 'alertsFilter'
})
export class AlertsFilterPipe implements PipeTransform {
    transform(alerts: Alert[], searchTerm: string): Alert[] {
        if (!alerts || !searchTerm) {
            return alerts;
        }
        return alerts.filter(alert =>
            alert.alertMessage.author.fullName.toLocaleLowerCase().indexOf(searchTerm.toLowerCase()) !== -1);
    }
}

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>

2 个答案:

答案 0 :(得分:0)

不能发表评论,但我建议替换* ngIf,如果可以的话,您应指向函数以指向变量或指向pipes。 Angular的更改检测无法缓存功能的结果,因此它们会在每个循环中重新运行,这会非常快地累加起来。

根据“今天”部分,这大致就是您需要执行的操作。我建议您尝试一下如何在功能中添加一些控制台日志,并将其与此进行比较。创建两个管道:

@Pipe({ name: 'alertCheckingPipe' })
export class AlertCheckingPipe implements PipeTransform {
    transform(alertRecipient: any, options: any): boolean {
        // logging to illustrate how little pipe logic gets called
        console.log('alertCheckingPipe');
        // replace with your more specific parameter validation logic
        return alertRecipient.recipientId === options.currentUser.id;
    }
}

然后处理一个数组,该数组委派回第一个

@Pipe({ name: 'alertArrayCheckingPipe' })
export class AlertArrayCheckingPipe implements PipeTransform {
    alertCheckingPipe = new AlertCheckingPipe();
    transform(value: any[], options: any): boolean {
        console.log('alertArrayCheckingPipe');
        return Array.isArray(value) && value.some(arrayElement => this.alertCheckingPipe.transform(arrayElement, options));
    }
}

在模块中声明它们,然后将html更新为:

<div *ngIf="!loadingIndicator && alertsCreatedToday$ | async as results">
    <h2 *ngIf="results | alertArrayCheckingPipe:options">
        TODAY
    </h2>
    <ng-container *ngFor="let alert of results">
        <div *ngIf="alert | alertCheckingPipe:options">
            {{ alert.recipientId }}
        </div>
    </ng-container>
</div>

这里还有一些重组,我只使用了异步管道一次,然后将结果存储在“结果”变量中,然后在内部元素中使用。

这是我用于测试的支持数据对象:

this.alertsCreatedToday$ = of([{ recipientId: 'testId', isDeleted: false, isRead: true }, //
        { recipientId: 'badId', isDeleted: false, isRead: true }]).pipe(delay(2000));

this.options = { currentUser: { id: 'testId' }, showDeleted: true, showRead: true };

答案 1 :(得分:0)

不幸的是,在实施Freddys解决方案之后,性能没有任何改善。

使用管道的解决方案如下:

alert-array-checking.pipe.ts

    @Pipe({
  name: 'alertArrayChecking'
})
export class AlertArrayCheckingPipe implements PipeTransform {

    alertCheckingPipe = new AlertCheckingPipe();
    transform(value: any[], options: any): boolean {
        return Array.isArray(value) && value.some(arrayElement => this.alertCheckingPipe.transform(arrayElement, options));
    }

}

alert-checking.pipe.ts

@Pipe({
  name: 'alertChecking'
})
export class AlertCheckingPipe implements PipeTransform {

    transform(alert: any, options: any): boolean {
        const isForCurrentUser = alert.alertRecipient.recipientId === options.currentUserId;
        const isNotMarkedAsDeleted = !alert.alertRecipient.isDeleted;
        const isNotMarkedAsRead = !alert.alertRecipient.isRead;
        const isShownWhenShowDeletedIsSetToTrue = (alert.alertRecipient.isDeleted && options.showDeleted);
        const isShownWhenShowReadIsSetToTrue = (alert.alertRecipient.isRead && options.showRead);

        const alertShouldBeRendered = (isForCurrentUser) && (isShownWhenShowDeletedIsSetToTrue || isNotMarkedAsDeleted) &&
        (isNotMarkedAsRead || isShownWhenShowReadIsSetToTrue);
        return alertShouldBeRendered;
    }

}

alerts-page.component.html

    <ng-container *ngIf="!loadingIndicator && alertsCreatedToday$ | async as alertsCreatedToday">
    <h2 [@fadeInOut] *ngIf="alertsCreatedToday | alertArrayChecking:options">Today</h2>
    <div>
        <ng-container *ngFor="let alert of alertsCreatedToday">
            <alert *ngIf="alert | alertChecking:options"
                [alertRecipient]="alert.alertRecipient" 
                [alertMessage]="alert.alertMessage">
            </alert>
        </ng-container>
    </div>
</ng-container>