Angular UI navbar collapse not working - javascript

I can't seem to get my navbar to start as collapsed with the below code. I'm using Angular-ui-bootstrap:
navbar.directive.html:
<button type="button" ng-click="isCollapsed = !isCollapsed">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<div collapse="isCollapsed">
<ul class="nav navbar-nav">
<li>Test</li>
<li>Testing...</li>
</ul>
</div>
navbar.controller.js:
angular.module('gameApp')
.controller('NavbarController', NavbarController);
NavbarController.$inject = ['playersService'];
function NavbarController(playersService) {
var vm = this;
vm.isCollapsed = true;
var getCurrentPlayer = function() {
playersService.getCurrentPlayer().$promise.then(function(data) {
vm.player = data.player;
});
};
var init = function() {
getCurrentPlayer();
};
init();
}
When the page is minimized to the point where the responsive navbar toggle appears the menu is already showing. Even if I change it to vm.isCollapsed = false; the menu still starts as open.

Controller is built using this to allow controllerAs syntax but variables in html are set up to use $scope.
If directive does not have isolated scope you need to declare the alias for controller somewhere and prefix all the variables with the alias.
--OR--
Need to change controller to bind all the variables to $scope not this
Not sure how directive is configured or where controller is being declared in the view

Related

AngularJS 1.5 - Iterate and set child components' property

I have a parent component that creates "n" child components within an ng-repeat. Each child component has an accordion element (from ui-bootstrap directives) in its template. From the parent component I would like to collapse or expand all accordions using a link in the parent component level. Each child accordion can be expanded/collapsed individually setting the local vm.isAccordionExpanded variable.
I am planning to use $scope.$broadcast() form the parent to notify the children, each of them will intercept the events with $scope.$on() and set a local boolean variable vm.isAccordionExpanded to open/close the accordion respectively.
Parent component template:
<span id="accordionListCommands" ng-if="vm.pastVisits.totalResults > 0">
<span id="collapseAllAccordion">
<a ng-click="vm.collapseAll()" href="">
<i class="fa fa-minus-square" aria-hidden="true"></i></a>
</span>
<span id="expandAllAccordion">
<a ng-click="vm.expandAll()" href="">
<i class="fa fa-plus-square" aria-hidden="true"></i></a>
</span>
</span>
<div ng-repeat="visitItem in vm.pastVisits.data">
<visits-list-component visit="visitItem"></visits-list-component>
</div>
Parent component js file:
$scope.$on('collapse-all-accordion', function () {
vm.isAccordionExpanded = false;
});
$scope.$on('expand-all-accordion', function () {
vm.isAccordionExpanded = true;
});
Child template:
<uib-accordion close-others="false">
<div uib-accordion-group is-open="vm.isAccordionExpanded">
//Rest of the template
Is there a better or more performant way to achieve this?
The way you are doing this is not right and its not the angular way to write it.
Instead use one way data binding or two way data binging:
bindings: {
visit: '<' // or ('=') respectivly
}
and then implement your collapseAll function like follows:
angular.forEach( vm.pastVisits.data,function(visitItem) {
visitItem.isAccordionExpanded = false;
});
then in the visit component you can write:
vm.$onChanges = function() {
if(changes.visit) {
vm.isAccordionExpanded = changes.visit.currentValue.isAccordionExpanded;
}
}
or even better without having to put $onChanges listener :
<uib-accordion close-others="false">
<div uib-accordion-group is-open="vm.visit.isAccordionExpanded">

How do I refresh a specific controller in Angular.js (in app.js)

