MatSort打破MatTable详细信息行动画 [英] MatSort breaks MatTable detail row animations

查看:69
本文介绍了MatSort打破MatTable详细信息行动画的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我来到这里之前,我已经为解决这个问题而奋斗了很长时间.本质上,我有一个Angular Material表,该表使用动画创建明细行.当表格排序时,它会重新排列数据.在此过程中,一些明细行会过渡到空白.之后,即使触发了动画事件,详细信息行也将停止播放动画.我怀疑MatSort会以某种方式破坏动画,但是我不确定如何.

I've been beating my head against this problem for quite a while before I got here. Essentially, I have an Angular Material table that uses animations to create a detail row. When the table sorts, it rearranges the data. Some of the detail rows have a transition to void during that process. Afterwards, the detail rows stop playing the animation, even though the animation events are firing. I suspect that MatSort is breaking the animations somehow, but I'm not sure how.

角度材料表:

<mat-table matSort
        [dataSource]="tableData"
        multiTemplateDataRows>

        <!-- More Column -->
        <ng-container matColumnDef="more">
            <mat-header-cell *matHeaderCellDef 
                translate>
                More
            </mat-header-cell>
            <mat-cell *matCellDef="let scheduleCourse">
                <p class="fa fa-angle-right" *ngIf="!tableData.checkExpanded(scheduleCourse)"></p>
                <p class="fa fa-angle-down" *ngIf="tableData.checkExpanded(scheduleCourse)"></p>
            </mat-cell>
        </ng-container>

        <!-- Meets Column -->
        <ng-container matColumnDef="meets">
            <mat-header-cell *matHeaderCellDef
                mat-sort-header="Meets" 
                translate>
                Meets
                <filter [data]="tableData" columnName="Meets" dataType="string"></filter>
            </mat-header-cell>
            <mat-cell *matCellDef="let scheduleCourse">
                {{scheduleCourse.Meets}}
            </mat-cell>
        </ng-container>

        <!-- Term Column -->
        <ng-container matColumnDef="term">
            <mat-header-cell *matHeaderCellDef 
                mat-sort-header="Term"
                translate>
                Term
                <filter [data]="tableData" columnName="Term" dataType="string"></filter>
            </mat-header-cell>
            <mat-cell *matCellDef="let scheduleCourse">
                {{scheduleCourse.Term}}
            </mat-cell>
        </ng-container>

        <!-- Course Name Column -->
        <ng-container matColumnDef="course">
            <mat-header-cell *matHeaderCellDef 
                mat-sort-header="Course"
                translate>
                Course Name
                <filter [data]="tableData" columnName="Course" dataType="string"></filter>
            </mat-header-cell>
            <mat-cell *matCellDef="let scheduleCourse">
                {{scheduleCourse.Course}}
            </mat-cell>
        </ng-container>

        <!-- Teacher Column -->
        <ng-container matColumnDef="teacher">
            <mat-header-cell *matHeaderCellDef 
                mat-sort-header="Teacher"
                translate>
                Teacher
                <filter [data]="tableData" columnName="Teacher" dataType="string"></filter>
            </mat-header-cell>
            <mat-cell *matCellDef="let scheduleCourse">
                {{scheduleCourse.Teacher}}
            </mat-cell>
        </ng-container>

        <!-- Room Column -->
        <ng-container matColumnDef="room">
            <mat-header-cell *matHeaderCellDef 
                mat-sort-header="Room"
                translate>
                Room
                <filter [data]="tableData" columnName="Room" dataType="string"></filter>
            </mat-header-cell>
            <mat-cell *matCellDef="let scheduleCourse">
                {{scheduleCourse.Room}}
            </mat-cell>
        </ng-container>

        <!-- Entry Date Column -->
        <ng-container matColumnDef="entry date">
            <mat-header-cell *matHeaderCellDef 
                mat-sort-header="EntryDate"
                translate>
                Entry Date
                <filter [data]="tableData" columnName="EntryDate" dataType="date"></filter>
            </mat-header-cell>
            <mat-cell *matCellDef="let scheduleCourse">
                {{scheduleCourse.EntryDate.toString() != junkDate.toString() ? scheduleCourse.EntryDate.toLocaleDateString() : ''}}
            </mat-cell>
        </ng-container>

        <!-- Dropped Date Column -->
        <ng-container matColumnDef="dropped date">
            <mat-header-cell *matHeaderCellDef 
                mat-sort-header="DroppedDate"
                translate>
                Dropped Date
                <filter [data]="tableData" columnName="DroppedDate" dataType="date"></filter>
            </mat-header-cell>
            <mat-cell *matCellDef="let scheduleCourse">
                {{scheduleCourse.DroppedDate.toString() != junkDate.toString() ? scheduleCourse.DroppedDate.toLocaleDateString() : ''}}
            </mat-cell>
        </ng-container>

        <!-- Team Column -->
        <ng-container matColumnDef="team">
            <mat-header-cell *matHeaderCellDef 
                mat-sort-header="TeamCode"
                translate>
                Team
                <filter [data]="tableData" columnName="TeamCode" dataType="string"></filter>
            </mat-header-cell>
            <mat-cell *matCellDef="let scheduleCourse">
                {{scheduleCourse.TeamCode}}
            </mat-cell>
        </ng-container>

        <!-- Expand Row 1 -->
        <ng-container matColumnDef="expandedRow">
            <td mat-cell
                *matCellDef="let scheduleCourse"
                [attr.colspan]="columns.length"
                style="width: 100%">

                <!-- Links and Actions -->
                <div class="detailRow">
                    <div class="detailItem">
                        <label style="color: #595959" translate>Course-Section</label>
                        &nbsp;
                        {{scheduleCourse.SubjectCode}}-{{scheduleCourse.Section}}
                    </div>
                    <a class="detailItem"
                        (click)="assignmentClick(scheduleCourse)"
                        translate>
                        Assignments
                    </a>
                    <a class="detailItem"
                        (click)="attendanceClick(scheduleCourse)"
                        translate>
                        Attendance
                    </a>
                    <a class="detailItem"
                        (click)="emailTeacherClick(scheduleCourse)"
                        translate>
                        Email Teacher
                    </a>
                    <a class="detailItem"   
                        (click)="gradesClick(scheduleCourse)"
                        translate>
                        Grades
                    </a>

                    <!-- Menu Button -->
                    <button class="detailItem"
                        *ngIf="showProfiles"
                        style="cursor: pointer; border: none; background-color: inherit;" 
                        [matMenuTriggerFor]="actionMenu"
                        [matMenuTriggerData]="{'scheduleCourse': scheduleCourse}">
                        <img src="./assets/images/actions.png"
                            alt="actions">
                    </button>
                </div>

                <!-- School Indicator -->
                <div *ngIf="showSchool(scheduleCourse)" 
                    class="detailRow">
                    <div class="detailItem">
                        <label style="color: #595959" translate>
                            School
                        </label>
                        &nbsp;
                        {{scheduleCourse.SchoolName}}
                    </div>
                </div>
            </td>
        </ng-container>

        <!-- Row definitions -->
        <mat-header-row *matHeaderRowDef="columns"></mat-header-row>
        <mat-row *matRowDef="let row; columns: columns;"
            matRipple 
            tabindex="0" 
            style="cursor: pointer"
            [ngStyle]="{'background-color': selectedRow == row ? 'whitesmoke' : ''}"
            [ngClass]="{'detailRowOpened': tableData.checkExpanded(row)}"
            (click)="tableData.toggleExpanded(row); selectedRow = row;"></mat-row>
        <mat-row *matRowDef="let row; columns: ['expandedRow']" 
            matRipple
            (click)="selectedRow = row;"
            [ngClass]="{'selectedRow': selectedRow == row}"
            (@detailExpand.done)="animation($event)"
            [@detailExpand]="tableData.checkExpanded(row) ? 'expanded' : 'collapsed'"
            style="overflow: hidden"></mat-row>
    </mat-table>

