How can I access a factory from a directive? - angularjs

I have some navbar items that should only be shown if the user is authenticated. Simplified view below:
NAVBAR
<body ng-controller="mainController as main">...
<li ng-show="main.isSecretAgent">THIS IS A SECRET</li>
</body>
FACTORY
tardis.factory('bgData', [function() {
var persistentData = {
isSecretAgent: false,
};
return {
checkIfSecretAgent: function(){
return persistentData.isSecretAgent
}
}
}]);
MAIN CONTROLLER
tardis.controller('mainController',["$scope","bgData",
function($scope,bgData) {
$scope.isSecretAgent = bgData.checkIfSecretAgent()
}
]);
Assuming the isSecretAgent value set in bgData factory can possibly change in response to user actions, how can I set my ng-show to update based on this?

You can have a function this way ...
NAVBAR
<body ng-controller="mainController as main">...
<li ng-show="main.isSecretAgent()">THIS IS A SECRET</li>
</body>
MAIN CONTROLLER
tardis.controller('mainController',["$scope","bgData",
function($scope,bgData) {
$scope.isSecretAgent = function {
return bgData.checkIfSecretAgent()
}
}
]);
Also it would be better if you use ng-if because in case of ng-show the element would be still present in the DOM.

If you write your ng-show using a function call:
<span ng-show="isSecretAgent()">Show me when I'm a secret agent</span>
Then every time a digest runs the value inside the factory will be checked. That's all you should need to do.
Except you should change your isSecretAgent to call the function as follows:
tardis.controller('mainController',["$scope","bgData",
function($scope,bgData) {
$scope.isSecretAgent = bgData.checkIfSecretAgent
}
]);
Rather than assigning to the function call, assign the function (remove the '()' at the end.

Related

ng-repeat is not refreshed when ng-click method called from directive

I am working on creating reusable directive which will be showing composite hierarchical data .
On first page load, Categories like "Server" / "Software"/ "Motherboard" (items array bound to ng-repeat) would be displayed . If user clicks on "Server" then it would show available servers like "Ser1"/"Ser2"/"Ser3".
html :
<div ng-app="myApp" ng-controller="myCtrl" ng-init="init()">
<ul>
<li ng-repeat="item in items">
<div my-dir paramitem="item"></div>
</li>
</ul>
</div>
Now first time Items are loading, but clicking on any item is not refreshing ng-repeat. I have checked ng-click, "subItemClick" in below controller, method and it is being fired. However the items collection is not getting refreshed.
http://plnkr.co/edit/rZk9cbEJU90oupVgcSQt
Controller:
var myApp = angular.module('myApp', []);
myApp.controller('myCtrl', ['$scope', function($scope) {
$scope.init = function() {
$scope.items = [{iname: 'server',subItems: ['ser1', 'ser2','ser3']}
];
};
$scope.subItemClick = function(sb) {
if (sb.subItems.length > 0) {
var zdupitems = [];
for (var i = 0; i < sb.subItems.length; i++) {
zdupitems.push({
iname: sb.subItems[i],
subItems: []
});
}
$scope.items = zdupitems;
}
};
}])
.directive('myDir', function() {
return {
controller: 'myCtrl',
template: "<div><a href=# ng-click='subItemClick(paramitem)'>{{paramitem.iname}}</a></div>",
scope: {
paramitem: '='
}
}
});
I am expecting items like ser1/ser2 to be bound to ng-repeat on clicking "Server" but it is not happening .
Any help?
I think that onClick is screwing up the method's definition of $scope. In that context, the $scope that renders the ngRepeat is actually $scope.$parent (do not use $scope.$parent), and you're creating a new items array on the wrong $scope.
I realize the jsfiddle is probably a dumbed down example of what you're dealing with, but it's the wrong approach either way. If you need to use a global value, you should be getting it from an injected Service so that if one component resets a value that new value is reflected everywhere. Or you could just not put that onClick element in a separate Directive. What's the value in that?

Pass toggling Boolean from service to controller

I want to show/hide an element based on the Boolean value that is changing in my service. I want the change of this Boolean to happen in my service so multiple controllers can access the true or false value, but I am having trouble returning this value to one or more controllers. Currently I'm only able to pass one value which is false, although the value does show it's changing in my service. Here is an example of my controller...
angular.module('myApp')
.service('ThisService', function(){
function toggleDisplay(){
return displayElement = !displayElement;
}
});
.controller('ThisCtrl', function (thisService, $scope) {
function init(){
$scope.displayElement = ThisService.toggleDisplay();
}
$scope.toggleElement = function(){
$scope.displayElement = ThisService.toggleDisplay();
}
init();
});
My HTML...
<div ng-show="displayElement">Show hide me</div>
<button ng-click='toggleElement()'></button>
Can you please tell me how to return the true/false value to my controller correctly?
You can use a value and then toggle that in your service. However, your service definition is not valid, you have a semi-colon in the middle of your chain of modules and you define your service with the name "ThisService", but then you try to reference it in your controller as "thisService" (it's case sensitive).
JS:
angular.module("myApp", [])
.value("DisplayElement", { value: true })
.service("ThisService", function(DisplayElement) {
this.toggleDisplay = function() {
return DisplayElement.value = !DisplayElement.value;
}
})
.controller("ThisCtrl", function(ThisService, $scope) {
function init() {
$scope.displayElement = ThisService.toggleDisplay();
}
$scope.toggleElement = function() {
$scope.displayElement = ThisService.toggleDisplay();
}
init();
});
HTML:
<div ng-app="myApp">
<div ng-controller="ThisCtrl">
<div ng-show="displayElement">Show hide me</div>
<button ng-click="toggleElement()">Toggle Display</button>
</div>
</div>
jsFiddle
You could even eliminate the service and just access the value directly in your controller (you'd have to inject it first).

How to bind content to individual div with promise

This plnkr : https://plnkr.co/edit/BjETLN7rvQ1hNRIm51zG?p=preview binds content to three divs within loop : <div ng-repeat="id in ids">
src :
{ "content" : "divContent" , "id" : "r1" }
<!doctype html>
<html ng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="FetchCtrl">
<div ng-repeat="id in ids">
<div ng-bind="content" ></div>
</div>
</div>
</body>
</html>
// Example of how to call AngularJS $http service and
// process the returned promise
function FetchCtrl($scope, $http, $q) {
$scope.ids = ["r1", "r2", "r3"];
$scope.ids = $scope.ids.map(function(id){
var getString = 'http-hello1.html?id='+id
return $http.get(getString);
});
$scope.responses = [];
$q.all($scope.ids).then(function (values) {
var counter = 0;
values.map(function(m){
$scope.content = m.data.content;
})
})
}
But how bind the result of each get request to the specific div ?
Could add id : <div id="{{id}}" ng-bind="content" ></div> but this means I need to maintain a map of id,value entries ? Is there an idiomatic angularjs way to achieve this ?
I think a directive which dynamically fetches your content might be the answer for you.
angular.module('whateverYourModuleNameIs')
.directive('dynamicRow', ['$http', '$interval', dynamicRowDirectiveFn]);
function dynamicRowDirectiveFn($http, $interval) {
return {
restrict: "EA", // I guess this is your choice how it renders
scope: {
id: '=' // you could grab the id and use it in your request
},
link: function linkFn(scope, element, attrs) {
// Use $interval to repeatedly fetch content
var repeatFetchWhichReturnsAPromise = $interval(fetchNewContent, 60000 * 15) //Executes function every x milliseconds.
// Function that executes every time your interval occurs
function fetchNewContent() {
$http.get('urlYouNeedToFetch'+id).then(
fetchNewContentSucess, fetchNewContentError
);
}
function fetchNewContentSuccess(responseObject){
//This sets your new HTML based on promise success
element = responseObject.data;
}
function fetchNewContentError(responseObject){
//If its a bad request we probably either want to stop repeating
// You can choose to do something else
$interval.cancel(repeatFetchWhichReturnsAPromise);
}
}
}
}
So Instead of using $q.all(), Id recommend individually fetching the content based on a timer or specific trigger. The downside with $q.all() is that if one of the promises fail, they all fail.
In terms of knowing what specific URL the directive needs to fetch, you'll have to provide that information to the directive to be used.
This is a very rough example of a directive that you could write. The upside is that you don't have to worry about bind-unsafe-html or include ngSanitize, you are instead just resetting the value of element inside your link function.
As I don't have a better picture of what you are trying to accomplish from a feature/product standpoint I can only suggest this based on the info provided.

AngularJS factory inside ng-class on body tag not working

Was told to create service/factory and use it on global controller. It's my first time hearing global controller. Anyways, I created a factory called Scroll and injected it on our main.controller.js. The function in the factory is isScrollingEnabled which returns true or false. The other function is setScrolling which will set the variable to true or false. The default value is set to true.
Inside the main controller, I have this code
$scope.scrollEnabled = Scroll.isScrollingEnabled();
console.log('value of $scope.scrollEnabled', $scope.scrollEnabled);
That code spits out true in the console which is good.
on my template, I'm using it this way. I have button that sets scrolling to false. The value in the console spits out false which is good.
<body ng-class="{ 'scrolling' : scrollEnabled }">
However, it's not working. If I change it to the code written below, it works
<body ng-class="{ 'scrolling' : false }">
So I guess, it's not in scope especially ui-view is in index.html and main.controller.js and main.html will be loaded in the ui-view. The < body > is before this which tells me, any scope inside main.controller.js will not work outside of ui-view.
So what's the solution for this?
Sorry for not posting the factory. Here it is
.factory('Scroll', function() {
var scrollEnabled = true; // i then changed it to false hoping it will work, it didn't
var ScrollEvent = {
isScrollingEnabled: function () {
return scrollEnabled;
},
disablePageScrolling: function() {
scrollEnabled = false;
}
};
return ScrollEvent;
});
The $scope of the controller you're attaching the value to doesn't extend to the <body> element. Instead, you can whip together a directive:
.directive('shouldScroll', function (Scroll) {
return {
restrict: 'A',
link: function ($scope, elem) {
$scope.$watch(Scroll.isScrollingEnabled, function (n) {
if (n) {
elem.addClass('scrolling');
} else if (n === false) {
elem.removeClass('scrolling');
}
});
}
};
});
You'd attach it to body like so:
<body should-scroll>
Another solution that works in some cases is just to use class rather than ng-class:
<body class="{{ scrollEnabled ? 'scrolling' : '' }}">

Obtaining scope variable from separate HTML file

I have an Angular template that pulls in two HTML pages ...
sidebar.html
content.html
In content.html I use a controller to make an API call that gives a true/false response and based on that response I render different text. Is there a way that I can gain access to the same true/false response from within sidebar.html? I would rather not duplicate the API call done in the controller for content.html.
content.html
<div data-ng-controller="AuthorizeController">
<div ng-if="isUserAuthorized">
text if User is authorized ...
</div>
<div ng-if="!isUserAuthorized">
text if User is NOT authorized ...
</div>
</div>
AuthorizeController.js
(function () {
var dependencies = [
'$scope',
AuthorizeController
];
module.controller('AuthorizeController', dependencies);
function AuthorizeController($scope) {
// Code to determine if authorized omitted for brevity
if (true) {
$scope.isUserAuthorized = true;
} else {
$scope.isUserAuthorized = false;
}
}
})();
I should add that the portion of the controller that is determining if the user is authenticated does so via an API call. I am hoping to make the API call only once.
Use an angular service (?) to share the authentication logic between your controllers.
Demo: http://plnkr.co/edit/n3iOSkAefxy8xRW7vzFG?p=preview
javascript
var app = angular.module('app', []);
app.controller('mainCtrl',function($scope, authenticationService) {
$scope.isAuthenticated = authenticationService.isAuthenticated();
}
);
app.controller('sidebarCtrl',function($scope, authenticationService) {
$scope.isAuthenticated = authenticationService.isAuthenticated();
}
);
app.factory('authenticationService',function() {
return {
isAuthenticated: isAuthenticated
}
function isAuthenticated(){
// Place your logic here
return true;
}
}
);
html
<body ng-app="app">
<div>
<h1>Obtaining scope variable from separate HTML file</h1>
</div>
<div ng-controller="mainCtrl">
{{ isAuthenticated }}
</div>
<div ng-controller="sidebarCtrl">
{{ isAuthenticated }}
</div>
</body>
There are three options:
1st solution:
Move the logic of Auth to a service, and use dependency injection to inject the service into AuthorizeController and SidebarController.
2nd solution:
Use $broadcast/$emit from AuthorizeController and $on on the SidebarController. Although this is not a good solution because then you will have two controllers tightly coupled.
3rd solution: (Even better than the 1st)
Write the sidebar as a directive and inject the Auth Service to that.

Resources