Skip to content

Add Array deprecation guides #1406

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions content/ember/v6/deprecate-ember-array-foundations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
---
title: Deprecation of @ember/array Foundation APIs
until: "6.0"
since: "5.8"
displayId: array.foundations
---

The foundational APIs of `@ember/array`, including the `A()` function and the core mixins (`EmberArray`, `MutableArray`, `NativeArray`), are deprecated. These were used to create and extend arrays with Ember's observability. The modern approach is to use native JavaScript arrays, and `TrackedArray` from `tracked-built-ins` when reactivity is needed.

### `A()` Function and Core Mixins

The `A()` function would wrap a native array, making it an `EmberArray`. The `EmberArray` and `MutableArray` mixins could be used to build custom array-like classes.

**Before**
```javascript
import { A } from '@ember/array';
import EmberObject from '@ember/object';
import { MutableArray } from '@ember/array';

let emberArr = A([1, 2, 3]);
emberArr.pushObject(4);

const MyArray = EmberObject.extend(MutableArray, {
// ... implementation ...
});
```

**After**
Use native arrays for standard array operations. For arrays that need to be tracked for reactivity in components and other classes, use `TrackedArray` from the `tracked-built-ins` addon.

```javascript
// For a standard array
let nativeArr = [1, 2, 3];
nativeArr.push(4);

// For a tracked array
import { TrackedArray } from 'tracked-built-ins';
let trackedArr = new TrackedArray([1, 2, 3]);
trackedArr.push(4); // This mutation is tracked
```

### Utility Functions: `isArray` and `makeArray`

These functions helped create and check for arrays.

**Before**
```javascript
import { isArray, makeArray } from '@ember/array';
let isArr = isArray([]);
let ensuredArr = makeArray('hello');
```

**After**
Use native JavaScript equivalents.

```javascript
// isArray() -> Array.isArray()
let isArr = Array.isArray([]);

// makeArray() -> custom helper or ensure data is correct
function ensureArray(value) {
if (value === null || value === undefined) return [];
return Array.isArray(value) ? value : [value];
}
let ensuredArr = ensureArray('hello');
```

### Creating Custom Arrays with Proxies

For advanced use cases where you might have created a custom class based on `MutableArray` to add special behaviors to your array, the modern JavaScript equivalent is to use a `Proxy`. A `Proxy` object allows you to intercept and redefine fundamental operations for a target object (like an array), enabling you to create powerful custom wrappers.

**Example: A Logging Array**

Imagine you want to log every time an item is pushed to an array.

**Before**, you might have done this with `MutableArray`:

```javascript
import EmberObject from '@ember/object';
import { MutableArray } from '@ember/array';

const LoggingArray = EmberObject.extend(MutableArray, {
// Internal content array
_content: null,

init() {
this._super(...arguments);
this._content = this._content || [];
},

// Required primitives
objectAt(idx) { return this._content[idx]; },
get length() { return this._content.length; },

// Override replace to add logging
replace(idx, amt, objects) {
if (amt === 0) {
console.log(`Adding items: ${objects.join(', ')}`);
}
this._content.splice(idx, amt, ...objects);
this.arrayContentDidChange(idx, amt, objects.length);
}
});

let arr = LoggingArray.create({ _content: [1, 2] });
arr.pushObject(3); // Logs: "Adding items: 3"
```

**After**, you can achieve the same result more cleanly by wrapping a `TrackedArray` in a `Proxy`. This allows you to add custom behavior while preserving the reactivity provided by `TrackedArray`.

```javascript
import { TrackedArray } from 'tracked-built-ins';

function createTrackedLoggingArray(initialItems) {
// Start with a TrackedArray instance
const trackedArr = new TrackedArray(initialItems);

return new Proxy(trackedArr, {
get(target, prop, receiver) {
// Intercept the 'push' method
if (prop === 'push') {
return function(...args) {
console.log(`Adding items via push: ${args.join(', ')}`);
// Call the original push method on the TrackedArray
// This will trigger reactivity automatically.
return target.push(...args);
}
}

// Forward all other property access and method calls to the TrackedArray
const value = Reflect.get(target, prop, receiver);
return typeof value === 'function' ? value.bind(target) : value;
},

set(target, prop, value, receiver) {
// Intercept direct index assignment
if (!isNaN(parseInt(prop, 10))) {
console.log(`Setting index ${prop} to ${value}`);
}
// Forward the set operation to the TrackedArray to trigger reactivity
return Reflect.set(target, prop, value, receiver);
}
});
}

// In a component:
class MyComponent {
loggingArray = createTrackedLoggingArray([1, 2]);

addItem() {
this.loggingArray.push(3); // Logs and triggers an update
}

updateItem() {
this.loggingArray[0] = 'new value'; // Logs and triggers an update
}
}
```

This `Proxy` approach is very powerful. By wrapping a `TrackedArray`, you can layer in custom logic while letting it handle the complexities of reactivity. This is the recommended pattern for creating advanced, observable array-like objects in modern Ember.
101 changes: 101 additions & 0 deletions content/ember/v6/deprecate-ember-array-read-methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
title: Deprecation of @ember/array Read Methods
until: "6.0"
since: "5.8"
displayId: array.read-methods
---

All read-only methods and computed properties from `@ember/array` are deprecated. You should use native JavaScript array methods and properties instead. This guide covers the common read-only APIs and their native equivalents.