detailExpand动画:

The detailExpand animation:

export const detailExpand = [
    trigger('detailExpand', [
        state('collapsed', style({
            paddingTop: '0px',
            height: '0px',
            minHeight: '0',
            paddingBottom: '0px'
        })),
        state('expanded', style({
            paddingTop: '*',
            height: 'auto',
            paddingBottom: '25px'
        })),
        transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)'))
      ])
];

我的组件,如果需要的话:

My component, in case you need it:

@Component({
    selector: 'student-schedule',
    templateUrl: './student-schedule.component.html',
    styleUrls: [
        './student-schedule.component.css'
    ],
    animations: [
        detailExpand
    ]
})
export class StudentScheduleComponent implements OnInit, DoCheck, OnDestroy {

    // Properties
    private _viewOption = 1;
    private _includeDropped = false;
    schedule: ScheduleCourse[] = [];
    subscriptions: Subscription[] = [];
    tableData = new TylerMatTableDataSource();
    junkDate = System.junkDate;
    V10: boolean;
    columns = ['more', 'meets', 'term', 'course', 'teacher', 'room', 'entry date', 'dropped date', 'team'];
    selectedRow: ScheduleCourse;
    expandEmitter = new EventEmitter<boolean>();
    tableHeight: number;
    minTableWidth: number;
    @ViewChild('tableContainer', {read: ElementRef}) tableContainer: ElementRef;
    showProfiles: boolean;
    studentEnrollment: Enrollment;
    _sort: MatSort;

