Skip to content

TypeError: Cannot assign to read only property '0' of object '[object Array]' の解決方法

問題の概要

Angularアプリケーションで、@Input()として受け取った配列をソートしようとすると、「TypeError: Cannot assign to read only property '0' of object '[object Array]'」エラーが発生します。このエラーは、配列の要素を直接変更しようとした際に発生します。

具体的な状況

  • コンポーネントが @Input() taskList として配列を受け取る
  • 初期表示時はソート機能が正常に動作する
  • データを削除して再取得した後、ソートしようとするとエラーが発生する
  • エラーは配列の要素を交換する exchange() メソッドで発生する
typescript
exchange(a, b) {
  const temp = this.taskList[a];
  this.taskList[a] = this.taskList[b]; // ここでエラー発生
  this.taskList[b] = temp;
}

原因

このエラーの根本的な原因は、変更不可(immutable)な配列を直接変更しようとしていることにあります。

なぜ配列が変更不可になるのか

  • Angularの変更検知メカニズムにより、@Input()で受け取ったデータが凍結(freeze)される場合がある
  • ReduxやNgRxなどの状態管理ライブラリでは、状態の不変性(immutability)が強制される
  • データ再取得後の配列が読み取り専用として返される

解決方法

方法1: 配列のコピーを作成して操作する

最も一般的で安全な解決策は、元の配列を変更せずにコピーを作成して操作することです。

typescript
// スプレッド演算子を使用したコピー
const arrayCopy = [...this.taskList];
arrayCopy.sort(/* ソートロジック */);
this.sortedTaskList = arrayCopy;

// または Array.slice() を使用
const arrayCopy = this.taskList.slice();
arrayCopy.sort(/* ソートロジック */);

方法2: ソート関数を修正する

元の exchange メソッドを修正して、配列のコピーに対して操作を行うように変更します。

typescript
// 修正後のソート処理
async taskChange(value, taskOrder) {
  // 元の配列をコピー
  const sortedList = [...this.taskList];
  
  const expr = {
    asc: (a, b) => a > b,
    desc: (a, b) => a < b,
  };
  
  // コピーした配列に対してソート処理を実行
  for (let i = 0; i < sortedList.length; i++) {
    for (let j = i + 1; j < sortedList.length; j++) {
      // ソート条件に基づいて要素を交換
      if (expr[taskOrder](sortedList[i].name, sortedList[j].name)) {
        const temp = sortedList[i];
        sortedList[i] = sortedList[j];
        sortedList[j] = temp;
      }
    }
  }
  
  // ソートされたコピーを表示用に設定
  this.displayTaskList = sortedList;
}

方法3: 組み込みのsortメソッドを活用する

カスタムソートロジックの代わりに、JavaScriptの組み込みsortメソッドを使用することもできます。

typescript
// 組み込みのsortメソッドを使用した例
sortTaskList(sortBy: string, order: 'asc' | 'desc') {
  const sorted = [...this.taskList].sort((a, b) => {
    if (sortBy === 'export_name') {
      return order === 'asc' 
        ? a.name.localeCompare(b.name)
        : b.name.localeCompare(a.name);
    }
    // 他のソート条件...
    return 0;
  });
  
  this.displayTaskList = sorted;
}

ベストプラクティス

不変性(Immutability)の原則

Angularアプリケーションでは、入力プロパティを直接変更しないことが重要です。代わりに:

  1. 常に入力データのコピーを作成する
  2. コピーに対して操作を実行する
  3. 必要に応じてコピーを表示用データとして使用する

パフォーマンスに関する注意

大規模な配列を操作する場合、ディープコピーはパフォーマンスに影響を与える可能性があります。変更が必要な部分だけをシャローコピーするように心がけましょう。

サンプルコード

以下は完全なコンポーネントの実装例です:

typescript
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';

@Component({
  selector: 'app-task-list',
  templateUrl: './task-list.component.html',
  styleUrls: ['./task-list.component.css']
})
export class TaskListComponent implements OnChanges {
  @Input() taskList: any[] = [];
  displayTaskList: any[] = [];
  selectedValue: string = 'export_name';
  sortOrder: 'asc' | 'desc' = 'asc';

  ngOnChanges(changes: SimpleChanges) {
    if (changes.taskList && this.taskList) {
      this.sortTaskList(this.selectedValue, this.sortOrder);
    }
  }

  sortTaskList(sortBy: string, order: 'asc' | 'desc') {
    // 元の配列を変更せずにコピーを作成
    this.displayTaskList = [...this.taskList].sort((a, b) => {
      if (sortBy === 'export_name') {
        return order === 'asc' 
          ? a.name.localeCompare(b.name)
          : b.name.localeCompare(a.name);
      }
      // 他のソート条件を追加
      return 0;
    });
  }

  onSortChange(sortBy: string) {
    // ソート順序を切り替え
    this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc';
    this.selectedValue = sortBy;
    this.sortTaskList(sortBy, this.sortOrder);
  }
}
html
<table>
  <thead>
    <tr>
      <th (click)="onSortChange('export_name')">
        タスク名 {{selectedValue === 'export_name' ? (sortOrder === 'asc' ? '▲' : '▼') : ''}}
      </th>
      <!-- 他のヘッダー -->
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let task of displayTaskList">
      <td>{{ task.name }}</td>
      <!-- 他のデータ -->
    </tr>
  </tbody>
</table>

まとめ

「Cannot assign to read only property」エラーは、変更不可の配列を直接操作しようとした際に発生します。Angularアプリケーションでは、@Input()で受け取ったデータを直接変更するのではなく、必ずコピーを作成してから操作するようにしましょう。これにより、予期しない副作用を防ぎ、アプリケーションの予測可能性を高めることができます。