AngularJS directive with isolate scope - do I really have to call $parent everywhere?

I have an angular.js directive to create a button (hover classes, left & right icons, etc.). I'm using the automatic binding of the button's left and right icons via scope: { uiButtonIconLeft: '@', uiButtonIconRight: '@' } so that I can bind those values to data from the parent scope. This, however, causes angularjs to create an "isolate" scope, which means using my directive in a situation like this doesn't work:

<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>

I must instead, 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 this way? Am I doing it wrong? Can our hero purge the superfluous, repetetive, annoying extra $parent.<whatever> in his markup?

EDIT

The answer I settled on for my button "widget" is to avoid using an isolate scope and watch the attribute values for the left and right icons via attributes.$observe(...) rather than binding via the scope.

There's a way to do it without using explicit scope, if you are writing a directive which does not dedicatedly deal with the elements in the scope, it is better to do it this way:

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 using {{val}} becomes a hassle, either for clean code, or you might also want to change val. Here is 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's 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's like ngClick
                        // you use in templates. It updates the value correctly.
                    }
                }
            });
        }

}

.

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

@ only applies to the local scope property. Try & instead, this allows you to execute an expression in the context of the parent scope.

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

& or &attr - provides a way to execute an expression in the context of the parent scope. If no attr name is specified then the attribute name is assumed to be the same as the local name. Given <widget my-attr="count = count + value"> and widget definition of scope: { localFn:'&myAttr' }, then isolate scope property localFn will point to a function wrapper for the count = count + value expression. Often it's desirable to pass data from the isolated scope via an expression and to the parent scope, this can be done by passing a map of local variable names and values into the expression wrapper fn. For example, if the expression is increment(amount) then we can specify the amount value by calling the localFn as localFn({amount: 22})

John Lindquist covers this well on his site egghead.io video's 17, 18, and 19.

I think I understand what you are trying to do. In your directive just configure the isolate scope to map to the function you want on the parent via the ng-click attribute.

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

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

Normally, you don't have to use $parent inside ng-repeat because ng-repeat normally creates a child scope that prototypically inherits from the parent/controller scope. So calling a method inside ng-repeat follows the prototype chain up to the parent scope to find the method.

Since your directive creates an isolate scope, each ng-repeat iteration is forced to use that same isolate scope instead of the scope it normally uses, since they are defined on the same element. The only way to get to methods defined on the parent scope (from the HTML) is to use $parent, since there is no prototype chain to follow from an isolate scope.

Any custom directive we write needs to document which kind of scope is created. For example, the Angular documentation indicates which directives create new scope. For a bit more discussion on this, see the comments on this answer: http://stackoverflow.com/a/14345214/215945

Your other option is to change your directive to not use an isolate scope, and use attributes to indicate which scope properties the directive should examine.