    // Class Functions
    constructor(
        private studentScheduleService: StudentScheduleService,
        private loginService: LoginService,
        private router: Router,
        private dialog: MatDialog,
        private studentService: StudentService,
        private sendEmailService: SendEmailService
    ) { }

    get viewOption(): number {
        return this._viewOption;
    }

    set viewOption(value: number) {
        this._viewOption = value;
        this.getSchedule();
    }

    get includeDropped(): boolean {
        return this._includeDropped;
    }

    set includeDropped(value: boolean) {
        this._includeDropped = value;
        this.checkColumns();
    }

    @ViewChild(MatSort) set sort(value: MatSort) {
        this._sort = value;
        this.tableData.sort = this._sort;
    }

    get sort(): MatSort {
        return this._sort;
    }

    // Event Functions
    ngOnInit() {
        // POST: initializes the data
        this.V10 = this.loginService.LoginSettings.V10;
        this.showProfiles = this.loginService.LoginSettings.ParentPortalCourseScheduleProfiles;
        this.checkColumns();
        this.subscriptions.push(
            this.expandEmitter.subscribe(expand => {
                this.tableData.expandAll(expand);
            }),
            this.studentService.selectedStudentStream$.subscribe(() => {
                this.studentEnrollment = this.studentService.studentEnrollment;
                this.getSchedule();
            })
        );
    }

    ngDoCheck() {
        // POST: determines the height and width of the table container
        if (this.tableContainer) {
            this.tableHeight = System.getTableHeight(this.tableContainer);
        }
    }

    ngOnDestroy() {
        // POST: unsubscribes to all observables
        this.subscriptions.forEach(subscription => {
            subscription.unsubscribe();
        });
    }

    assignmentClick(scheduleCourse: ScheduleCourse) {
        // PRE: the user clicks on an assignment link under a course
        // POST: routes the user to that assignment page
        // TODO: Ensure it links to the proper class
        this.router.navigateByUrl('/student360/assignments');
    }

    attendanceClick(scheduleCourse: ScheduleCourse) {
        // PRE: the user clicks on an attendance link under a course
        // POST: routes the user to that attendance page
        this.router.navigateByUrl('/student360/attendance');
    }

    emailTeacherClick(scheduleCourse: ScheduleCourse) {
        // PRE: the user clicks on an attendance link under a course
        // POST: routes the user to the email page
        // TODO: Ensure it links to the proper teacher
        this.sendEmailService.teacherName = scheduleCourse.TeacherName;
        this.sendEmailService.teacherEmailAddress = scheduleCourse.TeacherEmail;
        this.router.navigateByUrl('/student360/sendEmail');
    }

