AngularJS UI Bootstrap Tabs that support routing - angularjs

I would like to use AngularJS UI Bootstrap Tabs in my project, but I need it to support routing.
For example:
Tab URL
--------------------
Jobs /jobs
Invoices /invoices
Payments /payments
As far as I can tell from the source code, the current tabs and pane directives doesn't support routing.
What would be the best way to add routing?

To add routing you typically use an ng-view directive. I'm not sure it's easy enough to modify angular UI to support what you're looking for, but here's a plunker showing roughly what i think you're looking for (it's not necessarily the best way of doing it - hopefully someone can give you a better solution or improve on this)

Use data-target="#tab1". Worked for me

This answer really helped me http://odetocode.com/blogs/scott/archive/2014/04/14/deep-linking-a-tabbed-ui-with-angularjs.aspx (very simple but powerful solution)

I also have this requirement and I'm doing something similar to the answer provided by Chris, but I'm also using AngularUI router, because ngRouter does not support nested views and it is possible you'll have the tabs content view inside another view (I did) and that won't work with ng-view.

you could pass your own custom key value pairs in the route definition and achieve this.
here's a good example:
http://www.bennadel.com/blog/2420-Mapping-AngularJS-Routes-Onto-URL-Parameters-And-Client-Side-Events.htm

Agree with the use of UI-Router which tracks states and works great with nested views. Speaking particularly of your Bootstrap tabs issue there is a great implementation that leverages UI Router: UI-Router Tabs

If you have a route called settings and you want to have tabs in that settings page something like this works.
<div class="right-side" align="center">
<div class="settings-body">
<ul class="nav nav-tabs">
<li class="active"><a data-toggle="tab" href="#!/settings#private_email">Private email</a></li>
<li><a data-toggle="tab" href="#!/settings#signature">Signature</a></li>
<li><a data-toggle="tab" href="#menu2">Menu 2</a></li>
</ul>
<div class="tab-content">
<div id="private_email" class="tab-pane fade in active">
<div class="row" ng-controller="SettingsController">
<div>
<button class="btn btn-primary" ng-click="activatePrivateEmail()">Activate email</button>
<button class="btn btn-danger">Deactivate email</button>
</div>
</div>
</div>
<div id="signature" class="tab-pane fade">
<textarea ui-tinymce="tinymceOptions" ng-model="signature"></textarea>
<div class="send-btn">
<button name="send" ng-click="" class="btn btn-primary">Save signature</button>
</div>
</div>
<div id="menu2" class="tab-pane fade">
<h3>Menu 2</h3>
<p>Some content in menu 2.</p>
</div>
</div>
</div>
</div>

I got tabs with routing working the following way.
It's able to do everything you want from dynamic tabs, and it's actually very simple.
Tabs with a ui-view, so it can dynamically load templates,
Update routing in URL
Set history state
When directly navigating to a route with a tabbed view, the correct tab is marked as active.
Define the tabs with a parameter in your router
.state('app.tabs', {
url : '/tabs',
template : template/tabs.html,
})
.state('app.tabs.tab1', {
url : '/tab1',
templateUrl : 'template/tab1.html',
params : {
tab : 'tab1'
}
})
.state('app.visitors.browser.analytics', {
url : '/tab1',
templateUrl : 'template/tab2.html',
params : {
tab : 'tab2'
}
})
The tabs template (tabs.html) is
<div ng-controller="TabCtrl as $tabs">
<uib-tabset active="$tabs.activeTab">
<uib-tab heading="This is tab 1" index="'tab1'" ui-sref="app.tabs.tab1"></uib-tab>
<uib-tab heading="This is tab 2" index="'tab2'" ui-sref="app.tabs.tab2"></uib-tab>
</uib-tabset>
<div ui-view></div>
</div>
Which is paired with a very simple controller for getting the current active tab:
app.controller('TabCtrl', function($stateParams) {
this.activeTab = $stateParams.tab;
})

