Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>In <code>drinkLonghand</code>, you use the code</p> <pre><code>scope.flavor = attrs.flavor; </code></pre> <p>During the linking phase, interpolated attributes haven't yet been evaluated, so their values are <code>undefined</code>. (They work outside of the <code>ng-repeat</code> because in those instances you aren't using string interpolation; you're just passing in a regular ordinary string, e.g. "strawberry".) This is mentioned in the <a href="http://docs.angularjs.org/guide/directive" rel="noreferrer">Directives developer guide</a>, along with a method on <code>Attributes</code> that is not present <a href="http://docs.angularjs.org/api/ng.$compile.directive.Attributes" rel="noreferrer">in the API documentation</a> called <code>$observe</code>:</p> <blockquote> <p>Use <code>$observe</code> to observe the value changes of attributes that contain interpolation (e.g. <code>src="{{bar}}"</code>). Not only is this very efficient but it's also the only way to easily get the actual value because during the linking phase the interpolation hasn't been evaluated yet and so the value is at this time set to <code>undefined</code>.</p> </blockquote> <p>So, to fix <em>this</em> problem, your <code>drinkLonghand</code> directive should look like this:</p> <pre><code>app.directive("drinkLonghand", function() { return { template: '&lt;div&gt;{{flavor}}&lt;/div&gt;', link: function(scope, element, attrs) { attrs.$observe('flavor', function(flavor) { scope.flavor = flavor; }); } }; }); </code></pre> <p>However, the problem with this is that it doesn't use an isolate scope; thus, the line</p> <pre><code>scope.flavor = flavor; </code></pre> <p>has the potential to overwrite a pre-existing variable on the scope named <code>flavor</code>. Adding a blank isolate scope also doesn't work; this is because Angular attempts to interpolate the string on based on the directive's scope, upon which there is no attribute called <code>flav</code>. (You can test this by adding <code>scope.flav = 'test';</code> above the call to <code>attrs.$observe</code>.)</p> <p>Of course, you could fix this with an isolate scope definition like</p> <pre><code>scope: { flav: '@flavor' } </code></pre> <p>or by creating a non-isolate child scope</p> <pre><code>scope: true </code></pre> <p>or by not relying on a <code>template</code> with <code>{{flavor}}</code> and instead do some direct DOM manipulation like</p> <pre><code>attrs.$observe('flavor', function(flavor) { element.text(flavor); }); </code></pre> <p>but that defeats the purpose of the exercise (e.g. it'd be easier to just use the <code>drinkShortcut</code> method). So, to make this directive work, we'll break out the <a href="http://docs.angularjs.org/api/ng.$interpolate" rel="noreferrer"><code>$interpolate</code> service</a> to do the interpolation ourself on the directive's <code>$parent</code> scope:</p> <pre><code>app.directive("drinkLonghand", function($interpolate) { return { scope: {}, template: '&lt;div&gt;{{flavor}}&lt;/div&gt;', link: function(scope, element, attrs) { // element.attr('flavor') == '{{flav}}' // `flav` is defined on `scope.$parent` from the ng-repeat var fn = $interpolate(element.attr('flavor')); scope.flavor = fn(scope.$parent); } }; }); </code></pre> <p>Of course, this only works for the initial value of <code>scope.$parent.flav</code>; if the value is able to change, you'd have to <a href="http://docs.angularjs.org/api/ng.$rootScope.Scope#$watch" rel="noreferrer">use <code>$watch</code></a> and reevaluate the result of the interpolate function <code>fn</code> (I'm not positive off the top of my head how you'd know what to <code>$watch</code>; you might just have to pass in a function). <code>scope: { flavor: '@' }</code> is a nice shortcut to avoid having to manage all this complexity.</p> <p><strong>[Update]</strong></p> <p>To answer the question from the comments:</p> <blockquote> <p>How is the shortcut method solving this problem behind the scenes? Is it using the $interpolate service as you did, or is it doing something else?</p> </blockquote> <p>I wasn't sure about this, so I looked in the source. I found the following in <code>compile.js</code>:</p> <pre><code>forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) { var match = definiton.match(LOCAL_REGEXP) || [], attrName = match[2]|| scopeName, mode = match[1], // @, =, or &amp; lastValue, parentGet, parentSet; switch (mode) { case '@': { attrs.$observe(attrName, function(value) { scope[scopeName] = value; }); attrs.$$observers[attrName].$$scope = parentScope; break; } </code></pre> <p>So it seems that <code>attrs.$observe</code> can be told internally to use a different scope than the current one to base the attribute observation on (the next to last line, above the <code>break</code>). While it may be tempting to use this yourself, keep in mind that anything with the double-dollar <code>$$</code> prefix should be considered private to Angular's private API, and is subject to change without warning (not to mention you get this for free anyway when using the <code>@</code> mode).</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload