Implementing angularJs factory or service for bootstrap tabs - angularjs

how to write a service or factory for bootstrap tabs using angularJs.
I placed in one controller but need a common function(repeated code) for different controllers.
<ul class="nav nav-tabs">
<li data-ng-class="{active:tab===0}">
<a href ng-click="changeTab(0)"> <i class="fa fa-list"></i></a>
</li>
<li data-ng-class="{active:tab===1}">
<a href ng-click="changeTab(1)"><i class="fa fa-user"></i></a>
</li>
</ul>
<div data-ng-class="{activeTab:tab===0}" ng-show="isActiveTab(0)">
tab1
</div>
<div data-ng-class="{activeTab:tab===1}" ng-show="isActiveTab(1)">
tab2
</div>
controller
$scope.tab = 0;
$scope.changeTab = function(newTab){
$scope.tab = newTab;
};
$scope.isActiveTab = function(tab){
return $scope.tab === tab;
};

Managing tabs is a view concern. Rather than implementing a factory, I recommend creating two directives: tabContainer and tab. The tab directive registers itself with the parent tabContainer using the require attribute to access the parent directive's controller API.
Demo
Usage
<tab-container selected="tab2">
<tab name="tab1">tab1</tab>
<tab name="tab2">tab2</tab>
</tab-container>
Parent Directive
The parent directive publishes the following controller API that the child tab directives will access:
tabContainer controller
// registers this tab with the parent tabContainer
this.register = function(element) {
$scope.tabs.push(element);
}
// returns the selected tab object whose
// name property indicates which tab is active
this.getSelected = function() {
return $scope.selectedTab;
}
Child Directive
The tab directive is able to access the parent controller by requiring the parent directive in its directive definition, and accessing the parent directive's controller as the 4th argument to the tab directive's link function:
tab directive definition
scope: true,
require: '^tabContainer',
link: function(scope, element, attr, tabContainer) {
// set the tab so that it is visible in the tab directive's scope.
scope.tab = { name: attr.name, element:element};
scope.selectedTab = tabContainer.getSelected();
tabContainer.register(scope.tab);
}
The scope is set to true so that each tab will create its own child scope and not interfere with the scope of other tabs.
Template Files
For example purposes, the directive templates are embedded in the HTML:
<script type="text/ng-template" id="tabContainer.html">
<ul class="nav nav-tabs">
<li ng-repeat="tab in tabs" data-ng-class="{active:selectedTab.name===tab.name}">
<a href ng-click="changeTab(tab)"> <i class="fa fa-list">{{tab.name}}</i></a>
</li>
</ul>
<ng-transclude></ng-transclude>
</script>
<script type="text/ng-template" id="tab.html">
<div data-ng-class="{activeTab:selectedTab.name===tab.name}" ng-show="selectedTab.name === tab.name">
<ng-transclude></ng-transclude>
</div>
</script>
It is recommended to move these to dedicated HTML files.
Changing the Active Tab
The user is able to change the active tab by clicking the tab link. This is achieved by publishing a $scope function in the parent controller:
$scope.changeTab = function(tab) {
$scope.selectedTab.name = tab.name;
}
Creating a Tabs Module
The beauty of AngularJS and its pluggable modular architecture is that you can extend the AngularJS directive ecosystem, and have the directives work together seamlessly. For example, you could encapsulate the above tabs directive into a tabs module, and even use the ngRepeat directive to bind the tabs.
Demo
Controller
var app = angular.module('app',['tabs']);
app.controller('ctrl', function($scope) {
$scope.tabData = [
{ name: 'tab1', body: 'You selected tab1!'},
{ name: 'tab2', body: 'You selected tab2!'},
{ name: 'tab3', body: 'You selected tab3!'},
{ name: 'tab4', body: 'You selected tab4!'},
];
});
View
<div class="container" ng-controller="ctrl">
<tab-container selected="tab1">
<tab ng-repeat="tab in tabData" name="{{tab.name}}">{{ tab.body }} </tab>
</tab-container>
</div>

