【问题标题】:Angular6: view not updating on data change despite bindingAngular6:尽管绑定,但视图不更新数据更改
【发布时间】:2018-12-14 23:15:39
【问题描述】:

我用Angular material table 制作了一个自定义组件。它基本上是一个递归表,当单击一行时显示一个子表,然后单击一行时显示一个子子表。它需要一个递归模板。表格输入数据非常大,采用 JSON 的形式,带有 JSON 的数组等。这种格式是由项目的其余部分决定的,不灵活。

数据存储在名为data 的变量中。我的表格有输入字段来修改data 中的条目,我检查它是否正常工作,我的问题是如果从表格外部更新data,表格视图不会改变,没有更新。在将data 传递给组件时,我对使用绑定感到惊讶。我是 Angular 的初学者,对 SubjectsObservables 还不满意,所以我还没有朝那个方向看。

这里是代码。

对于递归组件:

@Component({
  selector: 'app-rectable',
  template: `
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

<div class="example-container mat-elevation-z8">
  <mat-table [dataSource]="theData">


    <ng-container matColumnDef="name">
      <mat-header-cell *matHeaderCellDef> channel </mat-header-cell>
      <mat-cell *matCellDef="let element">
        <button *ngIf="element.isexpandable" mat-icon-button (click)="element.isexpanded = !element.isexpanded;">
          <mat-icon class="mat-icon-rtl-mirror">
            {{element.isexpanded ? 'expand_more' : 'chevron_right'}}
          </mat-icon>
        </button>
        <mat-form-field class="example-full-width">
          <input matInput [(ngModel)]="element.name" (input)="update($event)">
        </mat-form-field>
      </mat-cell>
    </ng-container>

    <ng-container matColumnDef="min">
      <mat-header-cell *matHeaderCellDef> min </mat-header-cell>
      <mat-cell *matCellDef="let element">
        <input matInput type=number [(ngModel)]="element.min" (input)="update($event)">
      </mat-cell>
    </ng-container>

    <ng-container matColumnDef="max">
      <mat-header-cell *matHeaderCellDef> max </mat-header-cell>
      <mat-cell *matCellDef="let element">
        <input matInput type=number [(ngModel)]="element.max" (input)="update($event)">
      </mat-cell>
    </ng-container>

    <ng-container matColumnDef="value">
      <mat-header-cell *matHeaderCellDef> value </mat-header-cell>
      <mat-cell *matCellDef="let element">
        {{element.value}}
      </mat-cell>
    </ng-container>

    <ng-container matColumnDef="expandedDetail">
      <mat-cell *matCellDef="let detail">
        <app-rectable *ngIf="detail.element.isexpandable" [array]="detail.element.variables" [isTop]="false">
        </app-rectable>

      </mat-cell>
    </ng-container>

    <div *ngIf="isTop">
    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
    </div>
    <mat-row *matRowDef="let row; columns: displayedColumns;" matRipple class="element-row" [class.expanded]="row.isexpanded" >
    </mat-row>
    <mat-row *matRowDef="let row; columns: ['expandedDetail']; when: isExpansionDetailRow" [@detailExpand]="row.element.isexpanded ? 'expanded' : 'collapsed'" style="overflow: hidden">
    </mat-row>
  </mat-table>
</div>
<div *ngIf="!theData.value.length" > EMPTY DATA INPUT </div>
  `,
  styleUrls: ['./rectable.component.css'],
  animations: [
    trigger('detailExpand', [
      state('collapsed', style({ height: '0px', minHeight: '0', visibility: 'hidden' })),
      state('expanded', style({ height: '*', visibility: 'visible' })),
      transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
    ]),
  ],
})

export class RectableComponent implements OnInit {

  @Input() array;

  @Input() isTop: boolean;
  theData = [];
  displayedColumns = ["name", "min", "max", "value"];
  isExpansionDetailRow = (i: number, row: Object) => row.hasOwnProperty('detailRow');

  constructor() {
  }

  ngOnInit() {
    this.theData = observableOf(this.array);
  }

}

根组件html是

<app-rectable [array]='data' [isTop]="true"></app-rectable>

data 是一个 JSON 对象,我不想在这里明确说明。

