The problem here is in your area of โโisolation. Using scope: {} , you created a new one, isolate the scope for this element. Isolated areas are not inherited from the parent area. All attributes and contents of directives with allocation areas are evaluated in the context of the isolation area. gumball does not exist in the selection area, so everything looks like undefined.
You have two options to fix this: (1) remove the selection area (for example, scope: true to create a child area); or (2) bind values โโin the selection area.
To bind your attributes to scope variables, you just need to specify the scope and type of binding that you want:
scope: { id: '@', color: '@' },
This suggests that the id and color attributes should be interpolated in the context of the parent area and then added to the area. You can remove all this logic inside your link function - this will do it for you.
But this still leaves a content problem inside the directive. To interpolate this in the context of the parent area, you need to do the transition:
transclude: true, template: "<div class='gumballColor{{color}}' ng-transclude></div>"
Transclusion takes the contents of an element and interpolates it relative to the new child of the parent area, for example. where gumball will still be determined.
With these two changes, your directive will work as desired.
If you are confused about which area to use, here's another question that might help: When writing a directive, how can I decide if I need a new area, a new content area, or a new selection area?
Side note: Even without a highlighting area, the logic in your link function to determine attribute values โโwill not work. The order of execution is the important part, which roughly corresponds to: compiler โ controller โ link โ interpolation. Until interpolation is complete, there is no value for your attributes. This way your checks will not work.
However, you can set $observe to interpolated attributes; $observe will always be run for the first time, even if no value has been passed. You can use this to set your default value. $observe also very efficient.
attrs.$observe( 'attr1', function(val) { if ( !angular.isDefined( val ) ) { scope.attr1 = 'defaultValue'; } });