Hi I write it with out using any service or factory
see the example
<ul ng-init="tab = 1">
<li ng-class="{active:tab===1}">
<a href ng-click="tab = 1">Tab1</a>
</li>
<li ng-class="{active:tab===2}">
<a href ng-click="tab = 2">Tab2</a>
</li>
<li ng-class="{active:tab===3}">
<a href ng-click="tab = 3">Tab3</a>
</li>
<br><br>
<p ng-show="tab === 1"> Tab1 content </p>
<p ng-show="tab === 2"> Tab2 content</p>
<p ng-show="tab === 3"> Tab3 content</p>
</ul>
Dynamically change it through controller see the working example here

The structure for controllers/services in Angular are explained well here:
https://github.com/johnpapa/angular-styleguide
In app.js, we declare an angular application, give it a name, and any dependencies (ng-route / ng-grid). $http calls should be made using a factory or a service, and the controller should call the service to fetch or post data. From the angular documentation, "services are substitutable objects that are wired together using dependency injection (DI). You can use services to organize and share code across your app."
https://docs.angularjs.org/guide/services
app.js:
var app = angular.module('appname', []);
app.factory('httpResponseErrorInterceptor'...
app.config(['$httpProvider',...
Controller:
angular.module('appname').controller("NameCtrl", ["$scope", "$log", "$window", "$http", "$timeout", "SomeService",
function ($scope, $log, $window, $http, $timeout, TabService) {
//your controller code
$scope.tab = 0;
$scope.changeTab = function(newTab){
$scope.tab = newTab;
};
$scope.isActiveTab = function(tab){
return $scope.tab === tab;
};
}
]);
Service:
angular.module('appname').service("TabService", function () {
//Your code for shared functionality regarding tab service
var currentTab = {};
return {
currentTab : currentTab
}
});

Related

Angularjs : ui-sref state change in url not showing submenu

In angularjs to show/hide dynamic submenu, I am adding and removing dynamic classes in js file. Every time when the state changes in url (i.e ui-sref={{mymenu.url}}) sub menu is not visible. If there is no state change sub menu is working properly. Can anyone please suggest.
Html
<li ng-repeat='mymenu in menuItems' ng-click="showHideMenu($event);" >
<a class="iconsize" ui-sref={{mymenu.url}}>
<i class={{mymenu.image}}></i> <strong>{{mymenu.menuName}}</strong>
<span class="fa fa-chevron-down"></span>
</a>
<ul class="submenuHide">
<li class="" ng-repeat='submenu in mymenu.submenu'>
<a>{{submenu.submenuName}}</a>
</li>
</ul>
JS
$scope.showHideMenu = function(event) {
var classname = event.target.parentElement.parentElement.children[1].className;
if(classname == 'submenuHide'){
$(event.target.parentElement.parentElement.children[1]).removeClass('submenuHide');
$(event.target.parentElement.parentElement.children[1]).addClass('submenuShow');
}else if(classname == 'submenuShow'){
$(event.target.parentElement.parentElement.children[1]).removeClass('submenuShow');
$(event.target.parentElement.parentElement.children[1]).addClass('submenuHide');
}
}
A couple things. One, you'll need to make sure your menu is outside of the individual templates you're working with. Two, using ng-class bound to an ng-model ensures that your menu state is included in the digest cycle. This will save you the jqLite and dom parsing logic.
Here's an example plunker.
So your code might look like this:
<body ng-controller="MainCtrl">
<a ui-sref="hello">Hello</a>
<a ui-sref="about">About</a>
<button ng-click="toggleMenu()">Show / Hide Menu</button>
<ui-view></ui-view>
<ul ng-class="{showMenu: show, hideMenu: !show}">
<li ng-repeat="letter in ['a','b','c','d']">{{letter}}</li>
</ul>
</body>
With this JS:
app.controller('MainCtrl', function($scope) {
$scope.show = false;
$scope.toggleMenu = function() {
$scope.show = !$scope.show;
};
});

AngularJS page navigation

What approach do you recommend to navigate pages from nav menu using angularjs?
Pages will have footer and header html using ng-include to have common templates. I found ng-view to navigate between pages.
In this example, there's a module named navList and a controller and a method navClass inside it. Using a special service in angular.js, $location which parses the URL in the browser and makes the URL available in the application. Finally, just match the URL hash with the parameter passed.
var navList = angular.module('navList', []);
navList.controller('navCtrl', ['$scope', '$location', function ($scope, $location) {
$scope.navClass = function (page) {
var currentRoute = $location.path().substring(1) || 'home';
return page === currentRoute ? 'active' : '';
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="well sidebar-nav" ng-app="navList">
<ul class="nav nav-list" ng-controller="navCtrl">
<li ng-class="navClass('home')"><a href='#/home'>Home</a></li>
<li ng-class="navClass('about')"><a href='#/about'>About Us</a></li>
<li ng-class="navClass('contact')"><a href='#/contact'>Contact Us</a></li>
</ul>
</div>
JsFiddle Link
Angular router is what you need:
https://github.com/angular-ui/ui-router/blob/master/README.md

Angularjs active state not applying on clicked button

I have this very simple app with a few buttons. When the button is clicked the active css should be applied. It is not working for me for some reason,
controller:
'use strict';
angular
.module('myApp')
.controller('summaryCtrl', ['$scope', function ($scope){
$scope.buttons=[
{time:'1 Hour'},
{time:'2 Hours'},
{time:'24 Hours'},
{time:'48 Hours'},
{time:'1 Week'},
{time:'1 Month'}
];
$scope.selected = $scope.buttons[0];
$scope.select= function(item) {
$scope.selected = item;
};
$scope.isActive = function(item) {
return $scope.selected === item;
};
}]);
navigation:
<div ng-controller="summaryCtrl">
<ul>
<li ng-repeat="button in buttons" id="{{ button.time }}" ng-click="selectButton($index)" ng-class="{active: $index===selectedButton}">{{ button.time }}</li>
</ul>
</div>
Plunker here: http://plnkr.co/edit/0WxnRVwaHwCBGM6wXNtS?p=preview
Many thanks for your help.
You call the selectButton method in your ng-click directive but this method doesn't exist in your $scope. You named it select(). Also, the property selectedButton you use in your ng-class directive is not the one containing the selected button. It's simply selected like you assign it in your $scope.select() method.
Try like this:
<div ng-controller="summaryCtrl">
<ul>
<li ng-repeat="button in buttons" id="{{ button.time }}" ng-click="select($index)" ng-class="{active: $index === selected}">{{ button.time }}</li>
</ul>
</div>

Display list of items and edit them via AngularJS

I try to solve a classic problem using AngularJS: I need to display list of some entities and provide ability to add, edit and view details of this entities.
I implement two controllers: ListController to iterate list of entities and ItemController to display and save entity details. This is html code:
<div ng-app="myApp">
<a class="btn" data-toggle="modal" data-target="#modal">Add new item</a>
<div ng-controller="ListController">
<h4>List</h4>
<ul>
<li ng-repeat="item in list">
{{item.name}}
<a class="btn" data-toggle="modal" data-target="#modal" ng-click="editItem(item)">Edit item</a>
</li>
</ul>
</div>
<div id="modal" role="dialog" class="modal hide fade">
<div ng-controller="ItemController">
<div class="modal-header">
Item Dialog
</div>
<div class="modal-body">
<label for="txtName" />
<input type="text" id="txtName" ng-model="item.name" />
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="saveItem()" data-dismiss="modal">OK</button>
</div>
</div>
</div>
and controllers code:
var db_list = [{ name: "Test1" }, { name: "Test2" }];
var app = angular.module('myApp', []).
controller('ListController', function($scope, $rootScope) {
$scope.list = db_list;
$scope.editItem = function(item) {
$rootScope.item = item;
}
}).
controller('ItemController', function($scope, $rootScope) {
$scope.saveItem = function() {
db_list.push($rootScope.item);
$rootScope.item = null;
}
});
Also you can find the working ptototype at http://jsfiddle.net/yoyoseek/9Qntw/16/.
The general problem in this code that I store entity to display its description using scope of the ListController (via editItem()), but I need this stored entity details in the ItemController. I use $rootScope for sharing entity to edit and it looks like hack for me. Is it a normal practice?
This code has one more drawback: $rootScope.item have to been cleared on modal dialog hide.
It looks like the main problem here is that events triggered by data-toggle happen outside of your control and it's not part of the AngularJS bindings (I am new to it so I may be wrong).
Anyway, it seems like there is no way to cross-reference controllers in Angular, and the only way to get hold of them is via inspecting the DOM. But, once you get into that, you may as well initialize the scope directly (http://jsfiddle.net/B4kAW/4/):
var db_list = [{ name: "Test1" }, { name: "Test2" }];
var app = angular.module('myApp', []);
app.controller('ListController', function($scope) {
$scope.list = db_list;
$scope.editItem = function(item) {
angular.element(document.getElementById("modal")).scope().item = item;
};
});
app.controller('ItemController', function($scope) {
$scope.saveItem = function(item) {
//db_list.push(item);
//$rootScope.item = null;
};
});
Note:
The modal dialog here has no way of knowing whether it's opened for editing, or adding a new item (I commented out push).
Since the dialog is linked with "main" item in the list, it updates it instantly (can be seen while the dialog is open, on the background). You may need to copy it instead of using a reference.
Inspired by this answer. It looks like "the Angular way" around dialogs is to convert them into services.

nest ng-view inside a form

given the controller
function ctl($scope, $http) {
$scope.postForm = function() {
console.log("submitting form")
}
}
and the view
<form name="pform" ng-show="!error.Show">
<div ng-view></div>
<button type='button' value='Save' ng-click="postForm()" />
</form>
The controller method postForm doesn't get called, however, if i move the form tag into the view the method is called. Is there a reason that this doesn't work as I expect it to? Is there another way to accomplish the goal of sharing the form controls across different views?
Update
my module and routeProvider are configured like this:
angular.module("profilemodule", [])
.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when("/info", { templateUrl: '/partials/profile/info.html', controller: ProfileController })
.when("/user", { templateUrl: '/partials/profile/user.html', controller: ProfileController })
.when("/introduction", { templateUrl: '/partials/profile/editor.html', controller: ProfileController })
.otherwise({ redirectTo: '/info' });
}]).run(function ($rootScope, $location) {
$rootScope.location = $location;
})
and the page includes some nav elements which are set based on the location service like so:
<div class="row">
<div class="offset2 span10">
<ul class="nav nav-pills">
<li ng-class="{active: location.$$path.substring(0, '/info'.length) == '/info'}"><a href="#/info" >Information</a></li>
<li ng-class="{active: location.$$path.substring(0, '/user'.length) == '/user'}"><a href="#/user" >User</a></li>
<li ng-class="{active: location.$$path.substring(0, '/intro'.length) == '/intro'}"><a href="#/intro" >Introduction</a></li>
</ul>
</div>
</div>
<form name="pform" method="POST" ng-show="!error.Show">
<div ng-view></div>
<button type='button' value='Save' ng-click="postForm()" />
</form>
the ng-class statements works perfectly, is it because I've set the location property of $scope in the module's run method?
thanks,
jason
ng-view with routing creates a new scope with the controller, and you can't reach a child scope. Your submit action lies in the parent scope and the form data lies in the child scope (created by ng-view).
If you want to use common form controls, you can use ng-include, this directive gets template it and renders that in the current scope.
Move your form controls to a new template, then include them in all of your forms.
API reference:
http://docs.angularjs.org/api/ng.directive:ngInclude

Resources