From a4e760715963fafa6facf8f8bf58a3201c38d911 Mon Sep 17 00:00:00 2001 From: Peter Wagenet Date: Fri, 20 Jun 2025 15:53:51 -0700 Subject: [PATCH 1/2] Add Array deprecation guides --- .../v6/deprecate-ember-array-foundations.md | 160 ++++++++++++++++++ .../v6/deprecate-ember-array-read-methods.md | 101 +++++++++++ .../v6/deprecate-ember-array-write-methods.md | 94 ++++++++++ 3 files changed, 355 insertions(+) create mode 100644 content/ember/v6/deprecate-ember-array-foundations.md create mode 100644 content/ember/v6/deprecate-ember-array-read-methods.md create mode 100644 content/ember/v6/deprecate-ember-array-write-methods.md diff --git a/content/ember/v6/deprecate-ember-array-foundations.md b/content/ember/v6/deprecate-ember-array-foundations.md new file mode 100644 index 00000000..daa26fe7 --- /dev/null +++ b/content/ember/v6/deprecate-ember-array-foundations.md @@ -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. diff --git a/content/ember/v6/deprecate-ember-array-read-methods.md b/content/ember/v6/deprecate-ember-array-read-methods.md new file mode 100644 index 00000000..17af6836 --- /dev/null +++ b/content/ember/v6/deprecate-ember-array-read-methods.md @@ -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() +); +``` + diff --git a/content/ember/v6/deprecate-ember-array-write-methods.md b/content/ember/v6/deprecate-ember-array-write-methods.md new file mode 100644 index 00000000..be357d0d --- /dev/null +++ b/content/ember/v6/deprecate-ember-array-write-methods.md @@ -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. From 534bcd803bfb7282b3a3780f54bdd68c38cd0c69 Mon Sep 17 00:00:00 2001 From: Peter Wagenet Date: Tue, 12 Aug 2025 11:47:32 -0700 Subject: [PATCH 2/2] Add note about Computed Properties --- content/ember/v6/deprecate-ember-array-foundations.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/content/ember/v6/deprecate-ember-array-foundations.md b/content/ember/v6/deprecate-ember-array-foundations.md index daa26fe7..bfb6d173 100644 --- a/content/ember/v6/deprecate-ember-array-foundations.md +++ b/content/ember/v6/deprecate-ember-array-foundations.md @@ -7,6 +7,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. +> Warning: If you have Computed Properties that rely on EmberArray values, you may find their behavior affected when you migrate. Please consider migrating these Computed Properties to use native getters before beginning the EmberArray migration. + ### `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.