Angular6: представление не обновляется при изменении данных, несмотря на привязку - PullRequest
0 голосов
/ 06 июля 2018

Я сделал нестандартный компонент из Таблица угловых материалов . По сути, это рекурсивная таблица, в которой при щелчке на строке отображается подтаблица, а при щелчке на строке последующего - подтаблица. Требуется рекурсивный шаблон. Входные данные таблицы достаточно велики и принимают форму JSON с массивами JSON и т. Д. Этот формат был выбран остальной частью проекта и является негибким.

Данные хранятся в переменной с именем data. В моей таблице есть поле ввода для изменения записей в data, и я проверил, работает ли он должным образом, моя проблема в том, что если обновление data извне таблицы, представление таблицы не изменяется, обновление не происходит. Меня это удивляет, так как используется привязка при передаче data компоненту. Я новичок в Angular и пока не чувствую себя комфортно с Subjects и Observables, поэтому я не смотрел в этом направлении.

Вот код.

Для рекурсивного компонента:

@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, который я предпочитаю не указывать здесь явно.

Ответы [ 3 ]

0 голосов
/ 10 июля 2018

Потому что в вашем ngOnInit вы меняете тип data с Array на Observable.

В вашем случае, так как вы привязываетесь к [dataSource], который ожидает тип dataSource: DataSource<T> | Observable<T[]> | 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() {
  }
}
0 голосов
/ 12 июля 2018

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, и я получу свои изменения в корневом компоненте.

Надеюсь, это может быть полезно.

0 голосов
/ 10 июля 2018

Я не знаю, какова цель этой строки 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
...