AngularJS - Role Based Navigation - javascript

I have a application using Ruby on Rails (Devise/CanCan for Authentication/Roles) and a AngularJS client.
I have 3 roles - each with different navigation menus. I'd rather not have 3 different views with different navigation bars- is there a way I can show/hide navigation elements based on which user is loaded?
Anyone familiar with this or have any good ideas? I did some hunting but came up with little to no success... Anything helps!

I know you already marked an answer but I wanted to point out a nuance related to performance.
Depending on the size of your menu and HTML you might want to go beyond ng-show to use ng-if. The problem with ng-show is that all of the nodes are compiled, even the ones the user will never use. For example, a Manager may never use the Admin or User nodes but they are still parsed and compiled.
If you use ng-if you can avoid that and only render/compile the fragments when the condition is true. Since you are always going to use the same controller, you wouldn't have to repeat it:
<div ng-controller="AccountController">
<div ng-if="IsAdmin()">...admin nav...</div>
<div ng-if="IsUser()">...user nav...</div>
</div>
We are on a massive Angular application and small changes like this reap major performance benefits. When the "if" expression fails, the element is removed from the DOM and never compiled, vs. ng-show while will still compile the element and simply hide it.

You can use the ng-show tag.
You will do something like this:
<div ng-show="IsAdmin()" ng-controller="AccountController" >...admin nav...</div>
<div ng-show="IsUser()" ng-controller="AccountController" >...user nav...</div>
<div ng-show="IsManager()" ng-controller="AccountController" >...manager nav...</div>
..Controller
$scope.IsAdmin = function(){
return $scope.UserRole == "Admin";
}
$scope.IsUser = function(){
return $scope.UserRole == "StandardUser";
}
$scope.IsManager = function(){
return $scope.UserRole == "Manager";
}
In your controller you determine some logic to show each nav based off of the role then return true/false.

Related

why use ng-hide when ng-show can work for both situations

I have gone through so many questions like this but got this only solutions that ng-show by default hides the element and show it if condition is true and on the other hand ng-hide by default show the element and hide it when condition is true.
But my concern is the condition can be taken care of with ng-show or ng-hide only then why we use different things.
For example
I saw this somewhere in this code user is using ng-show and ng-hide both
<div ng-init="isShow = 'one'">
<a href="#" ng-click="isShow == 'one' ? isShow = 'two' : isShow = 'one'">
<div ng-show="isShow=='one'">
If One show this
</div>
<div ng-hide="isShow=='one'">
If Two show this
</div>
</div>
But according to me this can be achieved also with this code
<div ng-init="isShow = 'one'">
<a href="#" ng-click="isShow == 'one' ? isShow = 'two' : isShow = 'one'">
<div ng-show="isShow=='one'">
If One show this
</div>
<div ng-show="isShow=='two'">
If Two show this
</div>
</div>
So what exactly is the difference between both the codes. There must be some specific difference if ng-show and ng-hide both exists. Anyone know it?
Thanks in advance!
I don't see any reason for this question to be downvoted - it's a valid thought. The reason is simple, though. AngularJS has "declarative" as one of its core philosophies. If 90% of the time you want an element shown, but occasionally it should be hidden, ng-hide="thatcondition" clearly indicates when it should be hdiden. If most of the time it should be HIDDEN, then ng-show="thatrarecondition" is more readable.
Clear, readable code is an important principle in any framework, and especially in AngularJS. The ! operator is narrow and easily missed, far more than any of the other comparisons like >, <, >=, <=, etc. Providing positive- and negative-visibility operators makes it much more readable what's going on here.
An important detail to note is that both directives look for "truthy" values, not exactly-equal ones. JS is pretty vague about this, and sometimes that's an advantage. For example, suppose you have an object that may have a sub-object (a detail element). You might have the detail-display DIV written as follows:
<div ng-show="{{ object.details }}">
<!-- Render object.details here -->
</div>
This "truthy" comparison is also handy for the negative case. Suppose you want to HIDE an order-cancellation block in a sales system if the order has been shipped. Consider:
<div ng-hide="{{ order.shipped }}">
Want to cancel this order? click here
</div>
Why is this important? Well, it means ANY non-undefined/null value for order.shipped will hide this block! That means if today, you set it as a true/false, it will work. But tomorrow you change it to a DATE that the order was shipped? The rule will still work! This makes it easy to code (and maintain) displays like this.
This is actually pretty good explained in the docs. AngularJS ngShow
Here's a quote from the docs: "The ngShow directive shows or hides the given HTML element based on the expression provided to the ngShow attribute. The element is shown or hidden by removing or adding the .ng-hide CSS class onto the element. The .ng-hide CSS class is predefined in AngularJS and sets the display style to none (using an !important flag)."
Regarding ngHide: AngularJS ngHide
Quote: "The ngHide directive shows or hides the given HTML element based on the expression provided to the ngHide attribute. The element is shown or hidden by removing or adding the ng-hide CSS class onto the element. The .ng-hide CSS class is predefined in AngularJS and sets the display style to none (using an !important flag)."
Also check out this for short but accurate explanation about different Angular DOM handling: http://www.w3schools.com/angular/angular_htmldom.asp
So in the end they actually do the same thing. And as far as I know, you should not use them in combination. If you would like to create multiple boolean values as parameters to either one of them you could do it like this: <div ng-show="value1 && !value2">Something</div>. Still I suggest that if you need more paramter values you should go with a function.
<div ng-show="ShowMe()">Content</div>
$scope.ShowMe = function(){
return $scope.value && !$scope.value2;
}

