1

What is the correct way to hide/show elements in custom directive handler?

In my application I have number of elements which should be hidden/shown based on user authentication state. To track the state I created 'Auth' service. At the moment I'm concerned that 'Auth' service is injected almost into each controller. To avoid this I decided that it would be better to create a directive which will show/hide elements based on 'Auth' service state. Something like

<div data-app-auth-only="true">Content is visible only for authenticated users</div>

I've read angular directive tutorial, created directive and set watch on Auth service. I get the handler triggered whenever Auth state is changed but I'm stuck on hiding/showing element. At some tutorials I see people use 'element.hide()' command but for some reason the hide() is not defined in my case.

Here is the link to Plunkr with my example.

I'm also concerned if the directive is the correct way here. What is the best practice for such kind of task? Does it worth to make a directive or maybe inject 'Auth' into root scope would be better?

Soteric
  • 3,070
  • 5
  • 24
  • 23
  • Hiding/showing elements still keeps it in the DOM. You don't want such information vulnerable to a simple `.show()` in the console, it's better if you don't send it from the server in the first place. – Omri Aharon Nov 30 '14 at 11:50
  • This is not an issue in my case. The content is not secret and it's okay if non authenticated user will see it. That's just a matter of view and design. Actually the content will be some control elements like 'Create ticket' button. Non authenticated user won't be able to use it anyway because server side has additional security checks. – Soteric Nov 30 '14 at 11:57
  • I see. Wrote my answer then. – Omri Aharon Nov 30 '14 at 12:06

2 Answers2

2

Solution

In those tutorials, hide() and show() most likely comes from jQuery. To use those methods, you need to add jQuery before adding AngularJS. Then, change your directive for something like:

app.directive('appAuthOnly', ['Auth', function(Auth) {
  function link(scope, element, attrs) {
    scope.$watch(Auth.isAuth, function(value, oldValue) {
      if (Auth.isAuth()) {
        element.show();
      } else {
        element.hide();
      }
    });
  }

  return {
    link: link
  };
}]);

If you don't want to add a dependency to jQuery, you can use element.addClass("my-class-name") and element.removeClass("my-class-name"). These two methods are included with AngularJS (jqLite). In this case, my-class-name is a CSS class that sets display to none (display: none).

You can look at my forked plunker for the solution. I tried to change your code as little as possible.

Editorial

In my projects, I use a directive in this scenario. I found them to be simple and flexible. If you eventually need permissions, you can pass a parameter to your directive to check for a given permission with your Auth factory. (app-auth-only="my-permission") At that point, I would also rename my directive to something like required-permissions="view:this, view:that.

In my opinion, such directives doesn't make sense as a comment, class or element. So I would also restrict it for attributes only.

app.directive('appAuthOnly', ['Auth', function(Auth) {
  return {
    restrict: 'A',  // Forces the directive to be an attribute.
    link: function link(scope, element, attrs) {
      scope.$watch(Auth.isAuth, function(value, oldValue) {
        if (Auth.isAuth()) {
          element.show();
        } else {
          element.hide();
        }
      });
    }
  };
}]);

In both examples, I use jQuery for simplicity. In a real project, I wouldn't want to include jQuery for this. I would implement the addClass and removeClass solution. I found that including jQuery makes it too easy to go back to my bad jQuery habbits.

Maxime Morin
  • 2,008
  • 1
  • 21
  • 26
0

What I do is inject the service, AuthService in your case, to every relevant controller, and place on the HTML elements:

<div ng-show="showAuthContent()">Create Ticket</div>

And in the controller i'll have a function on scope:

$scope.showAuthContent = function() {
      // call to logic, preferably in a auth service so it can be reused
      // and also no business logic in controller
}
Omri Aharon
  • 16,959
  • 5
  • 40
  • 58
  • That's what I previously had. This code was presented in each controller. Such massive duplication alarmed me and I decided to ask for better solution. So you think that I shouldn't be concerned by this duplication and keep injecting AuthService? – Soteric Nov 30 '14 at 12:11
  • @Soteric Yes, you shouldn't be concerned. Your logic is not duplicated code-wise, you have a service that controls this and every controller has to go through it (which is good for maintenance as well). In my opinion this is part of why AngularJS is a good framework :) – Omri Aharon Nov 30 '14 at 12:24
  • @Soteric Another thing - if you have a controller on an outer scope, you can have the `showAuthContent` function on that controller, and you can call if from all other nested controllers. That way the function will only be written once. – Omri Aharon Nov 30 '14 at 12:25