Skip to content

Commit

Permalink
[CLEANUP beta] Remove deprecated array observers
Browse files Browse the repository at this point in the history
Part of #19617

* Remove `someArray.addArrayObserver`
  https://github.com/emberjs/ember.js/pull/19833/files#diff-8116921433c6e1664250d5735e4f9daeb6ea9e00940783e0c4db7adf88aa8772L532
* Remove `someArray.removeArrayObserver`
  https://github.com/emberjs/ember.js/pull/19833/files#diff-8116921433c6e1664250d5735e4f9daeb6ea9e00940783e0c4db7adf88aa8772L548
* Remove `someArray.hasArrayObservers`
  https://github.com/emberjs/ember.js/pull/19833/files#diff-8116921433c6e1664250d5735e4f9daeb6ea9e00940783e0c4db7adf88aa8772L571
* Remove `someArray.arrayContentWillChange`
  https://github.com/emberjs/ember.js/pull/19833/files#diff-8116921433c6e1664250d5735e4f9daeb6ea9e00940783e0c4db7adf88aa8772L642
* Remove `someArray.arrayContentDidChange`
  https://github.com/emberjs/ember.js/pull/19833/files#diff-8116921433c6e1664250d5735e4f9daeb6ea9e00940783e0c4db7adf88aa8772L670
  (also at
  https://github.com/emberjs/ember.js/pull/19833/files#diff-79a0015ca0e6a6c3661d9119f4caf6b26b111af6bb0e5bb784558631c599a81cL357)
* On the internal version of `addArrayObserver`, remove the optionality
  of `willChange` and `didChange` arguments to name the hooks. In
  framework code (and now in test) these are always provided.
* Retain basically all the test coverage of array observers since they
  remain used by `ArrayProxy` internally (and through that interface, in
  Ember Data).

Co-authored-by: Matthew Beale <[email protected]>
  • Loading branch information
kategengler and mixonic committed Nov 12, 2021
1 parent 88b354e commit a9aedea
Show file tree
Hide file tree
Showing 9 changed files with 50 additions and 360 deletions.
83 changes: 24 additions & 59 deletions packages/@ember/-internals/metal/lib/array.ts
Original file line number Diff line number Diff line change
@@ -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<T> extends EmberArray<T> {
_revalidate?: () => void;
}

export function objectAt<T>(array: T[] | EmberArray<T>, index: number): T | undefined {
Expand Down Expand Up @@ -58,82 +56,49 @@ export function replaceInNativeArray<T>(
}

interface ArrayObserverOptions {
willChange?: string;
didChange?: string;
willChange: string;
didChange: string;
}

type Operation = (
obj: ObjectHasArrayObservers,
type Operation<T> = (
obj: ObservedObject<T>,
eventName: string,
target: object | Function | null,
callbackName: string
) => void;

function arrayObserversHelper(
obj: ObjectHasArrayObservers,
function arrayObserversHelper<T>(
obj: ObservedObject<T>,
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<T>
): ObservedObject<T> {
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;
}

export function addArrayObserver<T>(
array: EmberArray<T>,
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<T> {
return arrayObserversHelper(array, target, opts, addListener);
}

export function removeArrayObserver<T>(
array: EmberArray<T>,
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<T> {
return arrayObserversHelper(array, target, opts, removeListener);
}
203 changes: 1 addition & 202 deletions packages/@ember/-internals/runtime/lib/mixins/array.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
Loading

0 comments on commit a9aedea

Please sign in to comment.