What is the best way to refresh a specific controller in Angular.js, I can detect page changes like this:
//when app runs call this function.
app.run(function($rootScope, $route, $location, $cookies){
//Bind the `$locationChangeSuccess` event on the rootScope, so that we dont need to
//bind in induvidual controllers.
$rootScope.$on('$locationChangeSuccess', function() {
$rootScope.actualLocation = $location.path();
});
$rootScope.$watch(function () {return $location.path()}, function (newLocation, oldLocation) {
if($rootScope.actualLocation === newLocation) {
console.log('user hit back');
if($rootScope.actualLocation=='/'){
//clear cookie
var testing = $cookies.getObject('globals');
$location.path('/');
}
}
});
});
I need to refresh my navigationController, so if the user presses the back button or refreshes their browser window it dynamically keeps its state.
Navigation controller:
nav.js
'use strict';
//app global variable
//this is the controller that handles post requests
//declare services as dependecies $http, $location, custom service apiServiceWeb
app.controller('navigationController', function($scope, $route, $rootScope, $http, $location, $cookies, setCredentials) {
//get what is in the cookie
var cookieValue = setCredentials.getCookie('globals');
console.log($cookies.get('globals'));
//hide menu in certain paths
if ($location.path() == '/' || $location.path() == 'signup') {
$scope.isConnected = true;
} else {
$rootScope.$watch('globals', function() {
//if a currentUser does NOT exist, the hide menu.
if (cookieValue = null) {
$scope.isConnected = true;
} else {
$scope.isConnected = false;
}
});
}
});
index.html
This is the view.
<div ng-controller="navigationController">
<!-- Navigation -->
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation" ng-hide="isConnected">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Start Bootstrap</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li>
About
</li>
<li>
Services
</li>
<li>
Contact
</li>
</ul>
</div>
<!-- /.navbar-collapse -->
</div>
<!-- /.container -->
</nav>
</div>
<div ng-view=""></div>

Angular1.3: Access functions from standard controllers, inside a view which uses controllerAs?

I have a simple controller defined in my main app.js file, which controls opening/closing of my navbar and is visible on all other views of my app:
app.js:
.controller('mainController', ['$scope', function($scope){
$scope.menuActive = false;
$scope.toggleMenu = function(){
$scope.menuActive = !$scope.menuActive;
}
}]);
index.html:
<nav class="side-menu" ng-class="{ 'side-menu-open' : menuActive }">
<ul>
<li>LINK1</li>
<li>LINK2</li>
</ul>
</nav>
<!--Other views::.....-->
<div ui-view></div>
All my other views(which use controllerAs), have a button with an ng-click which I am using to access the above $scope.toggleMenu() function and ng-class, but this does not work, and I don't get any errors either:
view1.html :
<span class="btn btn-default"
ng-click="toggleMenu()">
MENU
</span>
View1.js:
angular
.module('myApp.view1', [])
.controller('View1Ctrl', [
function(){
................
}
]);
Also, the reason I have done it this way again is because my navbar is persistent throughout my app. Does this go against best practices by any chance?
If you are using the .. controller as .. syntax, make sure that you are using it for all controllers. Don't be selective about it.
Next, when using the syntax, you need not inject the $scope object. You need to instead use the this variable and attach any properties or functions that you would normally associate with the $scope object with the this object instead.
Thus,
$scope.toggleMenu = function(){
$scope.menuActive = !$scope.menuActive;
}
becomes
this.toggleMenu = function(){
this.menuActive = !this.menuActive;
}
Finally in your view, be sure to associate each expression with a controller.
<div ng-controller="mainController as main">
<nav class="side-menu" ng-class="{ 'side-menu-open' : main.menuActive }">
<ul>
<li>LINK1</li>
<li>LINK2</li>
</ul>
</nav>
<div ui-view>
<!-- Assuming that the following gets compiled to ui-view -->
<span class="btn btn-default" ng-click="main.toggleMenu()">
MENU
</span>
</div>
</div>
You can get some further hints on using the controller as syntax here

Why can't I access the new DOM element created by Angular?