    gradesClick(scheduleCourse: ScheduleCourse) {
        // PRE: the user clicks on a grade link under a course
        // POST: routes the user to the grade page
        this.router.navigateByUrl('/student360/reportcardgrades');
    }

    courseDescriptionClick(scheduleCourse: ScheduleCourse) {
        // PRE: the user clicks on a course description link under a course
        // POST: shows a modal for the course's description
        this.dialog.open(CourseDescriptionDialogComponent, {
            data: {
                course: scheduleCourse.Course,
                section: scheduleCourse.Section,
                teacherName: scheduleCourse.TeacherName,
                schoolName: scheduleCourse.SchoolName,
                curriculum: scheduleCourse.Curriculum,
                description: scheduleCourse.Description
            }
        });
    }

    classInformationClick(scheduleCourse: ScheduleCourse) {
        // PRE: the user clicks on a class information link under a course
        // POST: shows a modal for that class' profile
        this.dialog.open(ProfileViewerDialogComponent, {
            data: {
                courseSSEC_ID: scheduleCourse.Id,
                courseName: scheduleCourse.Course,
                courseSection: scheduleCourse.Section,
                teacherName: scheduleCourse.TeacherName,
                school: scheduleCourse.SchoolName
            }
        });
    }

    teacherProfileClick(scheduleCourse: ScheduleCourse) {
        // PRE: the user clicks on a teacher profile link under a couse
        // POST: shows a modal for that teacher's profile
        this.dialog.open(ProfileViewerDialogComponent, {
            data: {
                teacherId: scheduleCourse.TeacherId,
                teacherName: scheduleCourse.TeacherName,
                school: scheduleCourse.SchoolName
            }
        });
    }

    animation(event) {
        console.log(event);
    }

    // Methods
    showSchool(scheduleCourse: ScheduleCourse): boolean {
        return this.studentEnrollment.SchoolName &&
            scheduleCourse.SchoolName &&
            this.studentEnrollment.SchoolName.trim().toUpperCase() != scheduleCourse.SchoolName.trim().toUpperCase();
    }

    getSchedule() {
        // POST: obtains the schedule from the server
        this.subscriptions.push(
            this.studentScheduleService.getStudentSchedule(this.viewOption).subscribe(schedule => {
                this.schedule = schedule;
                for (let i = 0; i < this.schedule.length; i++) {
                    this.schedule[i] = System.convert<ScheduleCourse>(this.schedule[i], new ScheduleCourse());
                }
                this.tableData = new TylerMatTableDataSource(this.schedule);
                if (this.sort) {
                    this.tableData.sort = this.sort;
                }
            })
        );
    }

    checkColumns() {
        // POST: checks the columns for ones that shouldn't be there

        // Team is a V9 only column
        if (this.V10 && this.columns.includes('team')) {
            this.columns.splice(this.columns.indexOf('team'), 1);
        } else if (!this.V10 && !this.columns.includes('team')) {
            this.columns.push('team');  // Team is always on the end
        }

        // Entry date and dropped date are only there if include dropped
        if (this.includeDropped) {
            if (!this.columns.includes('entry date')) {
                this.columns.splice(5, 0, 'entry date');
            }
            if (!this.columns.includes('dropped date')) {
                this.columns.splice(6, 0, 'dropped date');
            }
            this.minTableWidth = 1000;
        } else {
            if (this.columns.includes('dropped date')) {
                this.columns.splice(this.columns.indexOf('dropped date'), 1);
            }
            if (this.columns.includes('entry date')) {
                this.columns.splice(this.columns.indexOf('entry date'), 1);
            }
            this.minTableWidth = 750;
        }
    }
}

这是我正在谈论的使无效的动画事件.一,动画停止工作.另外,我已经测试过是否可以创建一个无效的过渡动画,但是该动画也不能播放.