### `firstObject` and `lastObject`

These computed properties provided safe access to the first and last elements of an array.

**Before**
```javascript
import { A } from '@ember/array';
let arr = A(['a', 'b', 'c']);
let first = arr.get('firstObject'); // 'a'
let last = arr.get('lastObject'); // 'c'
```

**After**
Use native array bracket notation, or the `at()` method for accessing elements from the end of the array.

```javascript
let arr = ['a', 'b', 'c'];
let first = arr[0];
let last = arr.at(-1);
```

### `objectAt` and `objectsAt`

These methods provided safe, index-based access to array elements.

**Before**
```javascript
import { A } from '@ember/array';
let arr = A(['a', 'b', 'c']);
let middle = arr.objectAt(1); // 'b'
let some = arr.objectsAt([0, 2]); // ['a', 'c']
```

**After**
Use native array bracket notation for `objectAt`. For `objectsAt`, you can use `map`.

```javascript
let arr = ['a', 'b', 'c'];
let middle = arr[1];
let some = [0, 2].map(index => arr[index]);
```

### `mapBy`, `filterBy`, `rejectBy`, `findBy`

These methods were shortcuts for common mapping and filtering operations on arrays of objects.

**Before**
```javascript
import { A } from '@ember/array';
let users = A([
{ name: 'John', isActive: true },
{ name: 'Jane', isActive: false },
]);
let names = users.mapBy('name');
let active = users.filterBy('isActive', true);
let john = users.findBy('name', 'John');
```

**After**
Use the native `map`, `filter`, and `find` methods with arrow functions.

```javascript
let users = [
{ name: 'John', isActive: true },
{ name: 'Jane', isActive: false },
];
let names = users.map(user => user.name);
let active = users.filter(user => user.isActive === true);
let john = users.find(user => user.name === 'John');
```

### `uniqBy`

`uniqBy` created a new array with unique elements based on a property.

**Before**
```javascript
import { uniqBy } from '@ember/array';
let users = [{ id: 1 }, { id: 2 }, { id: 1 }];
let unique = uniqBy(users, 'id');
```

**After**
Use a `Map` to efficiently create a unique list.

```javascript
let users = [{ id: 1 }, { id: 2 }, { id: 1 }];
let unique = Array.from(
users.reduce((map, user) => map.set(user.id, user), new Map()).values()
);
```

94 changes: 94 additions & 0 deletions content/ember/v6/deprecate-ember-array-write-methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
title: Deprecation of @ember/array Write Methods
until: "6.0"
since: "5.8"
displayId: array.write-methods
---

All methods from `@ember/array` that mutate or "write" to an array are deprecated. This includes observable methods like `pushObject`.

The modern approach is to use `TrackedArray` from the [`tracked-built-ins`](https://github.com/tracked-tools/tracked-built-ins) addon. This class provides a tracked version of the native JavaScript `Array` that can be mutated directly, and these mutations will be tracked automatically.

First, install the addon:

```bash
ember install tracked-built-ins
```

### Observable Write Methods

Methods like `pushObject`, `popObject`, `removeObject`, `insertAt`, and `removeAt` were used to modify arrays in a way that Ember's classic observability system could track.

**Before**
```javascript
import { A } from '@ember/array';
let arr = A([1, 2, 3]);

arr.pushObject(4);
arr.removeAt(1, 1); // remove 1 item at index 1
```

**After**
Use `TrackedArray` and mutate it directly with standard JavaScript array methods. Using `TrackedArray` provides an ergonomic API that is nearly identical to working with plain JavaScript arrays, while providing the reactivity needed for your application's UI to update automatically.

```javascript
import { TrackedArray } from 'tracked-built-ins';

// In a component or class
class MyComponent {
myArray = new TrackedArray([1, 2, 3]);

addItem() {
// pushObject -> push
this.myArray.push(4);
}

removeItem() {
// removeAt -> splice
this.myArray.splice(1, 1);
}

clearItems() {
// clear -> set length to 0
this.myArray.length = 0;
}
}
```

This pattern applies to all mutation methods. Here is a brief mapping for the most common methods:

| `@ember/array` Method | Native `TrackedArray` Method |
|-----------------------|------------------------------|
| `pushObject(s)` | `push` / `...` (spread) |
| `popObject()` | `pop` |
| `shiftObject()` | `shift` |
| `unshiftObject(s)` | `unshift` |
| `insertAt(idx, obj)` | `splice(idx, 0, obj)` |
| `removeAt(idx, len)` | `splice(idx, len)` |
| `clear()` | `length = 0` |
| `replace()` | `splice` |

### Handling Uniqueness and Specific Objects

For methods like `addObject` and `removeObject` that deal with specific object instances or uniqueness, you need a bit more logic.

```javascript
// In your component class with `myArray = new TrackedArray([...])`

// removeObject replacement
removeItem(item) {
const index = this.myArray.indexOf(item);
if (index > -1) {
this.myArray.splice(index, 1);
}
}

// addObject replacement
addUniqueItem(item) {
if (!this.myArray.includes(item)) {
this.myArray.push(item);
}
}
```

Alternatively, if you are working with a list that must be unique, consider using a `Set` or `TrackedSet` from `tracked-built-ins`, as they handle uniqueness automatically.