Toggling between templates displayed by an angular directive

I have an angular app that shows a list of things
<div ng-repeat="thing in things" regular-thing>
implemented with an ng-repeat that shows a regularThing directive for each thing in my list.
It's actually a little bit trickier than that though:
<div ng-repeat-start="thing in things">
<div ng-if="$first || thing.isSpecial" special-thing></div>
<div ng-if="!$first && !thing.isSpecial" regular-thing></div>
</div>
<div ng-repeat-end></div>
I use a different directive for the first thing in my list, and also for any "special" things. For our purposes, a thing becomes "special" when the user clicks on it - so, as they're scrolling through the list, they can click on a thing to have it displayed in a different (more extensive) template.
The way I have it now feels wrong to me. For one thing, I really don't need to have two different directives - just different templates. The logic is identical, specialThing just has a bit more of it. For another thing, I'm toggling a property on the data (namely thing.isSpecial) for purely view-related reasons, which makes me die a little bit inside.
So my question: Don't I deserve to die a little bit inside for this? Isn't there a cleaner, more "angular" way to handle this (i.e. to toggle between the directive templates)?
First, no one deserves do die. So the answer to your question is "no".
But you do raise some interesting points.
First, it's ok to have "view model" information in your scope (or controller, depending on if you are using the ControllerAs syntax or not). However, you definitely don't want to add view model information to your data models. Here's how I might do it (using your click-toggles-something-special example).
<div ng-repeat-start="thing in things">
<div ng-if="$first || isSpecial" ng-include="/specialtemplate.html" ng-click="isSpecial = !isSpecial"></div>
<div ng-if="!$first && !isSpecial" ng-include="/regulartemplate.html" ng-click="isSpecial = !isSpecial"></div>
</div>
<div ng-repeat-end></div>
The key difference is I'm adding the isSpecial property to the scope, not to thing, and isSpecial will be specific to that particular ng-repeat item's scope.
Also, unless you're planning on doing DOM manipulation, you can replace them with ng-include + ng-controller in the template html.
Some people prefer that pattern (include + controller) instead of directives, and other prefer to go ahead and others prefer to go ahead and write directives because it's more "componenty" (I made that word up). I think either is a valid way to go.

AngularJS ng-include only once

How can I use ng-include in such way that it's content will be loaded only once?
Here is what I have:
<div data-ng-if="%condition-1%" data-ng-include="%url-1%"></div>
<div data-ng-if="%condition-2%" data-ng-include="%url-2%"></div>
<div data-ng-if="%condition-3%" data-ng-include="%url-3%"></div>
...
In my case only one condition is true at some moment of time.
And any condition can change its value many times during page lifetime.
So ng-include will load the same content again and again.
How can I tell Angular to process ng-include only once - when the appropriate condition becomes true for the first time?
Loading them all at once will kill the page because every template is large and heavy.
Also there is no strict sequence of condition changes, for example, condition-3 may never become true during page lifetime - I'd like not to load url-3 content at all in this case.
Thanks!
UPDATE
Yes, template is already on cache. But it has a complicated internal structure like references to external images, iframes and so on - all this things are reloading each time when I'm using ng-include.
You have many solutions but only 2 come to my mind at the moment
1° Replace the ng-if for a ng-show, as the ng-if deletes the dom and all children scopes available, forcing the framework to make the request once again, while if you were using ng-show, the dom would only be hidden and the request would have only be made once.
2° If you do need to use ng-if and the content from the server is static, you could cache it on the javascript layer by manually accesing the $templateCache service provided by angular, or if the content you wish to load is html, you could either use the $templateCache service on the javascript layer or use the ng-template tag to preload that data.
Example:
<script id="url/you/want.html" type="text/ng-template">
<div>I am preloaded dom that responds to the url/you/want.html
requests made by this application
</div>
</script>
Cheers
How about using only one ng-include and using some logic in the controller to switch which source to use using a binding? This way only one will ever be loaded at a time.
Controller
function($scope) {
$scope.activeTemplate = null; //some default or even null
$scope.$watch('condition', function(newvalue) {
//whatever logic you need to switch template
if (newvalue == 'condition1') {
$scope.activeTemplate = '/path/to/condition1.html';
} else if (newvalue == 'condition2') {
$scope.activeTemplate = '/path/to/condition2.html';
} else {
$scope.activeTemplate = '/path/to/default.html';
}
});
}
This way only one template will ever be loaded at a time, and you've reduced the number of bindings from 3 to 1. (however you have added a watch so effectively from 3 to 2 maybe)

