diff --git a/packages/@ember/-internals/metal/lib/array.ts b/packages/@ember/-internals/metal/lib/array.ts index 92043a6128e..fca4a400659 100644 --- a/packages/@ember/-internals/metal/lib/array.ts +++ b/packages/@ember/-internals/metal/lib/array.ts @@ -1,13 +1,11 @@ -import { EmberArray, getDebugName } from '@ember/-internals/utils'; -import { deprecate } from '@ember/debug'; +import { EmberArray } from '@ember/-internals/utils'; import { arrayContentDidChange, arrayContentWillChange } from './array_events'; import { addListener, removeListener } from './events'; -import { notifyPropertyChange } from './property_events'; const EMPTY_ARRAY = Object.freeze([]); -interface ObjectHasArrayObservers { - hasArrayObservers?: boolean; +interface ObservedObject extends EmberArray { + _revalidate?: () => void; } export function objectAt(array: T[] | EmberArray, index: number): T | undefined { @@ -58,34 +56,33 @@ export function replaceInNativeArray( } interface ArrayObserverOptions { - willChange?: string; - didChange?: string; + willChange: string; + didChange: string; } -type Operation = ( - obj: ObjectHasArrayObservers, +type Operation = ( + obj: ObservedObject, eventName: string, target: object | Function | null, callbackName: string ) => void; -function arrayObserversHelper( - obj: ObjectHasArrayObservers, +function arrayObserversHelper( + obj: ObservedObject, target: object | Function | null, - opts: ArrayObserverOptions | undefined, - operation: Operation, - notify: boolean -): ObjectHasArrayObservers { - let willChange = (opts && opts.willChange) || 'arrayWillChange'; - let didChange = (opts && opts.didChange) || 'arrayDidChange'; - let hasObservers = obj.hasArrayObservers; + opts: ArrayObserverOptions, + operation: Operation +): ObservedObject { + let { willChange, didChange } = opts; operation(obj, '@array:before', target, willChange); operation(obj, '@array:change', target, didChange); - if (hasObservers === notify) { - notifyPropertyChange(obj, 'hasArrayObservers'); - } + /* + * Array proxies have a `_revalidate` method which must be called to set + * up their internal array observation systems. + */ + obj._revalidate?.(); return obj; } @@ -93,47 +90,15 @@ function arrayObserversHelper( export function addArrayObserver( array: EmberArray, target: object | Function | null, - opts?: ArrayObserverOptions | undefined, - suppress = false -): ObjectHasArrayObservers { - deprecate( - `Array observers have been deprecated. Added an array observer to ${getDebugName?.(array)}.`, - suppress, - { - id: 'array-observers', - url: 'https://deprecations.emberjs.com/v3.x#toc_array-observers', - until: '4.0.0', - for: 'ember-source', - since: { - enabled: '3.26.0-beta.1', - }, - } - ); - - return arrayObserversHelper(array, target, opts, addListener, false); + opts: ArrayObserverOptions +): ObservedObject { + return arrayObserversHelper(array, target, opts, addListener); } export function removeArrayObserver( array: EmberArray, target: object | Function | null, - opts?: ArrayObserverOptions | undefined, - suppress = false -): ObjectHasArrayObservers { - deprecate( - `Array observers have been deprecated. Removed an array observer from ${getDebugName?.( - array - )}.`, - suppress, - { - id: 'array-observers', - url: 'https://deprecations.emberjs.com/v3.x#toc_array-observers', - until: '4.0.0', - for: 'ember-source', - since: { - enabled: '3.26.0-beta.1', - }, - } - ); - - return arrayObserversHelper(array, target, opts, removeListener, true); + opts: ArrayObserverOptions +): ObservedObject { + return arrayObserversHelper(array, target, opts, removeListener); } diff --git a/packages/@ember/-internals/runtime/lib/mixins/array.js b/packages/@ember/-internals/runtime/lib/mixins/array.js index d934edc8ba9..c8caf748b2e 100644 --- a/packages/@ember/-internals/runtime/lib/mixins/array.js +++ b/packages/@ember/-internals/runtime/lib/mixins/array.js @@ -12,14 +12,8 @@ import { replace, computed, Mixin, - hasListeners, beginPropertyChanges, endPropertyChanges, - addArrayObserver, - removeArrayObserver, - arrayContentWillChange, - arrayContentDidChange, - nativeDescDecorator as descriptor, } from '@ember/-internals/metal'; import { assert } from '@ember/debug'; import Enumerable from './enumerable'; @@ -476,201 +470,6 @@ const ArrayMixin = Mixin.create(Enumerable, { return -1; }, - // .......................................................... - // ARRAY OBSERVERS - // - - /** - Adds an array observer to the receiving array. The array observer object - normally must implement two methods: - - * `willChange(observedObj, start, removeCount, addCount)` - This method will be - called just before the array is modified. - * `didChange(observedObj, start, removeCount, addCount)` - This method will be - called just after the array is modified. - - Both callbacks will be passed the observed object, starting index of the - change as well as a count of the items to be removed and added. You can use - these callbacks to optionally inspect the array during the change, clear - caches, or do any other bookkeeping necessary. - - In addition to passing a target, you can also include an options hash - which you can use to override the method names that will be invoked on the - target. - - @method addArrayObserver - @param {Object} target The observer object. - @param {Object} opts Optional hash of configuration options including - `willChange` and `didChange` option. - @return {EmberArray} receiver - @public - @example - import Service from '@ember/service'; - - export default Service.extend({ - data: Ember.A(), - - init() { - this._super(...arguments); - - this.data.addArrayObserver(this, { - willChange: 'dataWillChange', - didChange: 'dataDidChange' - }); - }, - - dataWillChange(array, start, removeCount, addCount) { - console.log('array will change', array, start, removeCount, addCount); - }, - - dataDidChange(array, start, removeCount, addCount) { - console.log('array did change', array, start, removeCount, addCount); - } - }); - */ - - addArrayObserver(target, opts) { - return addArrayObserver(this, target, opts); - }, - - /** - Removes an array observer from the object if the observer is current - registered. Calling this method multiple times with the same object will - have no effect. - - @method removeArrayObserver - @param {Object} target The object observing the array. - @param {Object} opts Optional hash of configuration options including - `willChange` and `didChange` option. - @return {EmberArray} receiver - @public - */ - removeArrayObserver(target, opts) { - return removeArrayObserver(this, target, opts); - }, - - /** - Becomes true whenever the array currently has observers watching changes - on the array. - - ```javascript - let arr = [1, 2, 3, 4, 5]; - arr.hasArrayObservers; // false - - arr.addArrayObserver(this, { - willChange() { - console.log('willChange'); - } - }); - arr.hasArrayObservers; // true - ``` - - @property {Boolean} hasArrayObservers - @public - */ - hasArrayObservers: descriptor({ - configurable: true, - enumerable: false, - get() { - return hasListeners(this, '@array:change') || hasListeners(this, '@array:before'); - }, - }), - - /** - If you are implementing an object that supports `EmberArray`, call this - method just before the array content changes to notify any observers and - invalidate any related properties. Pass the starting index of the change - as well as a delta of the amounts to change. - - ```app/components/show-post.js - import Component from '@ember/component'; - import EmberObject from '@ember/object'; - - const Post = EmberObject.extend({ - body: '', - save() {} - }) - - export default Component.extend({ - attemptsToModify: 0, - successfulModifications: 0, - posts: null, - - init() { - this._super(...arguments); - - this.posts = [1, 2, 3].map(i => Post.create({ body: i })); - this.posts.addArrayObserver(this, { - willChange() { - this.incrementProperty('attemptsToModify'); - }, - didChange() { - this.incrementProperty('successfulModifications'); - } - }); - }, - - actions: { - editPost(post, newContent) { - let oldContent = post.body, - postIndex = this.posts.indexOf(post); - - this.posts.arrayContentWillChange(postIndex, 0, 0); // attemptsToModify = 1 - post.set('body', newContent); - - post.save() - .then(response => { - this.posts.arrayContentDidChange(postIndex, 0, 0); // successfulModifications = 1 - }) - .catch(error => { - post.set('body', oldContent); - }) - } - } - }); - ``` - - @method arrayContentWillChange - @param {Number} startIdx The starting index in the array that will change. - @param {Number} removeAmt The number of items that will be removed. If you - pass `null` assumes 0 - @param {Number} addAmt The number of items that will be added. If you - pass `null` assumes 0. - @return {EmberArray} receiver - @public - */ - arrayContentWillChange(startIdx, removeAmt, addAmt) { - return arrayContentWillChange(this, startIdx, removeAmt, addAmt); - }, - - /** - If you are implementing an object that supports `EmberArray`, call this - method just after the array content changes to notify any observers and - invalidate any related properties. Pass the starting index of the change - as well as a delta of the amounts to change. - - ```javascript - let arr = [1, 2, 3, 4, 5]; - - arr.copyWithin(-2); // [1, 2, 3, 1, 2] - // arr.lastObject = 5 - arr.arrayContentDidChange(3, 2, 2); - // arr.lastObject = 2 - ``` - - @method arrayContentDidChange - @param {Number} startIdx The starting index in the array that did change. - @param {Number} removeAmt The number of items that were removed. If you - pass `null` assumes 0 - @param {Number} addAmt The number of items that were added. If you - pass `null` assumes 0. - @return {EmberArray} receiver - @public - */ - arrayContentDidChange(startIdx, removeAmt, addAmt) { - return arrayContentDidChange(this, startIdx, removeAmt, addAmt); - }, - /** Iterates through the array, calling the passed function on each item. This method corresponds to the `forEach()` method defined in @@ -1551,7 +1350,7 @@ const MutableArray = Mixin.create(ArrayMixin, MutableEnumerable, { This is one of the primitives you must implement to support `Array`. You should replace amt objects started at idx with the objects in the - passed array. You should also call `this.arrayContentDidChange()` + passed array. Note that this method is expected to validate the type(s) of objects that it expects. diff --git a/packages/@ember/-internals/runtime/lib/system/array_proxy.js b/packages/@ember/-internals/runtime/lib/system/array_proxy.js index 8fed955c0a9..96cff0c996b 100644 --- a/packages/@ember/-internals/runtime/lib/system/array_proxy.js +++ b/packages/@ember/-internals/runtime/lib/system/array_proxy.js @@ -6,11 +6,11 @@ import { get, objectAt, alias, - PROPERTY_DID_CHANGE, addArrayObserver, removeArrayObserver, replace, arrayContentDidChange, + arrayContentWillChange, tagForProperty, } from '@ember/-internals/metal'; import { isObject } from '@ember/-internals/utils'; @@ -129,10 +129,6 @@ export default class ArrayProxy extends EmberObject { setCustomTagFor(this, customTagForArrayProxy); } - [PROPERTY_DID_CHANGE]() { - this._revalidate(); - } - willDestroy() { this._removeArrangedContentArrayObserver(); } @@ -256,11 +252,11 @@ export default class ArrayProxy extends EmberObject { let newLength = arrangedContent ? get(arrangedContent, 'length') : 0; this._removeArrangedContentArrayObserver(); - this.arrayContentWillChange(0, oldLength, newLength); + arrayContentWillChange(this, 0, oldLength, newLength); this._invalidate(); - this.arrayContentDidChange(0, oldLength, newLength); + arrayContentDidChange(this, 0, oldLength, newLength, false); this._addArrangedContentArrayObserver(arrangedContent); } @@ -272,7 +268,7 @@ export default class ArrayProxy extends EmberObject { isArray(arrangedContent) || arrangedContent.isDestroyed ); - addArrayObserver(arrangedContent, this, ARRAY_OBSERVER_MAPPING, true); + addArrayObserver(arrangedContent, this, ARRAY_OBSERVER_MAPPING); this._arrangedContent = arrangedContent; } @@ -280,14 +276,14 @@ export default class ArrayProxy extends EmberObject { _removeArrangedContentArrayObserver() { if (this._arrangedContent) { - removeArrayObserver(this._arrangedContent, this, ARRAY_OBSERVER_MAPPING, true); + removeArrayObserver(this._arrangedContent, this, ARRAY_OBSERVER_MAPPING); } } _arrangedContentArrayWillChange() {} _arrangedContentArrayDidChange(proxy, idx, removedCnt, addedCnt) { - this.arrayContentWillChange(idx, removedCnt, addedCnt); + arrayContentWillChange(this, idx, removedCnt, addedCnt); let dirtyIndex = idx; if (dirtyIndex < 0) { @@ -301,7 +297,7 @@ export default class ArrayProxy extends EmberObject { this._lengthDirty = true; - this.arrayContentDidChange(idx, removedCnt, addedCnt); + arrayContentDidChange(this, idx, removedCnt, addedCnt, false); } _invalidate() { @@ -351,10 +347,4 @@ ArrayProxy.reopen(MutableArray, { @public */ arrangedContent: alias('content'), - - // Array proxies don't need to notify when they change since their `[]` tag is - // already dependent on the `[]` tag of `arrangedContent` - arrayContentDidChange(startIdx, removeAmt, addAmt) { - return arrayContentDidChange(this, startIdx, removeAmt, addAmt, false); - }, }); diff --git a/packages/@ember/-internals/runtime/tests/helpers/array.js b/packages/@ember/-internals/runtime/tests/helpers/array.js index 0f5f5008d38..d7181c94813 100644 --- a/packages/@ember/-internals/runtime/tests/helpers/array.js +++ b/packages/@ember/-internals/runtime/tests/helpers/array.js @@ -61,12 +61,18 @@ const ArrayTestsObserverClass = EmberObject.extend({ }, observeArray(obj) { - addArrayObserver(obj, this); + addArrayObserver(obj, this, { + willChange: 'arrayWillChange', + didChange: 'arrayDidChange', + }); return this; }, stopObserveArray(obj) { - removeArrayObserver(obj, this); + removeArrayObserver(obj, this, { + willChange: 'arrayWillChange', + didChange: 'arrayDidChange', + }); return this; }, diff --git a/packages/@ember/-internals/runtime/tests/mixins/array_test.js b/packages/@ember/-internals/runtime/tests/mixins/array_test.js index 49b158919ae..c72c1cfe94b 100644 --- a/packages/@ember/-internals/runtime/tests/mixins/array_test.js +++ b/packages/@ember/-internals/runtime/tests/mixins/array_test.js @@ -193,11 +193,9 @@ moduleFor( // moduleFor( - 'notify array observers', + 'notify array observers (internal)', class extends AbstractTestCase { beforeEach(assert) { - expectDeprecation(/Array observers have been deprecated/); - obj = DummyArray.create(); observer = EmberObject.extend({ @@ -215,7 +213,10 @@ moduleFor( _after: null, }); - addArrayObserver(obj, observer); + addArrayObserver(obj, observer, { + willChange: 'arrayWillChange', + didChange: 'arrayDidChange', + }); } afterEach() { @@ -248,29 +249,16 @@ moduleFor( } ['@test removing array observer should disable'](assert) { - removeArrayObserver(obj, observer); + removeArrayObserver(obj, observer, { + willChange: 'arrayWillChange', + didChange: 'arrayDidChange', + }); arrayContentWillChange(obj); assert.deepEqual(observer._before, null); arrayContentDidChange(obj); assert.deepEqual(observer._after, null); } - - ['@test hasArrayObservers should work'](assert) { - assert.equal( - obj.hasArrayObservers, - true, - 'correctly shows it has an array observer when one exists' - ); - - removeArrayObserver(obj, observer); - - assert.equal( - obj.hasArrayObservers, - false, - 'correctly shows it has an array observer when one exists' - ); - } } ); diff --git a/packages/@ember/-internals/runtime/tests/mutable-array/replace-test.js b/packages/@ember/-internals/runtime/tests/mutable-array/replace-test.js index afa44afd6f2..7b72f9591ba 100644 --- a/packages/@ember/-internals/runtime/tests/mutable-array/replace-test.js +++ b/packages/@ember/-internals/runtime/tests/mutable-array/replace-test.js @@ -240,9 +240,7 @@ class ReplaceTests extends AbstractTestCase { obj.destroy(); } - async '@test Adding object should notify array observer'() { - expectDeprecation(/Array observers have been deprecated/); - + async '@test Adding object should notify array observer (internal)'() { let fixtures = newFixture(4); let obj = this.newObject(fixtures); let observer = this.newObserver(obj).observeArray(obj); diff --git a/packages/@ember/-internals/runtime/tests/system/array_proxy/array_observer_test.js b/packages/@ember/-internals/runtime/tests/system/array_proxy/array_observer_test.js deleted file mode 100644 index 8515d192521..00000000000 --- a/packages/@ember/-internals/runtime/tests/system/array_proxy/array_observer_test.js +++ /dev/null @@ -1,55 +0,0 @@ -import { set } from '@ember/-internals/metal'; -import ArrayProxy from '../../../lib/system/array_proxy'; -import { A } from '../../../lib/mixins/array'; -import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; - -moduleFor( - 'ArrayProxy - array observers', - class extends AbstractTestCase { - ['@test mutating content'](assert) { - expectDeprecation(/Array observers have been deprecated/); - - assert.expect(5); - - let content = A(['x', 'y', 'z']); - let proxy = ArrayProxy.create({ content }); - - proxy.addArrayObserver({ - arrayWillChange(proxy, startIndex, removeCount, addCount) { - assert.deepEqual([startIndex, removeCount, addCount], [1, 1, 3]); - assert.deepEqual(proxy.toArray(), ['x', 'y', 'z']); - }, - arrayDidChange(proxy, startIndex, removeCount, addCount) { - assert.deepEqual([startIndex, removeCount, addCount], [1, 1, 3]); - assert.deepEqual(proxy.toArray(), ['x', 'a', 'b', 'c', 'z']); - }, - }); - - proxy.toArray(); - content.replace(1, 1, ['a', 'b', 'c']); - } - - ['@test assigning content'](assert) { - expectDeprecation(/Array observers have been deprecated/); - - assert.expect(5); - - let content = A(['x', 'y', 'z']); - let proxy = ArrayProxy.create({ content }); - - proxy.addArrayObserver({ - arrayWillChange(proxy, startIndex, removeCount, addCount) { - assert.deepEqual([startIndex, removeCount, addCount], [0, 3, 5]); - assert.deepEqual(proxy.toArray(), ['x', 'y', 'z']); - }, - arrayDidChange(proxy, startIndex, removeCount, addCount) { - assert.deepEqual([startIndex, removeCount, addCount], [0, 3, 5]); - assert.deepEqual(proxy.toArray(), ['a', 'b', 'c', 'd', 'e']); - }, - }); - - proxy.toArray(); - set(proxy, 'content', A(['a', 'b', 'c', 'd', 'e'])); - } - } -); diff --git a/packages/@ember/-internals/utils/lib/ember-array.ts b/packages/@ember/-internals/utils/lib/ember-array.ts index f6b3d68a2b5..b81e3c63e0e 100644 --- a/packages/@ember/-internals/utils/lib/ember-array.ts +++ b/packages/@ember/-internals/utils/lib/ember-array.ts @@ -4,7 +4,6 @@ const EMBER_ARRAYS = new _WeakSet(); export interface EmberArray { length: number; - hasArrayObservers?: boolean; objectAt(index: number): T | undefined; replace(start: number, deleteCount: number, items: T[]): void; splice(start: number, deleteCount: number, ...items: T[]): void; diff --git a/tests/docs/expected.js b/tests/docs/expected.js index 54ac5a51513..961c10486de 100644 --- a/tests/docs/expected.js +++ b/tests/docs/expected.js @@ -58,7 +58,6 @@ module.exports = { 'actions', 'activate', 'adapter', - 'addArrayObserver', 'addListener', 'addObject', 'addObjects', @@ -77,8 +76,6 @@ module.exports = { 'ariaRole', 'arrangedContent', 'array', - 'arrayContentDidChange', - 'arrayContentWillChange', 'assert', 'assertDestroyablesDestroyed', 'assign', @@ -248,7 +245,6 @@ module.exports = { 'handleEvent', 'handleURL', 'has', - 'hasArrayObservers', 'has-block', 'has-block-params', 'hash', @@ -418,7 +414,6 @@ module.exports = { 'reject', 'rejectBy', 'releaseMethods', - 'removeArrayObserver', 'removeAt', 'removeListener', 'removeObject',