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>.
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;
});
}
};
});
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??
});
}
};
});
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'
}));
Related
I'm trying to call directive method on button click from the calling controller.
Here is the directive code:
myApp.directive("helloDirective", function() {
return {
restrict: "E",
template: '<input type="text" data-ng-model="model.msg" />',
scope: {},
bindToController: {
param: "="
},
controller: 'helloDirectiveController',
controllerAs: 'model'
}
})
.controller("helloDirectiveController", function() {
var self = this;
self.actions = {
get: function() {
return self.msg;
},
set: function(msgData) {
self.msg = msgData;
}
});
I have call the get and set method from controller..
myApp.controller("indexController", [function() {
var self = this;
self.helloParam ={};
self.get = function() {
//how to call the Directive get method from here
}
}]);
i tried to create a fiddle here
plnkr
The idea
For me, the cleanest solution (so far) for your problem is a solution used by Angular Material developers (I don't know if they were the authors, but I found it there). I've used it once in my project and it worked like a charm.
The idea is to create a global registry for directives' actions. Directives would be stored there by unique ids. We can also create a service dedicated for each directive, just in case we need some external logic.
The solution
1. Components registry
Firstly, we need a components registry. It can be a really simple service:
angular.module('app');
.service('$componentsRegistry', function() {
var self = this;
var components = {};
self.put = function(id, actions) {
components[id] = actions;
}
self.get = function(id) {
return components[id];
}
})
The components registry has methods for storing and getting components by ids. Of course, there might be much more methods and they might be more complicated, but this is just a simple example.
2. Service for our directive
Let's say we have a simple show-message directive, so we can create a $showMessageService:
angular.module('app')
.service('$showMessageService', ['$componentsRegistry', function($componentsRegistry) {
return function(id) {
return $componentsRegistry.get(id);
}
}])
For now, the only task of the service is to return our directive's actions. But it can be extended in the future, of course.
3. Directive's code
The last thing we need is our show-message directive:
angular.module('app')
.directive('showMessage', function($componentsRegistry) {
return {
restrict: 'E',
scope: {
directiveId: '#' // Unique id is passed from the view
},
template: '<div>{{ text }}</div>',
link: function(scope) {
scope.text = 'TEST TEXT';
// Create actions
scope.actions = {
set: function(value) {
scope.text = value;
}
}
// Store actions in the components registry
$componentsRegistry.put(scope.directiveId, scope.actions);
}
}
})
In a link function, we need to simply register our actions in components registry. We pass the unique id from the view so that developer has control over it inside views/controllers.
Example of usage
And now we can finally use the directive in our application. Here is a simple code which shows how to do that:
View
<div ng-controller="testController as test">
<show-message directive-id="testDirective"></show-message>
<button ng-click="test.changeText()">CHANGE</button>
</div>
Controller
angular.module('app')
.controller('testController', function(['$showMessageService', $showMessageService) {
var self = this;
self.changeText = function() {
$showMessageService('testDirective').set('NEW');
}
}])
I've Change The Directive Code, moved Controller code to link and it's working fine.
testApp.directive("helloDirective", function () {
return {
restrict: "E",
template: '<input type="text" data-ng-model="model.msg" />',
scope: {},
bindToController: {
param: "="
},
link: function (scope, element, attrs) {
var self = scope.model;
var assignMethod = function () {
if (self.param == undefined) {
self.param = {};
}
self.param.actions = {
get: function () {
return self.msg;
},
set: function (msgData) {
self.msg = msgData;
}
};
};
assignMethod();
},
controller: function () { },
controllerAs: 'model'
}
});
now i can call the directive get Method from calling controller like,
self.helloParam = {};
self.click = function () {
alert(self.helloParam.actions.get());
}
I looked at the documentation about scope but can't wrap my head around it. My directive is a loading-overlay with an isolated scope. I tried both scope: true and scope: false, but neither has the desired effect. On top of that I read that scope: {} is considered to be best practice.
I understand that you can pass values to the isolated scope via # or =, but that's not what I need (I think?). What i need, is to add my directive(=the overlay) to the parent-scope when clicking on a breadcrumb.
tl;dr: My overlay doesn't appear when I navigate with the breadcrumbs.
Here's the HTML:
<div id="myApp">
<ui-breadcrumbs template-url="Scripts/app/uiBreadcrumbs.tpl.html" displayname-property="data.breadcrumbLabel" abstract-proxy-property="data.breadcrumbProxy"></ui-breadcrumbs>
<div loading-Overlay></div> **This is my isolated scope**
<div id="MainBody" ui-view="main">
#RenderBody()
</div>
</div>
This is the directive:
(function () {
"use strict";
angular
.module("myApp")
.directive("loadingOverlay", ["$http", "$timeout",
function ($http, $timeout) {
return {
restrict: "A",
replace: true,
templateUrl: "./Scripts/app/loading.overlay.html",
scope: {},
link: function (scope, element) {
scope.isRouteLoading = false;
scope.isRouteStillLoading = false;
scope.$on('$stateChangeStart', function () {
scope.isRouteStillLoading = true;
scope.isRouteLoading = true;
element.addClass("show");
});
scope.$on('$stateChangeSuccess', function () {
scope.isRouteStillLoading = false;
scope.isRouteLoading = false;
$timeout(function () {
if (scope.isRouteStillLoading)
scope.isRouteLoading = true;
element.removeClass("show");
}, 500);
});
scope.$watch(scope.isRouteLoading,
function (value) {
return value ? element.addClass("show") : element.removeClass("show");
});
}
};
}]);
})();
So I am having a bit of trouble. I have looked through all of the previous solutions from Injecting service to Directive, but I really have no idea what I'm doing wrong. I have an authServices shown below.
app.factory('authService', ['$http', function ($http) {
var authServiceFactory = {};
var _authentication = {
isAuth: false,
userName: ""
};
var _login = function (loginData) {
_authentication.isAuth = true;
_authentication.userName = loginData.userName;
}
appFactory.login = _login;
return appFactory;
}]);
I am injecting it via the method they had proposed.
app.directive('headerNotification', ['authService', function (authService) {
return {
templateUrl: 'app/scripts/directives/header/header-notification/header-notification.html',
restrict: 'E',
replace: true,
link: function (scope) {
scope.authService = authService;
}
}
}]);
My html is as
<li data-ng-hide="authentication.isAuth">
I really feel I am just doing this wrong. Any help would be greatly appreciated.
what is authentication.isAuth in your view.
I think you miss spelled your object.
<li data-ng-hide="authService.isAuth">
Your scope object is authService not authentication, right?
Update - Pass veraible to directive
I am assuming that you have your auth variable in your controller.
$scope.myAuthService = authservice;
Noe you can pass this variable to your directive as an attribute.
<header-notification my-auth="myAuthService"> </header-notification>
Here myAuthService is a scope variable.
Change your directive to accept this variable,
app.directive('headerNotification', function () {
return {
templateUrl: 'app/scripts/directives/header/header-notification/header-notification.html',
restrict: 'E',
scope : {
myAuth : '=' // here you specify that you need to convert your attribute variable 'my-auth' to your directive's scope variable 'myAuth'
},
replace: true,
link: function (scope, element, attr, controller) {
// here you will get your auth variable
scope.myAuth; // this contains your auth details
}
}
});
So, I have a question that I am unsure of how to even ask. I have a property that I am setting in a directive's controller by an auth0 library (code to follow). I need to modify that property from another app controller.
Specifically, the use case is around being logged in/logged out. When the user is logged in, and they click the logout button, I can set the value of the property, not a problem. But when they are not logged in, and they log in, I can't set that property in the directive from the login controller.
Directive:
angular
.module('app')
.directive('loginLogout', loginLogout);
function loginLogout() {
var directive = {
...
scope: {
loggedin: '='
},
controller: loginLogoutController,
controllerAs: 'vm',
bindToController: true
};
return directive;
function loginLogoutController(auth,store,$location,toastr,$parse ) {
var vm = this;
vm.logout = logUserOut;
vm.loggedin = auth.isAuthenticated;
function logUserOut() {
auth.signout();
...
vm.loggedin = false;
}
}
}
Login Controller:
(abbreviated)
function LoginController(auth, store, $location, toastr) {
var vm = this;
vm.login = function () {
auth.signin({}, loginSuccess, loginFailure);
function loginSuccess(profile, token){
...
// ========== I want to set the value of vm.loggedin from the directive here.
}
function loginFailure(){
...
}
};
}
I have tried things like $parse, and setting tinkering with the isolated scope on the directive config. No luck. Any help is appreciated.
You could try using $rootScope.$broadcast and $scope.$on for such communication.
You have used controllerAs to avoid injecting $scope. Ofcourse this would need injecting $scope in controller. However using $scope in such specific cases (that is when controllerAs is used) may not be all that bad idea (https://github.com/toddmotto/angularjs-styleguide).
Login Controller:
function LoginController(auth, store, $location, toastr) {
var vm = this;
vm.login = function () {
auth.signin({}, loginSuccess, loginFailure);
function loginSuccess(profile, token){
...
// ========== I want to set the value of vm.loggedin from the directive here.
$rootScope.$broadcast('loginCheck');
}
function loginFailure(){
...
}
};
}
Directive
function loginLogoutController(auth,store,$location,toastr,$parse ) {
var vm = this;
vm.logout = logUserOut;
vm.loggedin = auth.isAuthenticated;
function logUserOut() {
auth.signout();
...
vm.loggedin = false;
}
$scope.$on('loginCheck', function(event, args) {
// Set vm.loggedin
});
}
What i can think of now, You can use angular.js function binding.
.directive('loginLogout', loginLogout);
function loginLogout() {
var directive = {
...
scope: {
loggedin: '=',
confirmAction: '&'
},
controller: loginLogoutController,
controllerAs: 'vm',
bindToController: true
};
<!--In html-->
<login-logout confirm-action="doSomething()"> </login-logout>
function LoginController(auth, store, $location, toastr) {
var vm = this;
vm.login = function () {
auth.signin({}, loginSuccess, loginFailure);
function loginSuccess(profile, token){
...
// call doSomething here
doSomething()
}
function loginFailure(){
...
}
};
}
I am creating a directive with angular and in that i am using kendo-window control. Now i want to open that kendo window on demand from controller. In simple words i want to call a method of directive from controller on button click.
Here is my code sample
sample.directive('workorderwindow', [initworkorderwindow]);
function initworkorderwindow() {
return {
link: function (scope, elements, attrs) {
},
restrict: 'E',
template: "<div data-kendo-window='window.control' data-k-options='window.config'> HELLOW RORLD </div>",
scope: {
},
controller: function ($scope) {
$scope.window =
{
control: null,
config: { title: 'HELLO WORLD', visible: false }
}
$scope.open = function () {
$scope.window.control.center().open();
}
}
}
}
HTML
<workorderwindow></workorderwindow>
Now i want to call that directive open method from my controller.
sample.controller('datacontroller', ['$scope', 'datafac', initcontroller]);
function initcontroller($scope, datafac) {
$scope.onsampleclick = function () {
//FROM HERE
}
It's probably a bad practice to directly call a function of your directive from a controller. What you can do is create a Service, call it from your controller and injecting this service in your directive. With a $watch you will be able to trigger your directive function.
The service between Controller and Directive
app.factory('myWindowService', function () {
return {
windowIsOpen : null,
openWindow: function () {
this.windowIsOpen = true;
},
closeWindow: function () {
this.windowIsOpen = false;
}
};
Your directive :
app.directive('workorderwindow', function () {
return {
restrict: 'E',
template: "<div>test</div>",
controller: function ($scope, myWindowService) {
$scope.windowService = myWindowService;
$scope.$watch("windowService.windowIsOpen", function (display) {
if (display) {
console.log("in directive");
//$scope.window.control.center().open();
}
// You can close it here too
});
}
};
})
And to call it from your controller
app.controller('datacontroller', function ($scope, myWindowService) {
$scope.open = function () {
myWindowService.openWindow();
}
// $scope.close = ...
});
Here a working Fiddle http://jsfiddle.net/maxdow/ZgpqY/4/