This is the animation event to void that I'm talking about. After this one, the animation stops working. Also, I've tested to see if I can create a void transition animation, but that animation doesn't play either.

现在,我知道tableData可以正常工作,因为该表显示良好.此外,在从排序中触发该事件之前,动画可以完美运行.实际上,即使没有播放动画,排序工作和"detailRow.done"事件仍会触发.因此,我知道这一定与MatSort和Animation交互有关:我只是不知道什么.

Now, I know that the tableData works properly because the table displays fine. Further, the animations work perfectly before that event is fired from sorting. In fact, the sorting works and the "detailRow.done" event keeps firing even when the animation isn't playing. So, I know it must be something to do with MatSort and Animation interaction: I just don't know what.

这是我尝试过的:

  • 删除[ngStyle]和[ngClass]
  • 删除桌子及其容器上的宽度和高度样式
  • 卸下ngDoCheck生命周期挂钩
  • 更改mat-sort-header以使用matColumnDef并使matColumnDef与sort属性名称匹配
  • 使用setTimeout将排序方式设置为tableData
  • 排序发生变化后,将表弹回"到DOM中或从DOM中弹出
  • 排序更改后在表上强制一个renderRows

更新1

我尝试以堆叠闪电的方式重现该问题,但未能成功完成.看来MatSort和Angular Animations可以很好地相互配合,并且正在发生其他事情.这给了我一些指导.

更新2

因此,我发现了问题,尽管很奇怪这是一个问题.我用一些辅助函数扩展了MatTableDataSource,在该函数中,我获得了"tableData.checkExpanded"和"tableData.toggleExpanded"函数.当我使用组件中的布尔数组检查扩展时,组件工作正常.当我使用这些功能时,最终会遇到这个问题.这是该类的代码.我可能会更新stackblitz,以查看是否可以使用它来复制它.

export class TylerMatTableDataSource extends MatTableDataSource<any>{
    filterNumber:number = 0;
    filterTestValue:string = '';
    filters:FilterModel[] = [];

    expandedElements:number[] = [];

    constructor(initialData?: any[]){
        super(initialData);
        this.filterPredicate = this.genericFilter;
    }    

    toggleExpanded(row: any) {
        if (row != undefined) {

            if(row.detailRow == undefined || row.detailRow == false){
                row.detailRow = true;
            }
            else{
                row.detailRow = false;
            }
        }
    }

    checkExpanded(row:any):boolean{
        if(row.detailRow == undefined){
            row.detailRow = false;
        }

        return row.detailRow;
    }

    expandAll(expand: boolean) {
        this.data.forEach(element => {
            element.detailRow = expand;
        });
    }
}

更新3

我已经更新了stackblitz以演示该问题.请注意,仅当我在更多"列的p标签上使用两个* ngIf时,才会发生这种情况.如果使用插值,则不会发生错误.

I've updated the stackblitz to demonstrate the problem. Note that this only happens when I use two *ngIf's on the p tags in the 'More' column. If I use interpolation, the error does not occur.

https://stackblitz.com/edit/angular-te2cen

推荐答案

我也遇到了同样的问题,并通过更改动画从

I have had the same problem and fixed by adding an additional void state by changing the animation from

trigger('detailExpand', [
  state('collapsed', style({ height: '0px', minHeight: '0', display: 'none' })),
  state('expanded', style({ height: '*' })),
  transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
])

trigger('detailExpand', [
  state('collapsed, void', style({ height: '0px', minHeight: '0', display: 'none' })),
  state('expanded', style({ height: '*' })),
  transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
  transition('expanded <=> void', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)'))
])

仅更改是将第一个 state('collapsed')更改为 state('collapsed,void'和最后一个 transition(...)线.

Only changes are first state('collapsed' to state('collapsed, void' and the last transition(...) line.

现在,排序和扩展行均能按预期工作.

Now both sorting and expanding rows work as expected.

向pabloFuente寻求解决方案此处.

Credit to pabloFuente for solution here.

这篇关于MatSort打破MatTable详细信息行动画的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