2

http://plnkr.co/edit/C4mFd5MOLBD2wfm8bMhJ?p=preview

Let's take a simple example and say you want to display the value of a cookie regardless of what it is, but this could be a customer name or whatever you want. There seem to be so many options available: directives, services, directives with services, controllers - and no matter how many docs I review or blog posts I read, I still have some fundamental questions about the appropriate way to access data and then update the scope accordingly.

What's clouding my thought right now is the fact that there doesn't seem to be the equivalent of NgModelController for non ngModel capable DOM elements like span or div or anything besides user input. Basically, seeing how ngModelCtrl is utilized in the link function of a directive seems to make a lot of sense, it doesn't allow you to drown in scope soup and it nicely organizes your thoughts, but how do we achieve this decoupling with ngBind elements?

I think the answer is just 'use services', but perhaps maybe not in all cases is the thing that's gnawing at me. Suppose you want to display a very specific cookie (or a customer name) and you don't know where you want to display it, you could continually inject your custom CookieService where ever you go, but what about a specific directive that cleans things up: <specific-cookie></specific-cookie> Would we just inject our CookieService into that directive, or just access it via $cookies like we've done elsewhere.

Does the answer simply lie in whether or not you'll be accessing more than one cookie in the future? That is, if you only need one <specific-cookie></specific-cookie>, then just use $cookies in you're directive and move on with your life, or it is always appropriate to abstract away this type of call into a service, or am I just being super pedantic about understanding this.

Directive

angular-myapp.js

var app = angular.module('myApp', ['ngCookies']);

app.directive('cookie', ['$cookies', function($cookies) {
  return {
    scope: true,
    controller: function($scope, $element, $attrs) {
      $scope.cookie = $cookies[$attrs.cookie];
    }
  }
}]);

index.html

<div cookie="__utma">Cookie: {{cookie}}</div>

Controller

angular-myapp.js

app.controller('CookieCtrl', function($attrs, $cookies) {
  this.value = $cookies[$attrs['getcookie']];
});

index.html

<a ng-controller="CookieCtrl as cookie" getCookie="__utma" href="/{{cookie.value}}">{{cookie.value}}</a>

Service

angular-myapp.js

app.controller('SomeCtrl', function($scope, CookieService) {
  $scope.cookie = CookieService.getCookie('__utma');
});

app.service('CookieService', function($cookies) {
  var getCookie = function(cookie) {
    return $cookies[cookie];
  };
  return ({ getCookie: getCookie });
});

index.html

<div ng-controller="SomeCtrl">Cookie: {{cookie}}</div>

Directive with service

angular-myapp.js

app.directive('specificCookie', function(CookieService) {
  return {
    scope: true,
    template: 'Cookie: <span ng-bind="cookie"></span>',
    controller: function($scope, $element, $attrs) {
      $scope.cookie = CookieService.getCookie('__utma');
    }
  }
});

index.html

<specific-cookie></specific-cookie>
lux
  • 8,315
  • 7
  • 36
  • 49
  • I don't understand. Do you just need to "drop" a directive and have it render a cookie value? If so, you could use a `` directive with `$cookie` injected, or even a ``. – New Dev Dec 04 '14 at 04:55

1 Answers1

1

Unless I'm misunderstanding some of your scenarios, the simplest (and proper) way to do this is to create a reusable directive that displays a cookie based on a name passed to it via its attribute.

app.directive('cookie', ['$cookies', function($cookies) {
  return {
    scope: {},
    template: "<span>{{cookie}}</span>",
    restrict: "E",
    link: function(scope, element, attrs) {
      attrs.$observe("name", function(newVal){
        scope.cookie = $cookies[newVal];
      });
    }
  };
}]);

The usage would be trivial (and page controller-independent):

<cookie name="__utma"></cookie>

<input ng-model="cookieName" type="text">
<cookie name="{{cookieName}}"></cookie>

the resulting DOM would be:

<span class="ng-binding">137862001.838693016.141754...</span>
<span class="ng-binding">GA1.2.838693016.1417544553</span>
New Dev
  • 48,427
  • 12
  • 87
  • 129
  • Thanks for the response. My understanding of the $observe method is it's meant to essentially watch interpolated expressions for a given attribute, however if we're passing a string literal `__utma` do we really need to observe? Also, given that we eventually are just updating the scope. Why opt to use link here instead of controller (like the first directive example I posed). Does timing matter here? pre vs post compilation? Thanks again, still trying to wrap my head around _when_ to do something in terms of best practice. – lux Dec 04 '14 at 14:21
  • Yes, `$observe` is used to watch changes in the interpolated expression. You don't _have_ to use it, but then it would be a one-time assignment. (I updated the answer to show that it could be dynamic) – New Dev Dec 04 '14 at 15:50
  • As for link vs controller: the short answer is that controller is used to expose an API for other directives that `require` your directive. Long answer: this [SO answer](http://stackoverflow.com/a/12570008/968155) and last part from Angular's [documentation](https://docs.angularjs.org/guide/directive/#summary) – New Dev Dec 04 '14 at 15:59
  • Thanks, this seals it for me. controller = public API, whereas link = DOM manipulation (event bindings) and really any other non-public functionality. Thanks very much for the response and examples. – lux Dec 04 '14 at 16:03