Append DOM element below active ui-route anchor - angularjs

ui-router offers a nifty feature for anchor tags, ui-sref-active. It adds a class when the associated route or any child route is active.
I'd like to append an element to the DOM using the same rule, e.g.:
<ul class="books" ng-controller="TreeController">
<li ng-repeat="book in books">
<a ui-sref="book({id: book.id})" ui-sref-active="active">
{{book.title}}
</a>
<!-- NG-IF BELOW IS THE CODE IN QUESTION -->
<ul class="chapters" ng-if="UI-SREF-ACTIVE">
<li ng-repeat="chapter in chapters(book.id)">
What can I replace ng-if="UI-SREF-ACTIVE" with?
I can't use the CSS adjacent-sibling rule a.active + ul { display: block; }. I must wait to create the element because chapters(book.id) must lazy-load.
I also can't use $state.is('book',{id:book.id}). It doesn't return true for child routes.

Related

ng-class not working in one situation. What are some possible cause of this?

I'm stuck. Cannot figured this out. This question is very simple to show, but I'm not really sure how to put it as a question, therefore I'll try my best.
First, here's the layout of my whole app (The problem lies in the Header.jsp):
<jsp:include page="../home/Header.jsp" />
<jsp:include page="../home/Modals.jsp" />
<div data-ng-view data-save-scroll-position data-position-relative-to-menu></div>
<jsp:include page="../home/Footer.jsp" />
The problem is very simple. I have the following data-ng-class in the data-ng-view section that change a tab to active if something is true (The problem is it won't work in one scenario even though it displayed true in the tab name):
<ul class="nav nav-tabs">
<li role="presentation" data-ng-class="tab.isSelected ? 'active' : ''" data-ng-repeat="tab in ctrl.tabs"
data-ng-click="ctrl.fetchBIReports(tab)">
</li>
</ul>
In the JSP that use data-ng-include for the above markup, there's a side nav to change to this page. Once clicked this side-nav, it highlighted the tab 'active' as expected (trying not to include the whole jsp):
<div class="side-navbar">
<ul>
<li class="{{ ctrl.navigate.path == 'bi/schedule' ? 'active-link' : 'normal-link'}}">
Schedule Reports
</li>
</ul>
</div>
<div class="content-right" data-ng-include="ctrl.navigate.path"></div>
content-right includes the JSP mentioned in the second markup.
So far, so good. Here's a demo of it working (including both side-navbar and content-right):
The problem is, in my Header.jsp, there's a nav bar that takes me to the same page. If it is clicked from a different page with different controller, then it works. But if I'm in the current controller and click that nav bar link, then data-ng-class does not take 'active' as its class. Here's the markup for the Header.jsp for that link:
<li class="dropdown" data-roles="['ROLE_ADMIN']">
<a href="#/bi" class="dropdown-toggle" data-toggle="dropdown" data-ng-click="ctrl.changeNavigation('bi/schedule')"
role="button" aria-haspopup="true" aria-expanded="false">BI Management<span class="caret"></span></a>
<ul class="dropdown-menu">
<li>Schedule Reports</li>
</ul>
</li>
Here is the demo of it not working even though it is printing out true in the UI:
The only problem is with this UI. All the data are populated. Records are displayed for the correct tab. Even side nav-bar is displaying the correct active class.
Your syntax for ng-class is off a bit. The format is "{ '[class-name]': [expression that evaluates to true or false] }". You can have multiple class values separated by commas each with their own expression. When an expression is true, the corresponding class is applied to the element and when it is false the class is removed from the element. The way you have written it would almost work for the plain class attribute, but you would need to include the interpolation characters: {{ and }}. Here is a very simple example to illustrate ng-class.
angular.module('app', []);
.red {
color: #fff;
background-color: #e21d1d;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app="app">
<label>
<input type="checkbox" ng-model="applyRedClass" /> Apply 'red' class
</label>
<div ng-class="{'red': applyRedClass}">
This is a simple example of how to use ng-class.
</div>
</div>

AngularJS performance comparison between ng-class on root over multiple ng-if

If I need to use the same conditional in multiple places within one DOM parent, is it better to control this with CSS and ng-class (display: none) or use multiple ng-if (removes element from DOM but adds more watchers)?
Option 1. Multiple ng-ifs:
<div ng-repeat="item in items track by item.id">
<p ng-if="item.active">Show some active info</p>
<p>Some general info</p>
<ul>
<li ng-if="item.active">Some bullet with active info</li>
<li>Some bullet with active info</li>
<li ng-if="item.active">Some bullet with active info</li>
</ul>
</div>
Option 2. Single ng-class:
<div ng-repeat="item in items track by item.id" ng-class="{'active': item.active}>
<p class="if-active">Show some active info</p>
<p>Some general info</p>
<ul>
<li class="if-active">Some bullet with active info</li>
<li>Some bullet with active info</li>
<li class="if-active">Some bullet with active info</li>
</ul>
</div>
Together with CSS
.if-active {
display: none;
}
.active .if-active {
display: default;
}
Question
Disregarding the above contrived examples and assuming (1) the ng-repeat iterates over hundreds of entries and (2) one-time binding of the active property isn't possible, is it generally better to add more watchers with ng-ifs or add more work on the CSS rendering?
I have not seen this pattern discussed in any of the AngularJS optimization guides I've come across (maybe because it's too obvious).
Each ngIf directive adds a watcher to the digest cycle, so it will consume more memory.
Now I don’t know if your item.active property is changing during the lifecycle of your application, but if not you could make a directive ifActive that effectively removes the element from the DOM tree after a single check.
On the other side, “thousands of entries” will probably blow up your memory usage anyway. I suggest you take a look at “smart” ngRepeat alternatives that use virtual scrolling.

Is it possible to combine ng-include and other static content?

I am just getting started working with AngularJS and have stumbled on combining multiple bits of content under the same element. In our previous version (using knockout/durandal) we were able to leverage "container-less syntax" to make this work but it doesn't seem like we can do the same with AngularJS.
<ul>
<li>this item comes from an ng-include</li>
<li>this item is defined statically</li>
</ul>
What I thought would work:
<ul>
<ng-include src="'thefile.html'" />
<li>this item is defined statically</li>
</ul>
Unfortunately, the resulting html includes an extra DOM layer wrapper which breaks my css - I am using a "ul > li" selector.
<ul>
<ng-include class="ng-scope" src="'thefile.html'">
<li class="ng-scope">
...
</li>
</ng-include>
<li>this item is defined statically</li>
</ul>
Attempt #2 was to include the content on the itself. It also didn't work. The static content was omitted completely:
<ul ng-include src="'thefile.html'">
<li>this item is defined statically</li>
</ul>
Is there a smarter/correct way to do this? I believe I'm looking for something similar to a "replace" property that I could use on a directive.
Thanks in advance.
If your css is the only thing that is causing you problems. Try
ul li{
color:red;
}
or
ul > li,
ul > ng-include > li{
color:red;
}

ng-repeat inserting empty anchor tags

I'm trying to create a menu using angular. A menu item can have children requiring another ng-repeat to print the sub nav items. I'm noticing some strange behavior when attempting to insert an anchor tag within the 2nd ng-repeat.
Link to fiddle: http://jsfiddle.net/npU7t/
<li ng-repeat="sub_menu_item in menu_item.sub_menu">
<a href="">
{{ sub_menu_item.title }}
</a>
</li>
With
{
title: 'menu item with children',
sub_menu: [
{
title: '<-- empty anchor tag???'
}
]
}
Results in
<li ng-repeat="sub_menu_item in menu_item.sub_menu" class="ng-scope">
<-- empty anchor tag???
</li>
Where the did duplicate / empty anchor tag come from? How can I prevent it from being created?
Appreciate the help!
This isn't a bug with Angular, but rather how you have your markup.
UPDATE:
The issue is actually the nested <a> tag, not the <ul> tag.
<a href="">
<span class="title">{{ menu_item.title }}</span>
<ul class="sub-menu" ng-if="menu_item.sub_menu">
<li ng-repeat="sub_menu_item in menu_item.sub_menu">
<a href="">
{{ sub_menu_item.title }}
</a>
</li>
</ul>
</a>
In fact, if you remove Angular from the equation altogether, you will see that the extraneous <a> tag is still added to the DOM: http://jsfiddle.net/jwcarroll/cXkj4/
If you get rid of the nested <a> tag, then the extra element will disappear.
<a href="">
<span class="title">{{ menu_item.title }}</span>
</a>
<ul class="sub-menu" ng-if="menu_item.sub_menu">
<li ng-repeat="sub_menu_item in menu_item.sub_menu">
<a href="">
{{ sub_menu_item.title }}
</a>
</li>
</ul>
In both HTML 4.01, and HTML 5, having a nested <a> tag is a no no.
The simplest possible recreation of the problem I could come up with is this bit of markup:
<a href="">Outer
<p>Blah
Inner
</p>
</a>
Because you can't nest <a> elements within each other, the browser is doing it's best to recreate your intent while keeping the DOM clean. What you end up with is this:
Outer
<p>
Blah
Inner
</p>
This makes sense when you realize what the browser is trying to do. The browser is trying to do three things:
Keep Outer, Blah and Inner text elements inside hyperlinks
Contain Blah and <a>Inner</a> inside a single <p> tag
Ensure no <a> tags are nested within each other
The only sensible way to accomplish all three of these is to wrap both Outer and Blah text elements in separate <a> tags in a way that isn't nested. This is the closest approximation to the original intent without breaking the DOCTYPE rules.
I hope this helps.
Very strange. It doesn't appear with any tag besides <a> (like <p> or <div>). It looks like an outright bug to me - I'd submit a proper bug report.

How to change the content of a tab while you are on the same tab using AngularJS and Bootstrap?

Using AngularJS and Bootstrap, let say there are 3 tabs: tab1, tab2, and tab3. There are also some links on each tabs. Now for example, tab1 is active. The question is: how to change the content of the tab1 by clicking a link within the same tab?
main.html:
<div class="tabbable">
<ul class="nav nav-tabs">
<li ng-class="{active: activeTab == 'tab1'}"><a ng-click="activeTab = 'tab1'" href="">tab1</a></li>
<li ng-class="{active: activeTab == 'tab2'}"><a ng-click="activeTab = 'tab2'" href="">tab2</a></li>
<li ng-class="{active: activeTab == 'tab3'}"><a ng-click="activeTab = 'tab3'" href="">tab3</a></li>
</ul>
</div>
<div class="tab-content">
<div ng-include="'/'+activeTab"></div>
</div>
tab1.html:
<h1>TAB1</h1>
Something
something.html
<h1>SOMETHING</h1>
Now the question is how to change the tab1 content to something.html while the tab1 is active?
As pointed out in other examples there are many ways to do this. Direct DOM manipulation is not really the Angular way of thinking about this kind of use case. A better way to think about it might be:
What possible content can this tab contain?
What will control its' being displayed?
Using the ng-if or ng-switch directive allows you to selectively limit the content based on a variable defined in the scope.
Consider this possibility:
<div class="tabbable">
<ul class="nav nav-tabs">
<li ng-class="{active: activeTab == 'tab1'}"><a ng-click="activeTab = 'tab1'" href="">tab1</a></li>
<li ng-class="{active: activeTab == 'tab2'}"><a ng-click="activeTab = 'tab2'" href="">tab2</a></li>
<li ng-class="{active: activeTab == 'tab3'}"><a ng-click="activeTab = 'tab3'" href="">tab3</a></li>
</ul>
</div>
Based on your code for the included file you could do this:
<div class="tab-content">
<div ng-if="content==='A'" ng-include="'/'+activeTabA"></div>
<div ng-if="content==='B'" ng-include="'/'+activeTabB"></div>
</div>
Another approach is to utilize ng-view and routing. It is more complicated than conditionally including but less complicated than writing a whole new directive.
In short, you define a container element with the ng-view attribute like this
<div ng-view></div>
Then set up a routing table in your javascript code like this
$routeProvider.when('/tab1', {templateUrl: 'partials/tab1.html', controller: 'tab1Controller'});
$routeProvider.when('/tab2', {templateUrl: 'partials/tab2.html', controller: 'tab2Controller'});
For more detail see this link: http://docs.angularjs.org/api/ngRoute.directive:ngView
This is actually a fairly common AngularJS issue, and is handled pretty nicely by nested directives. It works well enough that it's actually one of the demos in the Custom Directive Guide on the AngularJS docs page. It's the last example, "Creating Directives that Communicate". You can see the full code there, but the idea is that you create a 'container' directive for the tab group and a 'pane' directive for the inside content. Your HTML ends up looking like this:
<my-tabs>
<my-pane title="Hello">
<h5 id="pane1">Hello</h5>
<p>This content is in the first pane.</p>
</my-pane>
<my-pane title="World">
<h5 id="pane2">World</h5>
<em>This content is in the second pane.</em>
</my-pane>
</my-tabs>
As #musically_ut pointed out in his comment, there are a lot of ways to handle this. This is just one way, but I think it works out pretty well.

Resources