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.
Related
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;
}
I'm using Angular 1.x and I have a section of code that I'm looking to repeat quite a bit, so I wanna throw it in a directive. The trouble is, it's somewhat complicated and I'm not sure how to begin writing it.
Essentially, it's a section of the page that displays various card directive and with infinite scrolling and perfect scrollbar.
<perfect-scrollbar refresh-on-change="myScope.data">
<div class="limit-columns">
<masonry masonry-options="{gutter: 30, isFitWidth: true}">
<user-card class="masonry-brick" ng-repeat="item in myScope.data"></user-card>
</masonry>
<div class="infinite-scroller" infinite-scroll="myScope.showMore()" infinite-scroll-tolerance="5"></div>
</div>
</perfect-scrollbar>
Perfect-scrollbar and masonry are both angular libraries on GitHub. Infinite-scroller is one I wrote myself, but works as you'd expect.
myScope contains a data attribute that is a list of objects containing a card's data. myScope.showMore is a function that adds items to that myScope.data list. Perfect-scrollbar also takes the refresh-on-change attribute which watches for changes on a particular object, in this case the list.
Ideally my directive would look something like this:
<card-scroller gutter="30" tolerance="5">
<some-card ng-repeat="achievements.data"></some-card>
</card-scroller>
But I'm not sure how feasible this is. Thanks!
I have a need to use the same directive within a directive, depending on a conditional param. However, when ever i try to do it, it seems to go to an endless loop. As i understand, it tries to pre-load the templates and that causes an endless recursion and at the end, it just throws me the following error:"RangeError: Maximum call stack size exceeded".
I have created an example in fiddle.. as you can see in the example, when the param's value is "1", it creates the error (even when the second level param is valued as "2" so it shouldn't have real recursion issues in the controller/app).
https://jsfiddle.net/qh9nh1gx/
"custom-directive"'s template:
<div>
<div ng-if='info==1'><div custom-directive info='2'></div></div>
<div ng-if='info==2'>DONE,single.</div>
</div>
Thanks
I have found 2 options to deal with the issue, the first one, is exactly what Jju described - creating a new "compiler" method (it can be grabbed from the url he sent).
The second option - always using an additional template for the "recursive" parts of the directive. For example, in my directive, i had a "ng-repeat" part that depending on the items value, it could request to display the directive again. while i used "ng-include" to have the other directive, it worked.
<div ng-repeat="item in items" ng-include="'inline-possibly-recursive-template"'></div>
in that template, you can call the directive again without any issues..
I hope that it will anyone else that will stumble into that issue.
You can look into https://stackoverflow.com/a/19065910/1680674 that describe a common approach to create directive that use himself inside
I'm fairly new to Angular JS and I'm trying to create a datepicker directive. It will not work like the Angular UI Bootstrap datepicker as it won't use a textbox but a full page calendar which you will be able to swipe through and click a day (which will update ngModel).
My plan was to have a single directive which has a renderMonth() function. This function would accept a month as a parameter and generate all the rows/days in an array which would then be bound to a template for the month.
My problem is that I can specify a template for the datepicker in my directive declaration but I don't know how to specify templates for the rows/days etc and load them in and bind them. I could do it using jQuery and lots of string concetenation, but that seems all wrong.
I have been reading the source code for the Angular UI Datepicker but as a newbie it makes very little sense to me. They have decoupled everything into many sub-directives (month directive, year directive etc.) and they have their own templates, but that's not what I want to do because my Angular skills aren't going to stretch to creating directives which communicate with each other. The Angular UI code is just way too complicated for me.
One thing I do like is that they use existing directives like ng-repeat in their templates and bind them to the array or rows/days. For me to do that I'd need to load the template in each time the renderMonth() is called and compile the template because it has existing directives in it.
So, basically my question is, does anybody have any examples of how I could write a import a template into my render function, compile it and ten bind it to the row/day data which is in my directives scope.
I'll be honest. I have no idea if I'm even speaking any sense. I'm just typing words which sound vaguely right.
ALl I need is for somebody to point me in the right direction. Thanks.
As discussed in the comments, I personally wouldn't worry too much about importing templates, the $compile function, or anything that complex for what is relatively simple markup.
I've created a (very) basic barebones plunker here, to demonstrate what my approach would be. I've used moment.js to deal with dates, because I am terrible with vanilla javascript date manipulation and find it very clumsy.
This is the template:
<div class="controls">
<button ng-click="prevMonth()"><-</button>
<span>{{selected.format('MMMM')}}</span>
<button ng-click="nextMonth()">-></button>
</div>
<div class="month">
<span class="day" ng-repeat="day in selected.days">
{{day.number}}
</span>
</div>
The template relies on the scope having a selected property which is a moment object. All it does is creates some buttons change the month and displays the month name at the top, then creates a span for each day in that month.
A tiny bit of CSS puts the days in rows of 7:
span.day{
float: left;
width: 25px;
}
span.day:nth-child(7n+1){
clear:left;
}
This is the directive's link function:
link: function(scope, element, attributes){
// Set the selection to now initially.
scope.selected = moment();
generateDaysArray = function(){
// -- REMOVED FOR BREVITY -- //
return days;
}
// Watch the month for changes and update the days array
scope.$watch(
function(){
return scope.selected.month();
},
function(newVal, oldVal){
scope.selected.days = generateDaysArray();
}
)
// Control button actions
scope.nextMonth = function(){
scope.selected.add('month', 1)
}
scope.prevMonth = function(){
scope.selected.subtract('month', 1)
}
}
There is really nothing special there, but do note that the $watch command used takes advantage of the fact that you can pass a function instead of a string (though looking at it now you could just use 'selected.month()' as a string).
Generating the days array is slightly more complex if you want to align the dates with days of the week, which I started to do, but didn't finish. My plan was for each object in the array to have a isPreviousMonth property which would conditionally apply a different style using ng-class, and would look up the correct number as well, rather than just using a ~ for the offset days!
There is a lot more to do to finish this off - not least making the dates selectable and integrating with ngModel, but hopefully this example has given you an idea of one way to approach the problem of templating. There are many other ways you could do this, but I find this the most intuitive.
Extension
To address your question regarding having multiple months, I've expanded the plunker a bit.
I added an attribute to the directive:
<date-picker num-months="3"></date-picker>
And modified the template to ng-repeat certain parts depending on the number of months you specify in the attribute:
<div class="controls">
<button ng-click="prevMonth()"><-</button>
<span ng-repeat="month in months" class="month-title">
{{month.moment.format('MMMM')}}
</span>
<button ng-click="nextMonth()">-></button>
</div>
<div class="month" ng-repeat="month in months">
<span class="day" ng-repeat="day in month.days">
{{day.number}}
</span>
</div>
So this instead requires an array of months, each with 2 properties: a moment, and an array of days. I added some quick and dirty javascript to generate that, but I won't paste it here as there are almost certainly much neater ways of doing it!
Note that at present we're still watching scope.selected for changes in the month, which would lead to the (probably unwanted) behaviour that if you selected a date not in the first month, the calendar would rearrange itself. I think instead of using a $watch in this case, I'd just add to the click events to handle redrawing the calendar properly.
PS This addresses the cloning part but not the scrolling part. For scrolling, I would recommend adding one extra month on either end of the months array, then having an ng-show condition that means only the middle month(s) is shown. You can then take advantage of angular's inbuilt animations (this is a good reference) to scroll them in/out using css. If you want more info about that contact me through the chat I opened for this question.
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.