HTML:
<div class="list-group link-list" ng-show="linksForPerson">
<a href="" class="list-group-item" ng-repeat="link in linksForPerson" ng-click="showLinkDetail(link)" ng-class="{active: isSelectedLink(link)}">
<h4 class="list-group-item-heading">[[ link.engine.name ]]</h4>
<p class="list-group-item-text">[[ link.engine.base_url ]]</p>
<p class="list-group-item-text" ng-show="link.user_sync_id">[[ link.user_sync_id ]]</p>
<p class="list-group-item-text" ng-show="link.group_sync_id">[[ link.group_sync_id ]]</p>
</a>
<span class="glyphicon glyphicon-plus"></span> Add a new link
</div>
Controller:
appModuleLightDashboard.controller('ManageLinksController',
function($scope, $http, $timeout) {
$scope.addLink = function(event) {
$scope.linksForPerson.push({});
// Error: [$rootScope:inprog] http://errors.angularjs.org/1.3.0-rc.1/$rootScope/inprog?p0=%24apply
$('.link-list .list-group-item').eq(-2).trigger('click');
// But this works ---- why?
// $timeout( function(){$('.link-list .list-group-item').eq(-2).trigger('click')} , 0);
}
});
I have changed the interpolate symbol to [[]] as it conflicts with Django
The problem:
A new list item will be created when the user clicks on the "Add a new link". I wanted to select this new list item automatically.
But it looks like I couldn't select that new DOM element created by Angular ( i.e. $('.link-list .list-group-item') doesn't return the new one ), unless I wrap the code with $timeout. Anyone knows why?
Also, please advise if there is a more Angular way to achieve it:)
Your question is "why". The answer is because at the moment you are trying to use jQuery to find the element, it hasn't yet been added to the DOM. That doesn't happen until the digest cycle runs.
$timeout works because the function call is now deferred until after the next digest cycle. The problem with that solution is that there are cases where the DOM still won't yet have been modified.
Looking in more detail, this will have several failure modes. The error you are showing is sent because you are actually triggering a click in the second to last element already added, and you are doing it from inside of a digest cycle. If you already have two or more items added to the collection, this triggers angular's ng-click on the second to last one (which happens to not be the one you think), which assumes it is called outside of a digest cycle and calls $apply, which fails with the error you see because it's actually inside of a digest cycle.
The "angular way" to achieve what you want is to use a directive.
.directive('triggerClick', function($parse) {
return {
restrict: 'A',
link: function(scope, elem, attr) {
var fn = $parse(attr['triggerClick']);
if(scope.$last) { //or some other logic
fn(scope);
}
}
}
})
div class="list-group link-list" ng-show="linksForPerson">
<a href="" class="list-group-item" ng-repeat="link in linksForPerson" ng-click="showLinkDetail(link)" ng-class="{active: isSelectedLink(link)}" trigger-click="showLinkDetail(link)">
<h4 class="list-group-item-heading">[[ link.engine.name ]]</h4>
<p class="list-group-item-text">[[ link.engine.base_url ]]</p>
<p class="list-group-item-text" ng-show="link.user_sync_id">[[ link.user_sync_id ]]</p>
<p class="list-group-item-text" ng-show="link.group_sync_id">[[ link.group_sync_id ]]</p>
</a>
<span class="glyphicon glyphicon-plus"></span> Add a new link
</div>
This works because the link function of the directive will be called after the node has been constructed and added to the DOM. Note the addition of "trigger-click" to your ng-repeat element.
elem in the directive is a jQuery object wrapped around the instance of the ng-repeat item. Angular will call the link function for every instance of the directive, which in this case is every instance of the ng-repeat.
Even more "angular" would be to not use a click event at all. You don't include the implementation of showLinkDetail, but rather than trigger a click, just call it in your controller.
As a general "angular" rule, anything that looks like jQuery should only happen in a directive.
EDIT: With more info on what you need, you can do this without need to do any DOM manipulation at all (no directives).
appModuleLightDashboard.controller('ManageLinksController',
function($scope, $http, $timeout) {
$scope.activeLink = undefined;
$scope.addLink = function(event) {
$scope.activeLink = {};
$scope.linksForPerson.push($scope.activeLink);
}
$scope.showLinkDetail = function(link){
$scope.activeLink = link
}
$scope.isSelectedLink = function(link){
return $scope.activeLink === link;
}
});
<div class="list-group link-list" ng-show="linksForPerson">
<a href="" class="list-group-item" ng-repeat="link in linksForPerson" ng-click="showLinkDetail(link)" ng-class="{active: isSelectedLink(link)}">
<h4 class="list-group-item-heading">[[ link.engine.name ]]</h4>
<p class="list-group-item-text">[[ link.engine.base_url ]]</p>
<p class="list-group-item-text" ng-show="link.user_sync_id">[[ link.user_sync_id ]]</p>
<p class="list-group-item-text" ng-show="link.group_sync_id">[[ link.group_sync_id ]]</p>
</a>
<span class="glyphicon glyphicon-plus"></span> Add a new link
</div>
you should not put your "add new link" inside the div with ngShow because when the linksForPerson array is empty, you will not be able to add a new link . Also, putting it outside the div will ease up every other manipulation (based on what you want to achieve"
linksForPerson is an array, use ng-show="linksForPerson.length" instead
you should initialize your arrays before pushing anything into it $scope.linksForPerson=[]
use of ng-bind is a better alternative to {{}} or [[]]
I refactored your code.
// ---- controller
appModuleLightDashboard.controller('ManageLinksController',
function($scope, $http, $timeout) {
var activeLink;
// you should initiate your array
$scope.linksForPerson = [];
$scope.isSelectedLink = function (link) {
return activeLink === link;
};
$scope.addLink = function(event) {
activeLink = {
engine: {
name : "engine" + ($scope.linksForPerson.length + 1),
base_url : " someUrl"
}
};
$scope.linksForPerson.push(activeLink);
};
});
and html (note use of ng-bind)
<div ng-controller="ManageLinksController">
<div class="list-group link-list" ng-show="linksForPerson.length">
<a href="#" class="list-group-item" ng-repeat="link in linksForPerson" ng-click="showLinkDetail(link)" ng-class="{active: isSelectedLink(link)}">
<h4 class="list-group-item-heading" ng-bind="link.engine.name"></h4>
<p class="list-group-item-text" ng-bind="link.engine.base_url"></p>
<p class="list-group-item-text" ng-show="link.user_sync_id" ng-bind="link.user_sync_id"></p>
<p class="list-group-item-text" ng-show="link.group_sync_id" ng-bind="link.group_sync_id"></p>
</a>
</div>
<span class="glyphicon glyphicon-plus"></span> Add a new link
</div>
here's jsfiddle for you to play with

scrollTo in AngularJS

Until now I have always been using jQuery for my single page "scroll to div" applications, but since in making an Angular app (just for learning purposes) I try to do everything in angular instead of falling back on good ol' jQuery.
Im trying to make a scroll-to-div-on-the-same-page-menu, but Im not really sure on how to do this in Angular tho.
Currently I'm using this snippet to do what I want:
JS
app.directive('scrollOnClick', function() {
return {
restrict: 'A',
link: function(scope, $elm, attrs) {
$elm.click(function() {
var linkHref = attrs.href;
angular.element('html,body').animate({
// select the element the href points to
scrollTop: angular.element(linkHref).offset().top - 60
}, 'slow');
});
}
}
});
HTML
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a ng-href="/" role="button" class="navbar-brand">angularApp</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
</div>
</div>
</div>
<div id="page-wrap">
<div id="one">...</div>
<div id="two">...</div>
<div id="three">...</div>
</div>
But it doesn't work perfectly.
I need it to scroll with a margin of 60px as you can se in the code, because of my fixed navbar. I also want it to navigate slower and have a pretty url like /two instead of /#two.
How is this achieved?
If you have jQuery loaded already, you can use angular.element as an alias for $. That's usually the "proper" way to do it in angular. If you don't load jQuery first, angular.element uses jQLite, which is limited in terms of what selectors are available. Since you're already using jQuery, lets assume you already have it loaded up before angular.
Assuming that your jQuery example is working the way you like, you can just use the same selectors, substituting $elm for target, subtract the 60px and also set the animation timer to 1000 as you did in your jQuery example.
Try something like this:
app.directive('scrollOnClick', function() {
return {
restrict: 'A',
link: function(scope, $elm) {
$elm.click(function() {
// same as your jQuery example
angular.element('html,body').animate({
scrollTop: $elm.offset().top - 60
}, 1000);
});
}
}
});
--EDIT--
Wait, I see what you're trying to do here...
You need to get the href attribute from your element. The link function can take a third argument, which is the attributes attached to the element. Then instead of setting scrollTop to the offset of the link you clicked, you set it to the offset of the element in the href, using angular.element to select that. Like this:
app.directive('scrollOnClick', function() {
return {
restrict: 'A',
link: function(scope, $elm, attrs) {
$elm.click(function() {
var linkHref = attrs.href;
angular.element('html,body').animate({
// select the element the href points to
scrollTop: angular.element(linkHref).offset().top - 60
}, 1000);
});
}
}
});
I ended up using angular-scrollto which I think is very good. Very simple to use. All I had to do after I installed it with bower was to inject the module and the simply add this to my HTML (just an example from the github):
Go to element
<div id="element">
You will scroll to me
</div>

Categories

Resources