-
Notifications
You must be signed in to change notification settings - Fork 27.5k
ngModel is locked into the isolate scope when used on a component #1924
Comments
btw the foodme app that we created as a tutorial app suffers from this issue: https://github.com/IgorMinar/foodme/blob/master/app/views/restaurants.html#L13 |
Another drawback: the use-site has to write $parent.my.model, so if you want to provide a re-usable component, the implementation of it leaks to all users of the component :-( |
@IgorMinar I just ran into this issue myself trying to create an angular component. Isolate scope seems to be by-far the most idiomatic looking way of writing directives but nearly 100% of the time the isolate-ness has been too agressive and made it impractical to use. Really frustrating. It seems like the problem is a design one, not a bug or anything. Any advice regarding future plans for isolate? |
FYI, when I ran into this problem, I used < ng-model="portal.myVar" portal="parentScopeVar" > Which then made changes to (in the parent scope): parentScopeVar.myVar Not a particularly elegant workaround, but it worked nicely, at least. |
I just ran into this again. Hard. Gah... this issue stands in the way of a cow-path I'm afraid. Its pretty critical to creating angular components. @mhevery @IgorMinar Could
There must be a better way than ^ though. |
i have a suggestion. I believe all that must be done is to provide a way to the user instruct a bi-directional bind without specifying the local property name, but the name of attribute that contains the local property name. Look, this is going to fail: <my-component ng-model="my.model"></my-component> scope: {
ngModel: '=ngModel'
} Currently, we are saying that when the user changes $scope. ngModel we'll reflect to $scope.$parent.my.model. All we got to do is to provide an attribute binding, something like this: scope: {
'=ngModel': '=ngModel'
} So instead of binding I can try to implement it if Angular team approve this approach. |
in the input.js we have: var ngModelGet = $parse($attr.ngModel), maybe explicity modifing the ngModelCtrl by asking for them in the link of the component so you can make something like auto binding at the parser everything the model is setting/getting |
This is the workaround I currently use on my directive: I got the original idea form here: |
@dobladez 's workaround has been useful for me too. |
Please checkout this solution |
As is often the case with Angular, the easiest work-around is simple element nesting. <div ng-model="">
<my-component />
</div> .directive('myComponent', function () {
return {
scope: {},
require: '^ngModel'
/* ... */
};
}); |
@hon2a yeah! it is but not it enough elegant |
Yup from : http://stackoverflow.com/a/15272359/390330 scope: {
model: '=ngModel'
} And then in your linking function keep both in sync: // Bring in changes from outside:
scope.$watch('model', function() {
scope.$eval(attrs.ngModel + ' = model');
});
// Send out changes from inside:
scope.$watch(attrs.ngModel, function(val) {
scope.model = val;
}); The solution by @inzurrekt is nice |
I'd still rather to have a native solution, that would make this sync transparent to the developer, making it eligible to further optimizations out of the box. So I still think an additional signature would solve this problem, like here #1924 (comment) Basically, allowing the user to bind to the same value as the attribute. This have a bonus of working for any other attribute we might need. |
@caiotoon I agree, that would be the ultimate solution. But I would prefer a syntax more consistent with what's already there i.e. <my-component ng-model="my.model"></my-component> scope: {
model: '^ngModel'
} From an implementation logic point of view it would mean that if you (within the directive) do: scope.model = 123; that would set the following for you, automatically, using $parse internally:
Additionally with |
After a lot of thought. The simplest solution seems to be the one offered by @inzurrekt here: #2904 Basically have a $setContext function on ngModel: ngModel.$setContext(scope.$parent); The reason why this function is required is $scope is available via closure (and not a member of ngModel) so its not easily changable otherwise. |
Simple modification in AngularJSBased on @inzurrekt idea it can be solved with a simple $setScope function in ngModelController: https://github.com/basarat/foodme/blob/master/app/lib/angular/angular.js#L11667 this.$setScope = function(context) {
$scope = context;
} You can see the pull request: #3148 Given that above is present in AngularJSYou can see that the directive no longer needs $parent in the html : <fm-rating ng-model="filter.rating"></fm-rating> All you need to do in your directive definition is set the scope for ngModel: // Move scope to parent for isloate scope ngModels
ngModel.$setScope(scope.$parent); And you can see the application still works : https://github.com/basarat/foodme |
Since that pull request would probably (and validly since it only fixed ngModel) get rejected, here are some other ideas. Solution 2 is more work but backward compatible. Solution 1 is simpler but backward incompatible (and I feel a better solution overall) Solution 1If the directive has an isolate scope and requires a controller scope :{},
require: '?ngModel', At the point where a required controller gets instantiated and a scope gets injected. The injector should pass scope.$parent automatically to (in this case) the ngModelController (or whatever else is required). This would be different behaviour from what is there right now but I feel the current behaviour is actually not functional. However if you want the current behaviour to continue (e.g. to not break people that used $parent. in the expression) then having a generic method to support the a scope change in the injected directive controllers would be required (my pull request only fixed ngModel). Which brings me to: Solution 2Building on previous suggestion of mine: scope :{},
require: '=ngModel',
Would the angular team agree on using something like this? |
Maybe the solution (just for NgModel) is more simple. I thinked it a long time and finally can say there is no reason to use an ngModel for the isolated properties of an input, but to use for connect an "input Module" with a data model... in other words when you have an ngModel on the same node of an isolated scope directive we can assume you want to evaluate on the parent |
Looks like this issue has been open for half a year. Any ideas for viable solutions in the near term? |
@pheuter The short term solution I copied out : #3148 was horrible. Sure it makes ng-model work but for ng-change / others you still need to do $parent, if you use them on the same element as the directive. My new potential recommendation:Basically a new option (perhaps called The location to modify would be around: https://github.com/angular/angular.js/blob/master/src/ng/compile.js?source=cc#L934-L1017 |
@basarat, one of the premises is that we have only one scope per element, acting as a common medium for sharing messages, and thus, only one scope shared among all directives of a specific element. This change would also demand rethinking and maybe changing and redesigning As we probably will need to address mixed cases (isolated and mixed properties), maybe the best solution is still to have a way of defining the two-way binding in a per property way. |
I've been bugged by this issue for the longest time. It's really annoying and it basically means you cannot easily write standalone components in angular. I think a possible solution is to simply create a shared scope for an element if there is at least one directive If backwards compatibility is not an issue, then the easiest would be to always created a shared and an isolate scope for a directive that requiring a scope. This way, each directive would have a way to store private scope data and public scope data. Would something like this not work or is there something fundamentally wrong with angular that prevents this issue from being addressed? I had really hoped this would be fixed for 1.2 |
This is not only an issue with ngModel, but with several other directives as well. Like ngShow, which has a similar thread going: #2500 In my mind, bindings placed on an element with an isolate scope should just bind to the parent scope. That's it. Why else would you be putting ngModel or ngShow on the element? No one wants them to bind to the isolate scope. The real question to ask is if there a compelling reason NOT to have it this way? |
👍 This does seem only logical. I'm with @kevinsbennett with this one so far. I can't come up with a scenario where that is not the case (then again, I didn't really give it that much of a thought). Any one? God knows the shameful hacks I've made to components with isolated scopes work together with other directives :/ |
I was just confused by this issue. I don't have anything to contribute re a solution, but am watching :) |
Until there is no proper solution for that problem, I also use the workaround 'dobladez' described above. // Bring in changes from outside: scope.$watch('model', function() { scope.$eval(attrs.ngModel + ' = model'); }); // Send out changes from inside: scope.$watch(attrs.ngModel, function(val) { scope.model = val; }); The synchronization with the two $watch-functions works well, but if you use an array-value as the
When I do this, I get an excpetion when the first $watch-function evalues the expression: TypeError: Cannot set property '0' of undefined at Function.extend.assign (http://localhost:9000/basTestClient/app/components/angular/angular.js:6518:59) at http://localhost:9000/basTestClient/app/components/angular/angular.js:6368:21 at Object.$get.Scope.$eval (http://localhost:9000/basTestClient/app/components/angular/angular.js:8218:28) at Object.fn (http://localhost:9000/basCore/services/util/basDirectiveUtil.js:20:23) at Object.$get.Scope.$digest (http://localhost:9000/basTestClient/app/components/angular/angular.js:8097:27) at Object.$get.Scope.$apply (http://localhost:9000/basTestClient/app/components/angular/angular.js:8304:24) at done (http://localhost:9000/basTestClient/app/components/angular/angular.js:9357:20) Does anybody have a solution for that problem? |
Would a directive for ng-Model-type binding on isolate scopes be an I called it ngModelIsolate and an example of it can be found here: -MikeMac On Tue, Oct 8, 2013 at 5:05 AM, anagelcg [email protected] wrote:
|
I created an See sample app : http://basarat.github.io/demo-angularjs-easeljs/ |
Sometimes, a widget directive needs to work with minimum effort of the developers who use it. Also it needs to have its own isolated scope to maintain its state. for example:
change to
to add the delayed execution behavior. It would be too much to modify the ng-model or add additional attribute for the obj.attr workaround. here is the workaround I came up with:
sample test here: |
this creates some weird things: http://plnkr.co/edit/7bad6WxZ8H18XJTAqOK3 |
Awesome guys Thank you! Time for real components now ;) |
This is really good news. I do have one question: what is the relationship between an isolate directive and a directive with shared scope on the same element? |
Does this mean it's going into 1.2 stable when it rolls out? Pretty -MikeMac On Fri, Nov 8, 2013 at 8:16 AM, spamdaemon [email protected] wrote:
|
@MikeMcElroy 1.2 is out and this commit is a part of the changelog : https://github.com/angular/angular.js/blob/master/CHANGELOG.md I thinks it's awesome :) |
@spamdaemon same as the relationship between isolate scope and the scope of a parent element. |
Fixes issue with isolate scope leaking all over the place into other directives on the same element. Isolate scope is now available only to the isolate directive that requested it and its template. A non-isolate directive should not get the isolate scope of an isolate directive on the same element, instead they will receive the original scope (which is the parent scope of the newly created isolate scope). Paired with Tobias. BREAKING CHANGE: Directives without isolate scope do not get the isolate scope from an isolate directive on the same element. If your code depends on this behavior (non-isolate directive needs to access state from within the isolate scope), change the isolate directive to use scope locals to pass these explicitly. // before <input ng-model="$parent.value" ng-isolate> .directive('ngIsolate', function() { return { scope: {}, template: '{{value}}' }; }); // after <input ng-model="value" ng-isolate> .directive('ngIsolate', function() { return { scope: {value: '=ngModel'}, template: '{{value}} }; }); Closes angular#1924 Closes angular#2500
Fixes issue with isolate scope leaking all over the place into other directives on the same element. Isolate scope is now available only to the isolate directive that requested it and its template. A non-isolate directive should not get the isolate scope of an isolate directive on the same element, instead they will receive the original scope (which is the parent scope of the newly created isolate scope). Paired with Tobias. BREAKING CHANGE: Directives without isolate scope do not get the isolate scope from an isolate directive on the same element. If your code depends on this behavior (non-isolate directive needs to access state from within the isolate scope), change the isolate directive to use scope locals to pass these explicitly. // before <input ng-model="$parent.value" ng-isolate> .directive('ngIsolate', function() { return { scope: {}, template: '{{value}}' }; }); // after <input ng-model="value" ng-isolate> .directive('ngIsolate', function() { return { scope: {value: '=ngModel'}, template: '{{value}} }; }); Closes angular#1924 Closes angular#2500
thanks @pheuter for suggesting $parent thing! but I still have problem using $parent in typeaheads that I have in my templates. ng-change works fine on all inputs checkboxes but not on typeahead! |
Is this even made to final commit or has been removed as of 1.5.6? |
@NavnathKale This was never included. I've never seen it before, either. |
When ngModel directive is used on an element that represents a component (implemented via a directive with isolate scope), ngModel is locked into this isolate scope and in order to get out and make ngModel useful the ngModel expression has to be prefixed with $parent.
so instead of:
one has to write:
this is non-intuitive and might be hard to debug for developer unfamiliar with what's going on.
the solution must however take into account that it's possible to use ngModel within the template of the isolated component, in which case we must not leak the model outside of the component.
The text was updated successfully, but these errors were encountered: