AngularJS scoped directive - do I really need to call $ parent everywhere?

I have an angular.js directive to create a button (hover classes, left and right icons, etc.). I am using auto binding for left and right buttons icons through scope: { uiButtonIconLeft: '@', uiButtonIconRight: '@' }

so that I can bind these values ​​to data from the parent area. This, however, causes angularjs to create an "isolation" scope, which means using my directive does not work in a situation like this:

<div ng-controller='someController'>
    <a ng-repeat='thing in things'
       ui-button
       ui-button-icon-left='{{thing.icon}}'
       ng-click='someMethodTheControllerPutOnTheScope(thing.id)'
       >
       I don't work, don't bother clicking me
    </a>
</div>

      

Instead, I have to do this:

<div ng-controller='someController'>
    <a ng-repeat='thing in things'
       ui-button
       ui-button-icon-left='{{thing.icon}}'
       ng-click='$parent.someMethodTheControllerPutOnTheScope($parent.thing.id)'
       >
       Holy leaky abstractions, Batman, it works!
    </a>
</div>

      

My question is: is this idiomatic? Should it be? Am I doing it wrong? Can our hero clean up unnecessary, repetitive, annoying additional $parent.<whatever>

in his markup?

EDIT

The answer to my "widget" widget is to avoid using the marquee and watch the attribute values ​​for the left and right icons using attributes.$observe(...)

instead of snapping through the marquee.

+3


source to share


4 answers


There is a way to do this without using explicit scoping, if you are writing a directive that does not commit itself to specific elements in scope, it is better to do it like this:

function MyDirective () {
    return {
        link: function (scope, iElement, iAttrs) {
            iAttrs.$observe("uiButtonLeft", function (val) {
                if (val) {
                    iElement.attr(src, val); // whatever you want to do
                }
            });
        }

}

      

...

<img ui-button ui-button-left="{{item.leftBtn}}"></img>

      



And sometimes it {{val}}

gets difficult to use , either for clean code or you can change as well val

. Here's how you can do it.

function MyDirective () {
    return {
        link: function (scope, iElement, iAttrs) {
            iAttrs.$observe("uiButtonLeft", function (val) {
                scope.$watch(val, function (valInScope) {
                    if (valInScope) {
                        iElement.attr(src, valInScope); // whatever you want to do

                        // the following statement updates the value in scope
                        // it kinda weird, but it works.
                        scope.$apply(val + "= Hello"); // if you are out of angularjs, like jquery event
                        scope.$eval(val + = "Hello"); // if you are in angualrjs
                        // $apply can handle string, it like ngClick
                        // you use in templates. It updates the value correctly.
                    }
                }
            });
        }

}

      

...

<img ui-button ui-button-left="item.leftBtn"></img>

      

+10


source


@

applies only to the local visibility property. Try &

this instead, this will allow you to execute the expression in the context of the parent scope.

Quoting from http://docs.angularjs.org/guide/directive



&

or &attr

- provides a way to execute an expression in a parental scope context. If attr is not specified, the attribute name is assumed to be the same as the local name. Given <widget my-attr="count = count + value">

and defining the widget's scope:, { localFn:'&myAttr' }

then isolating the scope property localFn

will point to the function wrapper for the expression count = count + value

. It is often desirable to transfer data from the enclosed space through the expression and into the parent area, this can be done by passing a map of local variable names and values ​​to the fn expression wrapper. For example, if an expression increment(amount)

, then we can specify a amount

value by calling localFn

aslocalFn({amount: 22})

John Lindqvist covers this well on his egghead.io video 17, 18 and 19.

+5


source


I think I understand what you are trying to do. In your directive, just set up the isolation scope to match the function you want from the parent using the ng-click attribute.

scope: {
    uiButtonIconLeft: '@',
    uiButtonIconRight: '@',
    clicky: '&ngClick'
}

      

+2


source


If you want your directive to use isolation scope, and if you want to call a method defined in the parent / controller scope from the same element in your HTML / markup, then using $ parent is fine.

You usually don't need to use $ parent inside ng-repeat, because ng-repeat usually creates a child scope that prototypically inherits from the parent / controller scope. Therefore, a method call inside ng-repeat follows the prototype chain up to the parent scope to find the method.

Since your directive creates an isolation scope, each ng-repeat iteration is forced to use the same selection area and not the area it normally uses as they are defined in the same element. The only way to get methods defined in the parent scope (from HTML) is to use $ parent as there is no prototype chaining from the selection scope.

Any custom directive we write needs to document what kind of scope is being created. For example, Angular documentation indicates which directives create a new scope. For more details on this see the comments on this answer: fooobar.com/questions/1997868 / ...

Another option is to change your directive to not use the isolation scope, and use attributes to specify the scope properties that the directive should check.

+2


source







All Articles