Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

feat(isArray): make angular.isArray support Array subclasses #15541

Closed
wants to merge 1 commit into from

Conversation

thorn0
Copy link
Contributor

@thorn0 thorn0 commented Dec 22, 2016

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)

feature

What is the current behavior? (You can also link to an open issue here)

Angular doesn't recognise objects prototypically inherited from Array.prototype as arrays.
See #15533

What is the new behavior (if this is a feature change)?

In particular, this change gets Angular to play nicely with MobX observable arrays.

Does this PR introduce a breaking change?

Yes. angular.isArray is used throughout Angular. In particular, after this change, angular.copy will use the array-cloning logic for objects prototypically inherited from Array.prototype whereas now it uses the object-cloning logic for them. This, in turn, affects deep watchers and other parts of the framework that use angular.copy.

Please check if the PR fulfills these requirements

@thorn0
Copy link
Contributor Author

thorn0 commented Dec 22, 2016

Please consider merging this into the 1.5 branch too.

@gkalpak
Copy link
Member

gkalpak commented Dec 22, 2016

Please consider merging this into the 1.5 branch too.

1.5? I am even skeptical about 1.6. This is a BC, I'm afraid.

Copy link
Member

@gkalpak gkalpak left a comment

Choose a reason for hiding this comment

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

LGTM (for 1.7.x)
Could you expand the BC notice a little to explain what other things might be affected (apart from direct angular.isArray(...))? E.g. watchers, copying etc.

@@ -232,8 +232,7 @@ function isArrayLike(obj) {

// NodeList objects (with `item` method) and
// other objects with suitable length characteristics are array-like
return isNumber(length) &&
(length >= 0 && ((length - 1) in obj || obj instanceof Array) || typeof obj.item === 'function');
return isNumber(length) && (length >= 0 && (length - 1) in obj || typeof obj.item === 'function');
Copy link
Member

Choose a reason for hiding this comment

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

We are relaxing the condition a bit, but it is OK imo.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

isArray is used in a couple of lines above. So if obj instanceof Array, the function hits return on that line.

Copy link
Member

@gkalpak gkalpak Dec 22, 2016

Choose a reason for hiding this comment

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

I know. But previously there was also the restriction that isNumber(length) and length >= 0, which is gone now.

What I am saying is that isArrayLike (and anything depending on it) is slightly affected by the change in isArray.

*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is an `Array`.
*/
var isArray = Array.isArray;
function isArray(arr) {
return arr instanceof Array || Array.isArray(arr);
Copy link

Choose a reason for hiding this comment

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

Array.isArray(arr) is unreachable here, since by definition any array exotic object is instanceof Array.

Copy link
Member

Choose a reason for hiding this comment

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

Not if it comes from a different context (e.g. another Window object). E.g.:

var win = window.open();
var arr = new win.Array();

console.log(arr instanceof Array);   // false
console.log(Array.isArray(arr));   // true

Copy link

Choose a reason for hiding this comment

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

good call

@gkalpak
Copy link
Member

gkalpak commented Dec 22, 2016

BTW, in anyone wanted to use an evil and totally not recommended hack for 1.5/1.6, they could surround the angular.js script with code that modified Array.isArray, let Angular store it locally, then restore the original method (bad bad demo).

@thorn0
Copy link
Contributor Author

thorn0 commented Dec 22, 2016

Could you expand the BC notice

Here in PR? Or in the commit message?

BTW, in anyone wanted to use an evil and totally not recommended hack

I'm just patching angular.js locally :)

@gkalpak
Copy link
Member

gkalpak commented Dec 22, 2016

I'm just patching angular.js locally :)

I know 😉

@petebacondarwin petebacondarwin modified the milestones: Backlog, 1.7.0 Oct 2, 2017
@Narretz
Copy link
Contributor

Narretz commented Nov 30, 2017

@thorn0 the BC change message still needs to be updated as @gkalpak has requested for this to land in 1.7.0

@thorn0 thorn0 force-pushed the isArray-instanceof branch 5 times, most recently from 9f56892 to a787bb4 Compare November 30, 2017 17:27
@thorn0
Copy link
Contributor Author

thorn0 commented Nov 30, 2017

@Narretz done

@Narretz
Copy link
Contributor

Narretz commented Dec 1, 2017

Cool! Can you please add that this change also affects angular.merge, angular.forEach, and angular.equals?

@thorn0 thorn0 force-pushed the isArray-instanceof branch 3 times, most recently from 3bbf03f to 5dd17e3 Compare December 1, 2017 17:07
@thorn0
Copy link
Contributor Author

thorn0 commented Dec 1, 2017

@Narretz done. Travis' error messages are irrelevant.

@Narretz
Copy link
Contributor

Narretz commented Dec 4, 2017

LGTM. Is that okay for you @gkalpak ?

@gkalpak
Copy link
Member

gkalpak commented Dec 4, 2017

I would like to see a more explicit mention of how the implementation changed/what "objects that weren't previously recognized as arrays [...] are now recognized as such". Just a brief notice that objects that prototypally inherit from Array are now recognized as arrays.

Closes angular#15533

BREAKING CHANGE:

`angular.isArray` is used by `angular.copy`, which in turn is used internally by the dirty
checking logic. That's why this change affects the way objects are copied and watched by AngularJS.
Objects that prototypally inherit from `Array` (e.g. MobX observable arrays, see angular#15533) weren't
previously recognized as arrays, now they are. This change also affects `angular.merge`,
`angular.forEach`, and `angular.equals`.

Previously, `angular.isArray` was an alias for and thus worked exactly as `Array.isArray`.
@gkalpak
Copy link
Member

gkalpak commented Feb 1, 2018

Tweaked the commit message, added some more tests and landed.
Thx for working on this, @thorn0 👍

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants