-
Notifications
You must be signed in to change notification settings - Fork 27.4k
fix(ngRepeat): support complex assignable aliasAs expressions #8440
Conversation
@fullName Non-assignable Expression | ||
@description | ||
|
||
Occurs when there is a syntax error in an {@link ng.directive:ngRepeat ngRepeat} expression which is expected to be assignable.= |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
spurious =
sign at the end of this line ?
Other than the error docs changes I think this looks good.
|
I'm not 100% sure that it is a good idea to fix this. I mean, I'd like to think of this alias as an intermediate variable that does not exist outside of the scope of |
#8398 wouldn't make it impossible, it would just mean the value assigned and scope of assignment would be different. This CL wouldn't cause any issues with that. I'm also not sure it's a problem to assign it to a controller, or a nested object --- but folks opinions are appreciated. |
Also, this has nothing whatsoever to do with assignment operator in expressions, so it's really not clear what you're even talking about there =P |
I believe the primary use case is to assign it to a Controller that was created with |
You wouldn't be able to assign it to the controller unless it was created with "controller as", since it assigns in the context of scope... |
@caitp - what @shahata is saying is that we are trying to get away from writing to the scope in templates (i.e. the assignment operator in expressions: |
@petebacondarwin the thing is, that's irrelevant here --- such an expression would throw a minErr (due to not being assignable) --- yes, we can assign properties to scope, but we're doing that anyways --- the only difference is that this means we can assign properties to objects in scope rather than just to scope directly. That point seems to be missed --- this is no different from just writing to $scope[aliasOf], except that it supports nested objects --- there are no real side effects here, because we specifically require an assignable expression --- no side affects that didn't exist originally (like the ability to overwrite some value) |
The alias must be stored on some scope (preferably on the child scopes imo), that's just how things work and there's not a lot we can do about that. But, I think allowing to put the alias in a nested object or on the controller will only encourage people to access it from their JS code, which seems just wrong. The thing is I don't really see the advantage of allowing this, maybe I'm missing something - When do you think someone might have a good reason to put the alias on some nested object? |
@caitp - You are right that you can use @shahata - the use case is |
I agree it is a bit messy and I wouldn't do it personally. In fact there are very few scenarios where I would use |
How does this seem wrong? We don't care if people access it from their JS code, it does not matter |
Literally the only difference this makes is that, Instead of Instead of creating |
None of this does "more" to "encourage people to use these collections from JS" --- the collections were exposed to JS already. None of this encourages people to do things they shouldn't, it just gives a minor convenience and prevents people from doing things they don't expect it to do. The argument that this is harming users is completely bogus to me, and we need to do better than that to avoid this, IMO. |
For what it's worth, I do agree that the alias should go into child scopes rather than the containing scope, that would be ideal. We could also blacklist the expression if it contains However, I don't think we should stop people from putting the value where they expect to put it. If you're trying to assign the value to Foo.bar, it had better go to ['Foo']['bar'] and not ['Foo.bar'] |
I agree 100% it should go into child scopes instead, but then we don't need this PR imo. The alias won't be any different from the single array item that is put on the child scope. Then we will also not need to worry about |
This is what I disagree with --- It's unexpected that If you want to prevent people from using |
Yes, that's why y'all are CC'd on this bug, to figure out the disaster points =) |
But if we have something like |
I mean we would like |
not if we assign an empty object to the first key of the expression |
yes, but then you won't be able to access anything in your controller inside the ng-repeat... |
so what? |
If I have |
and... so what! you aren't obligated to put the alias there. We could also just always call it $collection in child scopes, regardless of the alias |
I don't think anyone who doesn't really understand the internals of angular and prototypical inheritance would expect this to happen, and I don't believe there is anyone who would actually WANT this to happen. |
We should definitely not be adding the filtered items alias in the childscopes. |
@petebacondarwin the main reason it was asked for in the child scopes is so that a context for $index and etc would be added to an aliased property, to make nested repeaters easier to deal with (parentAlias.$index vs childAlias.$index) However, there are ways around this with the |
@petebacondarwin - see #8398 |
As far as this PR goes, I think it is OK. I think we could merge it. |
@shahata - I put my comments on that issue already |
Definitely need the alias on the parent scope, as that enables you to do things like show / hide an "add item" button if no items, etc. For me, that is the only place it is really needed. As far as putting it on the The reason for the request was that |
' <div ng-repeat="item in items | filter:x as foo=6 track by $index">{{item}}</div>' + | ||
'</div>')(scope); | ||
expect($exceptionHandler.errors.shift()[0].message). | ||
toMatch(/^\[ngRepeat:nonassign\] Expected collection alias to be an assignable expression but got \'foo=6\'\./); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use toThrowMinErr
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because $exceptionHandler causes problems for this, and it would make the test a lot bigger to reconfigure $exceptionHandler to rethrow instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can do it, but jus'sayin, other tests in this suite are using the same strategy here because of this
…s as a scope member ngRepeat can now alias the snapshot of the list of items evaluated after all filters have been applied as a property on the scope. Prior to this fix, when a filter is applied on a repeater, there is no way to trigger an event when the repeater renders zero results. Closes #5919 Closes #8046 Closes #8282
Another important reason this is needed is when you want to put the alias in a higher level scope as described in e0adb9c#commitcomment-7254841 (much more convincing then |
ngRepeat's As far as ngRepeat goes assigning filtered collection to upper scopes could leak to similar leaks. Assigning the result to controller instances is also a bit weird use-case. ngModel is special because of two-way data-binding. How about instead of checking if the alias is assignable we check if it matches a regular expression for a valid identifier? that way we provide good error reporting (that's what triggered this PR in the first place) and keep the door open for making this assignable in the future after we observe the real-world usage and find that making the expression assignable would outweight all the concerns about memory-leaks and spaghetti code that have been raised in this discussion? btw I don't like the idea of conflating the alias with a context for |
okay, we could make the regexp more strict, but I expect we'd run into issues in the future (as we do for other identifiers) due to people wanting to use non-latin characters |
Using the assignable strategy would not help to deal with the non-ident
|
Ensure that aliasAs expressions are valid simple identifiers. These are still assigned to $scope in the same way that they were previously, however now you won't accidentally create a property named "filtered.collection". This change additionally restricts identifiers to prevent the use of certain ECMAScript reserved words ("null", "undefined", "this" --- should probably add "super", "try", "catch" and "finally" there too), as well as certain properties used by $scope or ngRepeat, including $parent, $index, $even, $odd, $first, $middle, or $last. Fixes angular#8438
I've changed this in order to make it a bit more strict and help prevent people from doing weird things |
I'd like to be able to assign the results to a property inside an object that uses two-way data binding in a directive: angular.module("directives").directive("myDirective", function () {
return {
scope: {
myObject: "="
},
templateUrl: "myDirective.html"
};
}); <input type="text" ng-model="searchText"><input>
<div ng-repeat="item in items | filter:searchText as myObject.filteredItems"><div> So that the client of this directive has access to the list of filtered items through But the current implementation of |
You can't, Igor didn't like this because it would let people move alias'd collections into places where they'd be kept alive outside of the lifetime of the ngRepeat --- and thus be potentially leaky. Sorry :( (not that you can't technically do this anyways, but it's not a feature of ngRepeat, basically) --- the original implementation of this PR would have let you do that, but it was changed to limit you to simple identifiers rather than things which look like they'd be properties of other objects --- so that you don't accidentally create properties like "foo.bar" or whatever. |
I know, I read the whole thread 😃 I just thought I'd share my real-world use case since Igor said:
Thank you for your quick answer! EDIT: Would it be too counter intuitive to allow |
Parse aliasAs as an expression, and assert that the expression is assignable.
Just throwing together a quick fix, but I'm not sure this is what we want to do, especially since we
want to change the behaviour of aliasAs to provide more data. /cc @matsko / @shahata / @petebacondarwin
BREAKING CHANGE
Previously, any name passed as an expression would make up a single property
name, including constant values such as 1, NaN, null, undefined, or even
expressions such as function calls or boolean expressions.
Now, more complex expressions are possible, allowing the collection alias to
be assigned as a property of an object --- however, if the expression is not
determined to be assignable, it will throw.
Fixes #8438