Keywords: TypeScript | Angular | Immutable Data
Abstract: This article examines the TypeError: Cannot assign to read only property '0' of object '[object Array]' error in Angular applications when attempting to modify a read-only array received via @Input. It delves into the root cause—direct mutation of immutable data passed from parent components—and explains why the error occurs only under specific conditions, such as after data updates. Based on the best answer, the article proposes using the spread operator to create array copies and discusses best practices in Angular and NgRx state management, including avoiding direct state mutations, maintaining pure data flows, and enhancing application maintainability through immutable data patterns.
Error Context and Phenomenon Analysis
In an Angular 8 application, developers encounter a specific TypeScript error: TypeError: Cannot assign to read only property '0' of object '[object Array]'. This error occurs when attempting to sort an array taskList received via the @Input() decorator. Specifically, the error points to an exchange function that tries to swap array elements for sorting:
exchange(a, b) {
const temp = this.taskList[a];
this.taskList[a] = this.taskList[b]; // Error line
this.taskList[b] = temp;
}Interestingly, this error does not appear on initial application load but only triggers after deleting a row and refetching the taskList array. This inconsistency suggests complexity: the array might be mutable initially, but updated arrays are marked as read-only.
Root Cause Investigation
The root cause lies in directly modifying an array passed via @Input(). In Angular, input properties are often treated as immutable data, especially when integrated with state management libraries like NgRx. When a parent component passes an array to a child, it may be encapsulated as a read-only object to prevent accidental mutations. On initial load, the array might not be strictly marked as read-only, allowing sorting to succeed. However, after a delete operation, the new array instance may come from NgRx state, where the state tree is typically immutable, causing array properties to become read-only.
From the code flow, the ngOnChanges lifecycle hook triggers sorting upon detecting changes in taskList:
ngOnChanges(changes: SimpleChanges) {
if (this.taskList !== null && this.taskList !== undefined) {
this.taskChange('export_name', 'asc');
}
}The sorting function taskChange uses nested loops and the exchange method to directly modify this.taskList, violating immutable data principles and throwing an error when the array is read-only.
Solution and Implementation
Based on the best answer, the core solution is to avoid modifying the original array directly by creating a copy for operations. This can be achieved using JavaScript's spread operator:
async taskChange(value, taskOrder) {
this.sortOrder = taskOrder;
this.selectedValue = value;
const sortedArray = [...this.taskList]; // Create array copy
const expr = {
asc: (a, b) => a > b,
desc: (a, b) => a < b,
};
for (let i = 0; i < sortedArray.length; i++) {
for (let j = i + 1; j < sortedArray.length; j++) {
switch (value) {
case 'export_name':
if (expr[this.sortOrder](sortedArray[i].name, sortedArray[j].name)) {
const temp = sortedArray[i];
sortedArray[i] = sortedArray[j];
sortedArray[j] = temp;
}
break;
case 'file_type':
let type1 = this.exportType.transform(sortedArray[i].code, []);
let type2 = this.exportType.transform(sortedArray[j].code, []);
if (expr[this.sortOrder](type1, type2)) {
const temp = sortedArray[i];
sortedArray[i] = sortedArray[j];
sortedArray[j] = temp;
}
break;
}
}
}
this.taskList = sortedArray; // Assign sorted copy back to original property
}This approach ensures sorting operations are performed only on the copy, avoiding direct mutation of the read-only array. After sorting, the copy is assigned to this.taskList, triggering Angular's change detection and updating the view.
Extended Discussion and Best Practices
In the Angular and NgRx ecosystem, immutable data patterns are key to maintaining predictable application state. Referencing other answers, similar issues are common in React/Redux, such as direct state array modifications causing errors. Solutions similarly involve creating copies:
const items = [...getItems(state)]; // Create new array via spread operator
items.sort((a, b) => a.order - b.order); // Safe sortingTo enhance code quality, it is recommended to:
- Use pure functions for sorting to avoid side effects.
- Ensure state updates in NgRx effects are performed via immutable operations.
- Consider libraries like Immer to simplify immutable update logic.
- Clearly distinguish between the read-only nature of input data and the mutability of internal state in components.
By adhering to these practices, developers can prevent such runtime errors while improving code maintainability and performance.