1

I'm trying to build a header directive that:

  • If logged in, should display the username and a Log Out button
  • If logged out, should hide the above-mentioned things

I'm using a custom login service that captures this information, and broadcasts the events login and logout. I'm successfully listening to these events in both the header's controller and directive.

How can I reload the directive on these events?


loginService.js:

angular.module("app")
    .service("loginService", ["$http", "$rootScope", function ($http, $rootScope) {
        var loggedIn = false,
            _username = "";

        this.logIn = function (username, password) {
            // do some validation...

            loggedIn = ...validation was successful;
            _username = username;

            if (loggedIn) {
                $rootScope.$broadcast("login");
            }
        };
        this.getUsername = function () {
            return _username;
        };
        this.isLoggedIn = function () {
            return loggedIn;
        };
        this.logOut = function () {
            loggedIn = false;
            $rootScope.$broadcast("logout");
        };
    }]);

headerController.js

angular.module("app")
    .controller("headerController", ["loginService", "$rootScope", "$location", function (loginService, $rootScope, $location) {
        this.isLoggedIn = loginService.isLoggedIn();
        this.username = "";

        $rootScope.$on("login", function (event) {
            this.isLoggedIn = loginService.isLoggedIn();
            this.username = loginService.getUsername();
        });

        this.logOut = function () {
            loginService.logOut();
            this.isLoggedIn = loginService.isLoggedIn();
            this.username = "";
            $location.path("/login"); // redirecting
        };
    }]);

header.html:

<header ng-controller="headerController as header">
    <span ng-if="header.isLoggedIn">{{header.username}} <button ng-click="header.logOut()">Log Out</button></span>
</header>

headerDirective.js

angular.module("app")
    .directive("header", function () {
        return {
            restrict: "A",
            transclude: false,
            templateUrl: "app/header/header.html",
            controller: "headerController",
            link: function (scope, element, attrs) {
                scope.$on("login", function (event) {
                    // show the ng-if in header.html??
                });
                scope.$on("logout", function (event) {
                    // hide the ng-if in header.html??
                });
            }
        };
    });

I'm using this as <div header></div>.

3
  • 1
    shouldn't the header directive restricted to restrict: "E" instead of restrict: "A" since you're using it as an element? Commented Mar 21, 2016 at 5:08
  • 3
    You're not actually using the directive at all from what I can tell...the header element exists in HTML5, and it would be better to avoid confusing yourself to name your element something different. It's fine if it's an attribute, but it should be named differently. Commented Mar 21, 2016 at 5:12
  • I added example usage as <div header></div>, but renaming for clarity and using this as an element are good ideas thx. Commented Mar 21, 2016 at 5:14

3 Answers 3

1

It looks like there are some fundamental issues with the directive that will not allow this to work:

1) Declared as an Attribute Directive: You've create a header attribute directive: restrict: "A", but you are using it as an element directive: <header ng-controller...</header>. restrict property should be restrict: "E". Or you haven't used the directive as others have commented.

2) Transclude is false You have set transclude to false but you are attempting to use the directive with contents so transclude should be true.

To solve your issue I would suggest this as a solution: 1. Declare your header directive in its parent container view as just this.

<ian-header></ian-header>

ianHeader.html

<header>
    <span ng-if="header.isLoggedIn">{{header.username}} <button ng-click="header.logOut()">Log Out</button></span>
</header>

ianHeader.js

angular.module("app")
    .directive("ianHeader", function () {
        return {
            restrict: "E",
            templateUrl: "app/header/ianHeader.html",
            link: function (scope, element, attrs) {
                scope.header = {isLoggedIn: false};
                scope.$on("login", function (event) {
                    // show the ng-if in header.html??
                    scope.header.isLoggedIn = true;
                });
                scope.$on("logout", function (event) {
                    // hide the ng-if in header.html??
                    scope.header.isLoggedIn = false;
                });
            }
        };
    });
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks @Dr Jones! Using restrict "E" and transclude: true as you are describing solved my issue. Also, I was making a classic JavaScript mistake -- using "this" inside closures... using var self = this; instead solved that issue. Another thing to add is that listening to broadcasts with $on is working in both the directive and controller, so I chose to do this in the controller only.
0

If you would provide a JS snippet would be easier but anyway one approach might be:

angular.module("app")
    .directive("header", function () {
        return {
            restrict: "A",
            transclude: false,
            templateUrl: "app/header/header.html",
            controller: "headerController",
            link: function (scope, element, attrs) {
                scope.$on("login", function (event) 
                {
                  //header should be the controllerAs you declared
                  //If you would provide JS snippet would be easier to debbug
                  scope.$parent.header.isLoggedIn= true;
                    // show the ng-if in header.html??
                });
                scope.$on("logout", function (event) 
                {
                   scope.$parent.header.isLoggedIn = false;
                    // hide the ng-if in header.html??
                });
            }
        };
    });

Comments

0

Your solution is not great, usage of event broadcasting is always prone to errors and generally difficult to test and debug. What you need to do is to create a service which stores the current profile object and store a reference to it in the header directive (and other services which may use/modify the current user).

The Service

'use strict';

(function() {

    function AuthService() {
      
        var Auth = {
           User: User //call some API for user authentication if using sessions
        };

        return Auth;
    }

    angular.module('app')
        .factory('Auth', AuthService);

})();

Your header directive

'use strict';

//do not use "header" as the name of directive 
angular.module('app')
  .directive('navbar', () => ({
    templateUrl: 'components/navbar.html',
    restrict: 'E',
    controller: function(Auth){
      this.user = Auth.User; 
    },
    controllerAs: 'navbar'
  }));

2 Comments

Inside the context if your IIFE, move 'use strict'; inside of it, or it will get hoisted to global space, which has the potential to break a library which isn't compatible with strict mode.
@Makoto the point is? It should be used wisely of course, though that's not related to the topic of discussion

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.