directives that have '=' binding, changing scope value causes error if not specified(optional) #1435

Closed
johnnyelwailer opened this Issue Oct 3, 2012 · 30 comments

Projects

None yet
@johnnyelwailer

If I have a directive with a scope definition, and setting a value in the scope:

directive('some', function () {
  return  {
    scope: {optionalValue: '='},
    bind: function(scope) {
      scope.optionalValue = something;
    }
  }
}

And create it like

<some />

With optionalValue not specified, it yields the NON_ASSIGNABLE_MODEL_EXPRESSION error.
But I'd expect it to work in this case.

This could be solved in the "scope mode parser" by simply not applying the binding:

case '=': {
  if (attrs[attrName] == null) {
    return;
  }
   ...

Or is this intentionally? If so, maybe add a new symbol like '~' for optional attributes?

(just realized that I opened essentially the same issue 3 months ago: #1131)

@NickHeiner

I would like to see any easy way for optional arguments / argument defaults on directive attributes as well.

@pkozlowski-opensource

Created a jsFiddle to expose the same issue with the latest version of AngularJS: http://jsfiddle.net/yA2rn/
The use case seems to be a valid one...

@presstube

Just chiming in that this is something that would be very useful to me as well. thanks

@bendalton

ditto.

@fx
fx commented Dec 13, 2012

+1

@ryanzec
ryanzec commented Dec 24, 2012

I also think this would be very useful. Right now I am doing this in my directives:

var optionalParameters = scope.$eval(attributes.optionalParameters);

which works ok if I don't need bi-directional binding with the parent scope.

@tschieggm

+1

@lrlopez lrlopez added a commit to lrlopez/angular.js that referenced this issue Jan 26, 2013
@lrlopez lrlopez feat(compile): '=' binding can now be optional
If you bind using '=' to a non-existant parent property, the compiler
will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception, which is right
because the model doesn't exist.

This enhancement allow to specify that a binding is optional so it
won't complain if the parent property is not defined. In order to mantain
backward compability, the new behaviour must be specified using '=?' instead
of '='. The local property will be undefined is these cases.

Closes #909
Closes #1435
c07c4b8
@just-boris

That's good. Now, I make optional binding like this:

link: function(scope, element, attrs) {
    if (attrs.selected) {
        getSelected = $parse(attrs.selected);
        setSelected = getSelected.assign;
        scope.$watch(
            function watchSelected() {
                 return getSelected(scope.$parent); 
            },
            function updateSelected(value) {
                if(setSelected) {
                     setSelected(scope.$parent, value);
                }
                scope.selected = value;
            }
        );
        scope.selected = getSelected ? getSelected(scope.$parent) : false;
    }
}

This code can be replaced by simple config

scope: {
    selected: "=?"
}
@just-boris just-boris referenced this issue in angular-ui/bootstrap Jan 28, 2013
Closed

tab selection from controllers #87

@roypeled
roypeled commented Feb 3, 2013

Please merge lrlopez's fix with the master build!

@Mischi
Mischi commented Feb 14, 2013

+1

@aaronwhite

+1

@marcospassos

+1

@IgorMinar IgorMinar added a commit that closed this issue Feb 25, 2013
@lrlopez lrlopez feat($compile): '=?' makes '=' binding optional
If you bind using '=' to a non-existant parent property, the compiler
will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception, which is right
because the model doesn't exist.

This enhancement allow to specify that a binding is optional so it
won't complain if the parent property is not defined. In order to mantain
backward compability, the new behaviour must be specified using '=?' instead
of '='. The local property will be undefined is these cases.

Closes #909
Closes #1435
ac899d0
@IgorMinar IgorMinar closed this in ac899d0 Feb 25, 2013
@yourilima yourilima referenced this issue in angular-ui/ui-calendar Apr 11, 2013
Closed

exposure and binding #13

@rgraffconnect

I'm seeing this issue in 1.0.7 and getting an error when using =?

Error:
Invalid isolate scope definition for directive ...: =?

Is this issue back!??

@lrlopez
lrlopez commented Aug 29, 2013

This feature was merged into the 1.1.x branch so it's not available in 1.0.x, sorry...

@rgraffconnect

Oh ok, thank you.

On Aug 29, 2013, at 5:40 PM, Luis Ramón López notifications@github.com wrote:

This feature was merged into the 1.1.x branch so it's not available in 1.0.x, sorry...


Reply to this email directly or view it on GitHub.

@SimplGy
SimplGy commented Mar 12, 2014

Understanding that it works to use the syntax = for a required 2-way binding and =? for an optional 2-way binding, I wonder why both are needed.

Could they always be optional? This makes the API simpler. Is there a performance issue there?

@just-boris

@SimpleAsCouldBe conversely, performance would be improved if the required binding will be removed, because regexp will become simpler.
So I agree with you.

@just-boris

Someone from @angular, please take attention for this question

@klamping klamping referenced this issue in rackerlabs/encore-ui Mar 19, 2014
Closed

Rxnav fix #101

@SimplGy
SimplGy commented Jul 22, 2014

We might want to make this a separate feature request, @just-boris. Succinctly, the request is:

"Make = an optional binding all the time"

Wondering about performance and structure implications. Also maybe some folks depend on the enforcement of this.

I tend to think the better default is "just work/silently fail", sort of like exceptions in templates ({{ myUndefinedObject.name }}) don't throw. If enforcement is wanted syntax like =! or === could opt-in.

@wbeange
wbeange commented Aug 19, 2014

Thanks

@bernhard-hofmann

If the parent scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You can avoid this behavior using =? or =?attr in order to flag the property as optional.

The above was quoted from the $compile documentation.

I see it's been mentioned before, but after that there are more people calling for this feature who might only read the first and last post.

@caitp
caitp commented Sep 23, 2014

it won't throw if the parent scope property doesn't exist --- it will throw if the expression is just not assignable (for instance, foo.bar is assignable, while foo.bar() is not) --- need an lvalue

@joshribakoff

@bernhard-hofmann Thanks for the explanation, this fixed it for me.

To improve the documentation, you should know that I think the users are confused (I was at least) because many guides including the official one (https://docs.angularjs.org/guide/directive) mention '&', '@', and '=' syntax... (which is already confusing)... no guides that I read mention '=?' syntax. Nothing would have prompted me to look into the documentation for $compile since I'm not touching that service with my code.

@BorisKozo

Agree with @joshribakoff , I would never found this in the docs unless someone from my team pointed me to this thread.

@LeonardoGentile

@just-boris I can't use angular 1.1.x so I'm using your approach.

The problem is that I can't make a two ways binding with your approcah meaning that when I change the scope variable in the directive it doesn't get updated in the container controller. Would you clarify a little bit your solution ?
It's not very clear, I'm missing something..

@just-boris

@LeonardoGentile updated my code snippet, now it has a call of setSelected(scope.$parent, value);
But there is already three major releases, so I advice you to update

@LeonardoGentile

Thanks @just-boris but that did not work for me...I had to make a little modification:

link: function(scope, element, attrs) {
    if (attrs.selected) {
        getSelected = $parse(attrs.selected);
        setSelected = getSelected.assign;
        scope.$watch(
            function watchSelected() {
                 return getSelected(scope.$parent); 
            },
            function updateSelected(value) {
                scope.selected = value
            }
        );
        scope.selected = getSelected ? getSelected(scope.$parent) : false;
    }
    scope.$watch('selected', function(value) {
        if(setSelected) {
            setSelected(scope.$parent, value)
        }
    })   
}
@marsechelon

+1

@Narretz
Collaborator
Narretz commented Aug 29, 2016

@marsechelon I don't know why you are +1 this as optional bindings have been in Angular since 1.1.x.

@Narretz Narretz locked and limited conversation to collaborators Aug 29, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.