just a small add to accepted answer:
i needed to keep the current tab at page refresh so i used a switch like this:
$scope.tabs = [
{ link : '#/furnizori', label : 'Furnizori' },
{ link : '#/total', label : 'Total' },
{ link : '#/clienti', label : 'Clienti' },
{ link : '#/centralizator', label : 'Centralizator' },
{ link : '#/optiuni', label : 'Optiuni' }
];
switch ($location.path()) {
case '/furnizori':
$scope.selectedTab = $scope.tabs[0];
break;
case '/total':
$scope.selectedTab = $scope.tabs[1];
break;
case '/clienti':
$scope.selectedTab = $scope.tabs[2];
break;
case '/centralizator':
$scope.selectedTab = $scope.tabs[3];
break;
case '/optiuni':
$scope.selectedTab = $scope.tabs[4];
break;
default:
$scope.selectedTab = $scope.tabs[0];
break;
}

This aught to be able to do what you want:
https://github.com/angular-ui/ui-router

Related

AngularJs ui set the home page active

I am working of AngularJs v 1 app with ui routing.
My question simply how to set the home page active without clicking the ui-sref link.
I tried with ng-class="active" but it doesn't achieve the task.
<script>
angular.module("myApp",['ui.router'])
.config(function ($stateProvider,$urlRouterProvider,$locationProvider) {
$stateProvider
.state("home",{
url:"home",
views:{
'main':{templateUrl:"home.html"}
}
});
$locationProvider.hashPrefix('');
$urlRouterProvider.otherwise("/home");
</script>
<div class="container" style="margin-top: 60px">
<div ui-view="main"> </div>
</div>
Home page
<div class="row" style=" margin-top:100px; " ng-app="app" ng-class="active">
<h1>Home</h1>
</div>
What you are looking for is ui-sref-active
From the doc
A directive working alongside ui-sref to add classes to an element when the related ui-sref directive's state is active, and removing them when it is inactive. The primary use-case is to simplify the special appearance of navigation menus relying on ui-sref, by having the "active" state's menu button appear different, distinguishing it from the inactive menu items.
It will add the active for you if you're currently on the right state.
Markup should look something along the line of
<div class="some-navigation-class">
<a ui-sref="home" ui-sref-active="active">Home</a>
<!-- more nav goes here -->
</div>

Lazyload to multiple views in ui-router

A few months ago I've be created the topic: Try to render two templates in same View (Ui-Router), where I asked about how to render multiple views in the same page. My objective with this was created a web app as an desktop one, with views to minimize, maximize, close and stuff like that.
Well, my app is ready but I'm getting a problem, when I up my app to the production some computers are taking a long time to render all the Views. In the image bellow we can se a lot of requisitions that server take to return my templatesURL's.
There is a way to avoid this ? I was looking for an lazy load to templateURL but I Didn't find any. :(
This plunkr was the approach what I used. I have only one state for all my Views (My current app.config has 103 Views):
routerApp.config(function($stateProvider) {
$stateProvider.state('mainState', {
views: {
'CompanyView': {
templateUrl: 'Company.html'
},
'PeopleView': {
templateUrl: 'People.html'
},
.....
....
}
})
});
Introduction
The way you approached the solution is the cause of the problem you're facing, because you have too many views for a single state, it'll end up having to load all of them in order to set that state, so every time you access your state, ui-router has to load every template in order to set the views. It might not cause problem for a few number of templates, but, for larger numbers like yours it is definitely an issue.
Ng-Templates
You can try to cache your templates in your page using <script type="text/ng-template"... in order to prevent the loading time, it's a good practice by the way. Usually it's part of the production build optimization, load all templates in the template cache, so that the application load time decreases significantly provided that you don't have to wait for an http call to load a page. It will indeed increase the performance in your case, but I don't have a benchmark that ensure if it'd be enough for your scenario.
Component Based Solution
Anyhow, you can always implement interface components to behave the way you want, optimized in such a way that it doesn't have to load one hundred templates to show a single panel for the user.
My suggestion is, instead of using ui-router, use a component based solution, create a directive component to hold the panel content of each window and its behavior; and use a controller to manage the state of opened and closed panels, holding and managing each opened panel in a list and so on. For example:
<nav>
<button ng-click="openPanel({title: 'My Panel Title', templateUrl: 'myPanel.html'>">
Open myPanel
</button>
<nav>
<main>
<panel ng-repeat="panel in openedPanels"></panel>
</main>
The following snippet implements this approach using bootstrap 4 css, each panel is a bootstrap card, and it has a list of panels it can open and on click of a nav list it adds the respective panel to the opened panels list where angularjs can render it on the html using ng-repeat. This way, only the opened window will be rendered, therefore, only the opened window template will be loaded.
Disclaimer: This is a very simple example implemented not using the best practices available out there. If you intend to use this approach you should implement it based on your application to fit better the needs of your architecture, this one is not a complete functional component, it's just an example for the sake of the demonstration.
angular.module('app', [])
.controller('PanelsCtrl', function($scope) {
// available windows to be opened
$scope.panels = [
{ title: 'Window 1', templateUrl: 'window1.html' },
{ title: 'Window 2', templateUrl: 'window2.html' }];
// all currently opened panels
$scope.openedPanels = [];
// opens a panel (a.k.a, adds a panel
// to the opened panels list)
$scope.openPanel = function(panel) {
if ($scope.openedPanels.indexOf(panel) === -1)
$scope.openedPanels.push(panel);
};
// close a panel (a.k.a, removes a panel
// from the opened panels list)
$scope.onClosePanel = function(panel) {
$scope.openedPanels.splice($scope.openedPanels.indexOf(panel), 1);
};
})
.directive('window', function($templateRequest, $templateCache, $compile) {
return {
restrict: 'E',
scope: {
panel: '=',
onClosePanel: '&'
},
template: `
<div class="card">
<h4 class="card-header">
<span>{{ panel.title }}</span>
<button
ng-click="onClosePanel(panel)"
type="button"
class="close"
data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</h4>
<div class="card-body">
<ng-include src="panel.templateUrl"></ng-include>
</div>
</div>
`
}
})
// example controlelr to be used with ng-controller
.controller('Window1Ctrl', function($scope) {
$scope.window1Prop = 'This is a property from Window1Ctrl'
})
#import 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css'
<div ng-app="app">
<div class="container" ng-controller="PanelsCtrl">
<div class="row">
<div class="col-sm-3">
<ul class="nav flex-column">
<li class="nav-item" ng-repeat="panel in panels">
<a class="nav-link active" href="#" ng-click="openPanel(panel)">
{{ panel.title }}
</a>
</li>
</ul>
</div>
<div class="col-sm-9">
<window ng-repeat="panel in openedPanels" panel="panel" on-close-panel="onClosePanel(panel)">
</window>
</div>
</div>
</div>
<!-- NG-TEMPLATES -->
<script type="text/ng-template" id="window1.html">
<div ng-controller="Window1Ctrl">
<b>{{panel.title}}</b>
<h5>window1Prop: {{ window1Prop }}</p>
</div>
</script>
<script type="text/ng-template" id="window2.html">
<em>{{panel.title}}</em>
</script>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.js"></script>

AngularJS: Load page on link click

I got 2 divs, first has links pointing to individual pages and another one to display the content of pages the links are pointing to. Here is how it looks:
<div id="navigation">
<a href="http://mydomain/page1">
<a href="http://mydomain/page2">
<a href="http://mydomain/and-so-on">
</div>
<div id="content">
<!-- display content here -->
</div>
Is there a way to prevent redirecting the page on link click and display the content of the URL they point to? I'm doing it this way for SEO purposes so each individual pages can also be crawlable on their own.
I've heard of ng-include but I want to be sure I'm heading the right direction so I reckon I should ask first before going with it.
Thank you in advance.
Use AngularJS Routing for this purpose.
<a href="#page1">
<a href="#page2">
<a href="#and-so-on">
<div ng-view></div>
<script>
var app = angular.module("myApp", ["ngRoute"]);
app.config(function($routeProvider) {
$routeProvider
.when("page1", {
templateUrl : "http://yourDomain/page1.html"
})
.when("/page2", {
templateUrl : "http://yourDomain/page2.html"
})
.when("/and-so-on", {
templateUrl : "http://yourDomain/and-so-on.html"
});
});
</script>
Note: Must include angular-route.js

AngularJS with Bootstrap: Suppress redirect when clicking a modal

I have a "Contact" link in my Navbar set to open a form as a modal when clicked. This is using Bootstrap. Recently I decided to add some AngularJS routing to my site and it works great for all links except this one. My route config looks like this:
asApp.config(function($routeProvider) {
$routeProvider
// route for the home page
.when('/', {
templateUrl : 'pages/home.html',
controller : 'mainController'
})
// route for the about page
.when('/about', {
templateUrl : 'pages/about.html',
controller : 'aboutController'
})
// route otherwise
.otherwise({
templateUrl : 'pages/home.html',
controller : 'mainController'
});
});
Since I don't have a dedicated page for the contact form (it's meant to open as a modal), I haven't added any specific routing for "/contact" in the above code which is why clicking that link triggers the "route otherwise" block and redirects opens up the home page. Is there any way to suppress this routing only for the "Contact" link? I want it to stay on the page that was open when the link was clicked and the modal opened. Not sure if I have been coherent enough so please feel free to ask me if you are lost.
In case it helps, here's the modal snippet along with some other relevant HTML parts:
/* Navbar snippet */
<div class="collapse navbar-collapse" ng-controller="HeaderController">
<ul class="nav navbar-nav navbar-right">
<li ng-class="{ active: isActive('/about')}">About</li>
<li ng-class="{ active: isActive('/blog')}">Blog</li>
<li>Contact</li>
</ul>
</div>
/* Main body snippet */
<div id="main"><div ng-view></div></div>
/* Modal snippet */
<div class="modal fade" id="contact" role="dialog">
<div class="modal-dialog">
/* Contact form code here... */
</div>
</div>
Thanks a ton for your attention and help.
You could try data-target to not mess with the angular routes (url after #)
<a href data-target="#contact" data-toggle="modal">Contact</a>
There is a great module ui.bootstrap by angularUI team for using angular with bootstrap:
https://angular-ui.github.io/bootstrap/
It has a $modal service to handle opening views as a modal.
However if you don't want to use that, you can try the following:
The most simple solution will be not to have the contact as a anchor tag.
Use a div with ng-click instead.
i.e., in place of <li>Contact</li>
use <li><div class="contact-modal" ng-click="functionThatOpensModal()">Contact</div></li>
And you can style this specific div to look same as the other links.
Hope this helps

multiple nested views with their own router

I am building an angular app with nested views using ui-router. The app has a list of posts, each with its own Nav menu to change the post's view.
To simplify the context, lets just say that each nav menu can change the theme of the post it is attached to.
POST 1 CONTENT
[Light theme button] [Dark theme button]
--------------------------------------------
POST 2 CONTENT
[Light theme button] [Dark theme button]
--------------------------------------------
*
*
*
The problem I am facing is when I press a button on one post's nav menu, all the posts change.
I want the nav menu for each post only to effect its respective post.
I am a total beginner to angular and my questions are:
Is this a problem i would want to solve with ui-router? (do i want to
have a router for each post?)
If it is, how do i solve it?
If not, can you point me in the right direction?
any help is appreciated, whether it is a link, explanation, code or just a comment.
PS. in reality i am not just changing the theme, i am changing the content on the post completely. In the future i want to implement more than just 2 options menu options.
here is the code that i am using now (modified a bit to remove redundancy):
Main.js:
angular.module('app',['ui.router'])
.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/postlist');
$stateProvider
.state('postlist', {
url: '/postlist',
templateUrl: 'ListView.html',
controller: ListCtrl
})
.state('postlist.light', {
templateUrl: 'pollViewLight.html',
controller: PollLightCtrl
})
.state('postlist.dark', {
templateUrl: 'pollViewDark.html',
controller: PollDarkCtrl
})
});
simplified part of index.html:
<nav>
<ul>
<li><a ui-sref="postlist">Post List</a></li>
</ul>
</nav>
<div class="container">
<div ui-view></div>
</div>
ListView.html:
<div class="row" ng-repeat="post in posts">
<div class="col">
<div ui-view></div>
<ul class="nav nav-pills">
<li><a ui-sref=".light">Light Theme</a></li>
<li><a ui-sref=".dark">Dark Theme</a></li>
</ul>
</div>
</div>

Resources