Skip to content
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

[CLEANUP beta] Remove deprecated array observers #19833

Merged
merged 1 commit into from Nov 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
83 changes: 24 additions & 59 deletions 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<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?.();
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this patch hasArrayObserver has been removed. That property was not directly deprecated in 3.x, however we've removed a number of APIs in this patch which were related to someArray.addArrayObserver (APIs which were only meaningful once you had an observer installed).

Because it is removed, it no longer needs to be notified of change. However the _revalidate hook on ArrayProxy instances was being called via PROPERTY_DID_CHANGE symbol notification. Without trying to rewrite that system we have two options: Either call _revalidate() directly, or keep the property hasArrayObserver around just to trigger the PROPERTY_DID_CHANGE. Here I've chosen to call _revalidate() directly.


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
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