【问题讨论】:

    标签: angular typescript data-binding angular6


    【解决方案1】:

    我不知道这行 this.theData = observableOf(this.array); 的目的是什么,但这就是破坏您的代码的原因(我认为导致您这样做的原因是对 Angular @Input() 装饰器和 Observable.of() 运算符的误解使用数组的初始值发出一个事件)。

    这样做会破坏@Input() array 的引用,因此当您从父组件更新它时,模板不会更新,因为它绑定到theData,并且theData 仅在组件上设置初始化。

    那么解决方法是:

    • 如果this.theData = observableOf(this.array); 有实际用途,则将OnChanges 实现到您的组件中,并将theData 更新为ngOnChanges()
    • 如果this.theData = observableOf(this.array); 没有真正的用途(我坚信它没有),那么只需完全删除this.theData 并将您的模板绑定到array

    【讨论】:

    • 我不确定this.theData = observableOf(this.array); 做了什么,但mat-table 期望作为输入,是一个可观察的而不是数组,所以看起来我不能直接喂它this.array(我试过并得到一个TypeError: Cannot read property 'length' of undefined)我尝试将东西包装在DataSource 中以模拟来自角度材料网站的一些规范示例,但没有成功。感谢您的输入顺便说一句。
    • 我正在尝试了解您的解决方案,在我的设置中,什么会触发 ngOnChanges()?父组件或服务对array 进行了任何更改?
    • @Learningisamess 由于您使用的是默认更改检测策略,因此父组件对array 对象的任何更改都应触发ngOnChanges
    【解决方案2】:

    因为在您的ngOnInit 中,您将data 的类型从Array 更改为Observable

    在您的情况下,由于您绑定到 [dataSource],它需要 dataSource: DataSource&lt;T&gt; | Observable&lt;T[]&gt; | T[] 的类型,您可以简单地让父组件通过异步管道将数据传递给您的子组件。

    家长:

    @Component({
      selector: 'parent-component',
      template: `
    <div>
        <app-rectable [data]="data$ | async" [isTop]="true"></app-rectable>
        ...
    </div>
      `,
    })
    
    export class ParentComponent implements OnInit {
    
        data$: Observable<SomeType[]>;
        constructor(private dataService: DataService) {
        }
    
        ngOnInit() {
            // fetch your data via the service
            this.data$ = this.dataService.fetchData();
        }
     }
    

    孩子:

    @Component({
      selector: 'app-rectable',
      template: `
    <div class="example-container mat-elevation-z8">
      <mat-table [dataSource]="data">
      ...
    </div>
      `,
    })
    
    export class RectableComponent implements OnInit {
    
      @Input() data: SomeType[];
      @Input() isTop: boolean;
    
      displayedColumns = ["name", "min", "max", "value"];
      isExpansionDetailRow = (i: number, row: Object) => row.hasOwnProperty('detailRow');
    
      constructor() {
      }
    }
    

    【讨论】:

    • 非常感谢@DavidZ 的输入,我已经实现了您的代码,并且我看到绑定运行良好,因为数据服务的主题状态确实得到了更新。但是我在递归表中添加了一些东西:每一行都有一个删除按钮。它通过使用data.splicethis.data 中删除相应的元素。我再次看到数据服务中主题的状态得到更新,条目从其数组中删除,但此更改不会传播到视图,删除的行仍然可见。如何让视图更新?
    • 如果您希望数据是可变的,您可以考虑将数据包装在 DataSource 中并将其与 Paginator 结合使用。这是一个可以帮助您的好帖子:stackoverflow.com/questions/49272575/…
    • 确实有提供数据源的选项。我已经给了它一些想法,并且我看到了我需要首先“翻译”我的 JSON 文件的障碍,以便不是包含带有子数组的数组,以及子数组中的子数组,......它将包含包含其他数据源的数据源作为他们的data 成员。这个数据结构是否“能够运行”rectable,并且这些表在添加或删除任何深度的行时是可变的吗?
    • 我会尽量避免嵌套表,特别是如果你不知道你需要的嵌套深度并且如果你有一个大数据集,那么角度变化检测将会非常非常慢。我发现了一些可能对您有所帮助的附加链接:github.com/angular/material2/issues/6095github.com/angular/material2/issues/10755
    • 感谢您的见解,我刚刚接受了您的回答。
    【解决方案3】:

    Angular 2+ 是一种数据绑定方式。

    因此,如果您更改根目录中的数据,您可以看到组件内部的更改,但是当组件内部发生更改时,您看不到根目录的更改。

    你可以这样工作:

    import { EventEmitter} from '@angular/core';
    
    export class YourComponent {
        @Input() item: any;
        @Output() itemChange = new EventEmitter();
      emitItem() {
          this.itemChange.emit(this.item);
      }
    }
    

    这样就可以了。

      this.itemChange.emit(this.item);
    

    我是这样用的

      <input matInput type=number [(ngModel)]="item" (ngModelChange)="emitItem()" (input)="update($event)">
    

    因此,当项目更改时,将调用 emitItem 并且我将在根组件中进行更改。

    我希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-12-24
      • 2014-05-24
      • 1970-01-01
      • 1970-01-01
      • 2022-01-23
      • 2022-11-24
      • 2020-04-08
      相关资源
      最近更新 更多