Hej,
I am looking for a method to change the navigation links in the navigation bar according to whether a user is logged in or not. Obviously, a logged in user should be able to navigate to more pages.
I programmed a UserService which wraps the ng-token-auth module (https://github.com/lynndylanhurley/ng-token-auth) and adds a boolean variable holding the login state. The service looks similar to this:
angular.module('app')
.run(function($rootScope, $location, userService) {
$rootScope.$on('auth:login-success', function(ev, user) {
userService.setLoggedIn(true);
$location.path('/on/dashboard');
});
})
.factory('userService', function($auth) {
var service = {};
service.loggedIn = false;
service.submitLogin = submitLogin;
service.setLoggedIn = setLoggedIn;
service.getLoggedIn = getLoggedIn;
return service;
function submitLogin(params) {
$auth.submitLogin(params)
}
function setLoggedIn(bool) {
service.loggedIn = bool;
}
function getLoggedIn() {
return service.loggedIn;
}
});
My directive looked like this:
.directive('agNavigation', function(userService) {
if(userService.getLoggedIn()){
return {
restrict: 'AE',
replace: true,
templateUrl: '/views/userNavigation.html'
}
}else{
return {
restrict: 'AE',
replace: true,
templateUrl: '/views/navigation.html'
}
}
});
When logged in, the view changes to the dashboard but the directive does not update, which is logical because the directive isn't called again. Now, I am trying to use the $watch function but I cannot figure it out. See here:
.directive('agNavigation', function(userService) {
var navUrl = '';
if(userService.getLoggedIn()){
navUrl = '/views/userNavigation.html';
}else{
navUrl = '/views/navigation.html';
}
return {
restrict: 'AE',
replace: true,
templateUrl: navUrl,
link: function postLink(scope, element, attrs, controller) {
scope.$watch(function() {
return userService.getLoggedIn();
}), function(newValue, oldValue) {
if(newValue){
navUrl = '/views/userNavigation.html';
}else{
navUrl = '/views/navigation.html';
}
}
}
}
});
I am very new to AngularJS, thus I am very happy for any advice, or if I am approaching this in a wrong way.
userService.getLoggedIn() will fails in compile time of directiveas you write the code in your first example code. You should have really need to move that code to the templateUrl function & return the URL on basis of condition.
Code
.directive('agNavigation', function(userService) {
return {
restrict: 'AE',
replace: true,
templateUrl: function(){
if(userService.getLoggedIn()){
return '/views/userNavigation.html';
}
return '/views/navigation.html';
}
}
});
Related
I'm trying to write a directive such that an element with attribute if-login-service="facebook" will be created, but an element with any other value for this attribute will not.
The progress I've made with this directive so far is shown below
app.directive('ifLoginService', function($compile) {
return {
restrict: 'EA',
compile: function compile(element, attrs) {
return function($scope, $element, $attr) {
var serviceName = attrs.ifLoginService;
if (serviceName === 'facebook') {
// use compile to include and compile your content here
$compile($element.contents())($scope);
}
}
}
};
});
A Plunker demo is available here. If it were working, the "Facebook" button would be displayed and the "Not Facebook" button would not. Currently both buttons are displayed, but I'm not sure where I'm going wrong.
You should use compile service to write your directive.
$compile('your html content here')($scope);
To clear root element such as button use this:
$element[0].html('');
Or remove it from DOM:
$element[0].parentNode.removeChild($element[0]);
Here is your directive repaired:
var app = angular.module('plunker', ['ngSanitize']);
app.controller('MainCtrl', function($scope) {
$scope.model = {
toggle: 'true'
};
});
app.directive('ifLoginService', function($compile,$animate) {
return {
restrict: 'EA',
replace: true,
compile: function compile(element, attrs) {
return function($scope, $element, $attr) {
var serviceName = attrs.ifLoginService;
console.debug('Testing service name', serviceName);
if (serviceName === 'true') {
// use compile to include and compile your content here
$element.html($compile($element.contents())($scope));
}
else
{
$element[0].parentNode.removeChild($element[0]);
}
}
}
};
});
Link to plunkr here
I'm developing an application using angularjs and I'm also using directives because some UI is re-used across multiple pages. When I have a directive which is dependent on a value from a promise I have to use $scope.$watch and an if condition to check for undefined, because the directive compiles before the promise is completed. Here is an example:
myAppModule.directive('topicDropdown', function () {
return {
templateUrl: 'Scripts/app/shared/dropdown/tmplTopic.html',
restrict: 'AE',
scope: {
subjectId: '=',
setDefault: '=',
topicId: '='
},
controller: [
'$scope', 'service', function ($scope, service) {
$scope.$watch('subjectId', function () {
if ($scope.subjectId != undefined)
$scope.getTopics();
});
$scope.getTopics = function () {
service.get("section/topics/" + $scope.subjectId).then(function (data) {
$scope.listOfTopics = data;
if ($scope.setDefault) {
$scope.subjectId = $scope.listOfTopics[0].UniqueId;
}
});
}
}
]
}
});
The subjectId will eventually come from a promise but without the $watch I'll get an undefined error because it will fire getTopics without an ID.
scope: {
subjectId: '=',
setDefault: '=',
topicId: '='
},
Currently, this code works but I'm having to invoke the digest cycle every time subjectId changes which will loop through everything that scope is watching. I only care when subject ID changes at this point.
I've also seen some suggestions to use ng-if for the template html. Something like this:
<div ng-if="subjectId != undefined">
<topic-dropdown subject-id="subjectId"></topic-dropdown>
</div>
With this, I don't have to use $scope.$watch however I'm not sure if this is the best approach either.
Does anyone have a good solution to this problem? Are there any directive properties that I could be using which I am unaware of?
Nick
Pass a promise for the subjectId and make getTopics wait for it to resolve. It would look something like this:
$scope.getTopics = function () {
$scope.subjectIdPromise.then(function (subjectId) {
$scope.subjectIdPromise = service.get("section/topics/" + subjectId)
.then(function (data) {
$scope.listOfTopics = data;
if ($scope.setDefault) {
return data[0].UniqueId;
} else { return subjectId; }
});
});
};
Doing it this way all access to the subjectId is done in a then success function. If you then want to change the subjectId do it by replacing the promise with a new one.
Try using promise patterns,
myAppModule.directive('topicDropdown', function () {
return {
templateUrl: 'Scripts/app/shared/dropdown/tmplTopic.html',
restrict: 'AE',
scope: {
subjectId: '=',
setDefault: '=',
topicId: '='
},
controller: [
'$scope', 'service', function ($scope, service) {
$q.when($scope.subjectId).then(
service.get("section/topics/" + $scope.subjectId).then(function (data) {
$scope.listOfTopics = data;
if ($scope.setDefault) {
$scope.subjectId = $scope.listOfTopics[0].UniqueId;
}
});
)
]
}
});
OR
myAppModule.directive('topicDropdown', function ($q) {
return {
templateUrl: 'Scripts/app/shared/dropdown/tmplTopic.html',
restrict: 'AE',
scope: {
subjectId: '=',
setDefault: '=',
topicId: '='
},
link: function(scope, element, attrs){
$q.when(subjectId).then(
service.get("section/topics/" + scope.subjectId).then(function (data) {
scope.listOfTopics = data;
if (scope.setDefault) {
scope.subjectId = scope.listOfTopics[0].UniqueId;
}
});
)
}
}
});
but I think one way or another you are going to use $watch. Using $watch do not harm anything, its angular way to keep things connected.
I am working on a proof of concept where I am using angularjs. I have to use a click function on the links for which I am using ng-click. This ng-click is inside ng-repeat. I went through so many solved problems here in stackoverflow on how to use ng-click inside ng-repeat. But somehow, none of the solutions worked. Can some one help me to point out where I am doing the mistake?
HTML Code :
<collection collection='tasks'></collection>
<collection collection='articleContent'></collection>
app.directive('collection', function () {
return {
restrict: "E",
replace: true,
scope: {
collection: '='
},
template: "<ul><member ng-repeat='member in collection' member='member'></member></ul>"
}
});
app.directive('member', function ($compile) {
return {
restrict: "E",
replace: true,
scope: {
member: '='
},
template: "<li><a href='#' ng-click='getArticleContent(member.itemId)'>{{member.title}}</a></li>",
link: function (scope, element, attrs) {
if (angular.isArray(scope.member.tocItem)) {
if(scope.member.hasChildren == "true")
{
for(var i=0;i<scope.member.tocItem.length;i++){
if(scope.member.tocItem.title) {
scope.member.tocItem.title.hide = true;
}
}
}
element.append("<collection collection='member.tocItem'></collection>");
$compile(element.contents())(scope)
}
}
}
});
app.controller('AbnTestController', function($scope) {
var bookId ="";
$scope.tasks = data;
$scope.getArticleContent = function(itemId){
alert('inside article content');
$scope.articleContent = articleData[0].articleContent;
}
});
on click of the link, getArticleContent method is never called here.
Directive member has it's own scope, meaning isolated scope.
And the template has an ng-click to execute getArticleContent, that the member directive does not contain the getArticleContent function.
You have two choices:
Add the getArticleContent function to the directive member.
Create member directive without isolated scope.
There are issues with this though, having two instances of the same directive may cause conflicts.
Updated after OP comment:
I'm adding OP code with some data passed to directives for manipulation:
app.directive('collection', function() {
return {
restrict: "E",
replace: true,
scope: {
collection: '=',
articleData: '=',
articleContent: '='
},
template: "<ul><member ng-repeat='member in collection' member='member' article-data='articleData' article-content='articleContent'></member></ul>"
}
});
app.directive('member', function($compile) {
return {
restrict: "E",
replace: true,
scope: {
member: '=',
articleData: '=',
articleContent: '='
},
template: "<li><a href='#' ng-click='getArticleContent(member.itemId)'>{{member.title}}</a></li>",
link: function(scope, element, attrs) {
scope.getArticleContent = function(itemId) {
alert('inside article content');
scope.articleContent = articleData[0].articleContent;
}
if (angular.isArray(scope.member.tocItem)) {
if (scope.member.hasChildren == "true") {
for (var i = 0; i < scope.member.tocItem.length; i++) {
if (scope.member.tocItem.title) {
scope.member.tocItem.title.hide = true;
}
}
}
element.append("<collection collection='member.tocItem'></collection>");
$compile(element.contents())(scope)
}
}
}
});
app.controller('AbnTestController', function($scope) {
var bookId = "";
$scope.tasks = data;
$scope.getArticleContent = function(itemId) {
alert('inside article content');
$scope.articleContent = articleData[0].articleContent;
}
});
It would be more helpful if OP can create a jsfiddle for us to review and revise.
Currently, I am facing one issue related to angularjs directive. I want to send outlet object from directive1 to directive2. Both directives having same controller scope. I tried with emitting event from directive1 to controller, broadcasting that event from controller to directive2 and listening to that event on directive2. but that is not working.
Directive1:
angular.module('moduleName')
.directive('directive1', function() {
return {
restrict: 'E',
templateUrl: 'directive1.html',
scope: false,
link: function(scope) {
scope.selectOutlet = function(outlet) {
scope.order.entityId = outlet.id;
scope.navigation.currentTab = 'right';
};
}
};
Here, in directive1, scope.selectOutlet() setting outletId to scope.order.entityId. I want to move/set that line to directive2 save function.
Directive2:
angular.module('moduleName')
.directive('directive2', function(config, $rootScope, $state) {
return {
restrict: 'E',
templateUrl: 'directive2.html',
scope: false,
link: function(scope) {
scope.save = function() {
// Save functionality
// scope.order.entityId = outlet.id; This is what i want to do
};
}
};
});
});
Any help.
you can use a factory or a service. Inject that factory into your directive. Now when you are trying set the data in function written into factory. `app.factory('shared',function(){
var obj ={};
obj.setData = function(){
// call this function from directive 1.
}
return obj;
})`
So if you include this factory into your directives you will get the data in 2 directives.
I will try to make some jsfiddle or plunker. If it is not clear.
Do the following in first directive
angular.module('moduleName')
.directive('directive1', function($rootScope) {
return {
restrict: 'E',
templateUrl: 'directive1.html',
scope: false,
link: function(scope) {
scope.selectOutlet = function(outlet) {
$rootScope.$broadcast('save:outlet',outlet);
//scope.order.entityId = outlet.id;
//scope.navigation.currentTab = 'right';
};
}
};
and in second
angular.module('moduleName')
.directive('directive2', function(config, $rootScope, $state) {
return {
restrict: 'E',
templateUrl: 'directive2.html',
scope: false,
link: function(scope) {
$rootScope.$on('save:outlet',function(event,data){
// do staff here
});
}
};
});
I have a directive that wraps another one like this :
<div direction from="origin" to="destination">
<div direction-map line-color="#e84c3d"></div>
</div>
the direction-map directive is transcluded, see my code (Fiddle available here) :
var directionController = function() {
//do stuffs
};
var directionMapController = function() {
//do other stuffs
};
var Direction = angular.module("direction", [])
.controller("directionController", directionController)
.controller("directionMapController", directionMapController)
.directive("direction", function() {
var directive = {
restrict: "AEC",
controller: "directionController",
scope: {},
transclude: true,
link: {
pre: function($scope, $element, attrs, controller, transclude) {
console.log("direction's controller is directionController : ");
console.log(controller.constructor === directionController);//true, that's ok
transclude($scope, function(clone) {
$element.append(clone);
});
}
}
};
return directive;
})
.directive("directionMap", function() {
var directive = {
require: "^direction",
controller: "directionMapController",
restrict: "AEC",
scope: true,
link: {
pre: function($scope, $element, $attrs, controller) {
console.log("directionMap's controller is directionMapController :");
console.log(controller.constructor===directionMapController);//false that's not OK!!!!
}
}
};
return directive;
});
So my question is:
Why my child directive direction-map gets as parameter the controller of its parent (I think it's because it is transcluded), is it possible to avoid this or should I just re-think my code ?
It's happening beacause you are using require: "^direction" if you remove this line the directive will get the controller of itself rather than the parent one.
Hope it help :)
Updated Fiddle