Minimal jQuery template

I am creating a UI, in which user can add / delete items (of similar layout).
It starts with one item and you can click 'add' to add more. The UI consists of several different types of items.
What I am doing currently is populating a single item item 1 ( of each type ) and on add event, I clone the item 1, replace the changes done by user in item 1 and append the clone to the container.
In simple words, instead of dynamically creating html with jQuery, I am cloning html of a div. But in this approach , I had to change a lot of things to keep to give the new item to initial state.
So, I want to avoid the replacing the edits done by user, so I was thinking something like below,
<script type="text/template" id="item_type1">
<div>
<div>Box</div>
</div>
</script>
<script type="text/template" id="item_type2">
<div>
<div>Box2</div>
</div>
</script>
And on add event, I want to do something like $('#item_type1').html() and $('#item_type2') to create new items.
I know there are sophisticated libraries like handlebar and mustache and underscore has its own way of implementing templates.
But I am not using any of these already and thus do not want to included them just to copy content. I dont want anything special. I am not passing variables. I am just cloning some markup to use again and again.
Is this way to insert html in script tags , going to work in all browsers ? and is it a good way ?
EDIT:
Its for the wp plugin and I assume js is turned on , else the plugin wont work anyways.
What about:
Your HTML should be, for example:
<script type="text/template" id="item_type1">
<div>
<h1>Box1</h1>
<p>
</p>
</div>
</script>
And your code would be:
var templateHtml = $('#item_type1').html();
var $item = $(templateHtml);
$('body').append($item);
$item.on('click', function() {});
This is an easy way that will work on all browsers.
Step 1: Create an HTML file with your template inside of it
Step 2: Using jQuery's load() method, call your HTML template into a div element in the main HTML file:
$("#main-div").load("yourtemplate.html")
Step 3: Be amazed
Is this a good idea? It depends:
If it's a self contained application on a known environment with a determined supported browser and with equally determined settings (like if JavaScript is on or not) then yea, sure. Why not?
If it's open to the public in every single browser possible with many different configurations, then no, it's a horrible idea. If your user doesn't have JavaScript enabled, then your content doesn't show up. Also, if one of your scripts break in production, then you are again left with no content. You can learn this lesson from when Gawker made this same mistake

How to use inline ternary with data-bind variable?

I'm currently creating a footer with Save/Cancel/Delete, depending on where the user is. Now I'm trying to not show/render the Delete button when it's not required. How to achieve this using a variable from KnockoutJS (observable) as the operator in a ternary?
Current code doesn't work properly but below anyway.
<li>#(Global.ButtonCancel)</li>
<script>
var button = "<li>#(Global.ButtonDelete)</li>";
isEditingProduct ? button : false;
</script>
<li>#(Global.ButtonSave)</li>
The error i keep getting is that "isEditingProduct" is undefined. When i use it inline (outside the script), for a straight <li data-bind="isEditingProduct" ></li> with the other stuff inside it works. It hides the button, but leaves me with a gaping hole in the footer. Which is why I'm trying to get around it by not loading it in for rendering at all if it's not yet needed.
Any help would be appreciated.
Taking a look at your code, I'm confused.
No idea why you feel you need a ternary to hide/unhide elements.
Use the visible: binding.
<li data-bind="visible: isEditingProduct"></li>
isEditingProduct needs to be a property on your view model.
You could use visible or if binding:
<li>#(Global.ButtonCancel)</li>
<li>#(Global.ButtonDelete)</li>
<li>#(Global.ButtonSave)</li>
Read documentation about these bindings:
http://knockoutjs.com/documentation/if-binding.html
http://knockoutjs.com/documentation/visible-binding.html
Not sure where you "isEditingProduct" is defined but you can't simply reference a part of your View Model in JavaScript without fully qualifying it. Instead try:
myViewModel.isEditingProducts = true;
Also, the location of your script block is confusing. It shouldn't be in-lined between <li /> tags. The script will not necessarily execute at that time (as the browser is parsing your markup).

Categories

Resources