$routeChangeSuccess inside directive doesn't fire - angularjs

I have a directive placed in my navigation header. The point of it is to add/remove the active class. It does this by hooking into $routeChangeSuccess and looking at the anchors where the href is the route name.
angular.module('frApp')
.directive('bsActiveLink', ['$location', function ($location) {
return {
restrict: 'A', //use as attribute
replace: false,
link: function (scope, elem) {
//after the route has changed
scope.$on("$routeChangeSuccess", function () {
console.log("Route changed");
var hrefs = ['/#' + $location.path(),
'#' + $location.path(), //html5: false
$location.path()]; //html5: true
angular.forEach(elem.find('a'), function (a) {
a = angular.element(a);
if (-1 !== hrefs.indexOf(a.attr('href'))) {
a.parent().addClass('active');
} else {
a.parent().removeClass('active');
}
});
});
}
};
}]);
div class="col-3" style="vertical-align: middle;" bs-active-link>
<ul class="nav navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="/unpaid">Unpaid</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/paid">Paid</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/recon">Recon</a>
</li>
</ul>
</div>
The main issue is this event doesn't get called on initial load. Now normally that's not an issue as you can see I set one as default and I have the angularjs routing redirect to this. But if I place another route directly into the address bar it'll load that one but visually the default '/unpaid' route has the active class on it. If I remove the default active class in the html then nothing is active. I'm seeing that my $routeChangeSuccess isn't hooked yet when the page loads.
It would seem to me that directives couldn't rely on this event hook on startup? What's the workaround to that?

Have you tried something like:
link: function (scope, elem) {
function update(...) {}
// just call here
update();
scope.$on("$routeChangeSuccess", update);
P.S. Such directives do not follow angular ideas. As an example why it is bad - if at some point new element appears in DOM - it wont be covered by directive. E.g. look at ui-router and u will see how this should be done:
~something like this~~
<div>
<a href="/unpaid" my-active-if="unpaid">... // <- custom directive on each href, sp no weird find required
<a href="/paid" my-active-if="paid">...

Related

AngularJS sidebar link to stay active when clicked

I am creating a sidebar based off of this one
http://plnkr.co/edit/xzcjStdvmkI2rpfMzLjI?p=preview found from this tutorial
However I want to keep the selection active once the link is clicked. I've tried adding data-toggle, but that seems to work for nav-pills instead of navbar. I also found an is-active-nav directive that I've attempted to implement:
angular.module('sidebarmodule')
.directive('isActiveNav', ['$location', function ($location) {
return {
restrict: 'A',
link: function (scope, element) {
scope.location = $location;
scope.$watch('location.path()', function (currentPath) {
if ('/#' + currentPath === element[0].attributes['href'].nodeValue) {
element.parent().addClass('active');
} else {
element.parent().removeClass('active');
}
});
}
};
}]);
That I call in the sidebar template like so:
<li> <a is-active-nav href="#">Link1</a> </li>
Another approach I tried is in my controller adding $scope.isActive = function (viewLocation) { return viewLocation === $location.path(); }; and then adding to the template: <li ng-class="{ active: isActive('/importinvoice')}">First Link</li>
However, nothing I do will keep the sidebar elements highlighted after navigating to that page. I am trying to keep out of jQuery as I want it to be an AngularJS solution only.
I tried most of the solutions from this stackoverflow answer but can't get any of them to work on the plunker.
You can create a tab property in $scope, assign it a tab identifier and then use ng-class="{'active': tab==='tab1'}"
use this :
<div class="sidebar" sidebar-directive="state" ng-init="item=-1">
Navigation
<ul class="navigation">
<li class="navigation-items"> Link1
</li>
<li class="navigation-items"> Link2
</li>
<li class="navigation-items"> Link3
</li>
<li class="navigation-items"> Link4
</li>
<li class="navigation-items"> Link4
</li>
</ul>
</div>
see link : http://plnkr.co/edit/xzcjStdvmkI2rpfMzLjI?p=preview

AngularJS: clicking on active tab removes location hash tag [duplicate]

I want to validate certain condition before the browser follow the link dynamically created by ui-router.
I was looking into $rootscope.$on('$stateChangeStart', ..) but I have no access to the controller.$scope from there. I also need to use this in several places in the application and would be cumbersome.
Keep in mind that ui-sref is linked to ui-sref-active (work together), so i can't remove ui-sref and, by say, to use $state.$go('some-state') inside a function called with ng-click.
The condition should be evaluated inside a $scope function and on on-click event (before-transition with the ability to cancel it)
I need something like this:
<li ui-sref-active="active">
<a ui-sref="somestate" ui-sref-if="model.validate()">Go Somestate</a>
</li>
I tried:
<li ui-sref-active="active">
<a ui-sref="somestate" ng-click="$event.preventDefault()">Go Somestate</a>
</li>
<li ui-sref-active="active">
<a ui-sref="somestate" ng-click="$event.stopImmediatePropagation()">Go Somestate</a>
</li>
And
<li ui-sref-active="active">
<a ui-sref="somestate">
<span ng-click="$event.stopPropagation();">Go Somestate</span>
</a>
</li>
Even
<li ui-sref-active="active">
<a ui-sref="somestate" onclick="return false;">Go Somestate</a>
</li>
But does not work.
SANDBOX
This answer inspired me to create a directive that allows me to interrupt the chain of events that end up changing state. For convenience and other uses also prevents the execution of ng-click on the same element.
javascript
module.directive('eatClickIf', ['$parse', '$rootScope',
function($parse, $rootScope) {
return {
// this ensure eatClickIf be compiled before ngClick
priority: 100,
restrict: 'A',
compile: function($element, attr) {
var fn = $parse(attr.eatClickIf);
return {
pre: function link(scope, element) {
var eventName = 'click';
element.on(eventName, function(event) {
var callback = function() {
if (fn(scope, {$event: event})) {
// prevents ng-click to be executed
event.stopImmediatePropagation();
// prevents href
event.preventDefault();
return false;
}
};
if ($rootScope.$$phase) {
scope.$evalAsync(callback);
} else {
scope.$apply(callback);
}
});
},
post: function() {}
}
}
}
}
]);
html
<li ui-sref-active="active">
<a ui-sref="somestate" eat-click-if="!model.isValid()">Go Somestate</a>
</li>
PLUNKER
You can use a scope function that will either returns :
no state
an existing state
like so :
HTML :
<li ui-sref-active="active">
<a ui-sref="{{checkCondition()}}">Go Somestate</a>
</li>
JS scope :
$scope.checkCondition = function() {
return model.validate()
? 'someState'
: '-' // hack: must return a non-empty string to prevent JS console error
}
href attribute will be created only when the function returns an existing state string.
Alternatively, you could do a (ugly) :
<li ui-sref-active="active">
<a ui-sref="somestate" ng-if="model.validate()">Go Somestate</a>
<span ng-if="!model.validate()">Go Somestate</span>
</li>
Hope this helps
The easiest workaround to conditionally achieve routing without tinkering with directives, scope etc was a workaround i found here - https://github.com/angular-ui/ui-router/issues/1489
<a ui-sref="{{condition ? '.childState' : '.'}}"> Conditional Link </a>
You can always double up on the element and show/hide conditionally
<li ui-sref-active="active">
<a ng-show="condition1" style="color: grey">Start</a>
<a ng-hide="condition1" ui-sref="start">Start</a>
</li>
http://plnkr.co/edit/ts4yGW?p=preview
No need for complicated directives or hacks. The following works fine and allows for specific handling on click of non-sref items:
<a
ng-repeat="item in items" ui-sref="{{item.sref || '-'}}"
ng-click="$ctrl.click(item, $event)"
>...</a>
And in the controller, a simple click handler for the items which don't have an item.sref:
this.click = function(item, event) {
if (!item.sref) {
event.preventDefault();
//do something else
}
};
Based on the answers to How to dynamically set the value of ui-sref you can create a function in your scope for building the URL:
$scope.buildUrl = function() {
return $state.href('somestate', {someParam: 'someValue});
};
And then conditionally append it to the link with ng-href
<a ng-href="{{ someCondition ? buildUrl() : undefined }}">Link</a>
As you can see in the demo below, ng-href does not add the href attribute if value is negative.
angular.module('app', [])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<a ng-href="{{ condition ? 'http://thecatapi.com/api/images/get?format=src&type=gif' : undefined}}">This is the link</a>
<br>
<label for="checkbox">
<input type="checkbox" id="checkbox" ng-model="condition">
Link active?
</label>
</div>
I know this is an old question, but for future reference I wanted to offer an alternative solution since I didn't see it in any of the answers so far.
Desired:
<li ui-sref-active="active">
<a ui-sref="somestate" ui-sref-if="model.validate()">Go Somestate</a>
</li>
Potential solution (template):
<li ng-class="{ active: state.current.name === 'somestate' }">
<a ng-click="navigateToState()">Go Somestate</a>
</li>
And in the controller:
$scope.state = $state;
$scope.navigateToState = navigateToState;
function navigateToState() {
if ($scope.model.valid) {
$state.go('somestate');
}
}
Possible solution for those who still need ng-click working on ui-sref component or its parents.
My solution is to use href instead of ui-sref and to modify Emanuel's directive a bit to be able to stop href and ng-click calls separately.
Planker.
Though it has a few restrictions:
will not work with ui-sref
you should have different urls for each state because of previous restriction
ui-sref-active will not work either
For the binary case (link is either enabled or disabled), it "now" (since ~2018) works like this (prevents the click and sets it to disabled):
<a ui-sref="go" ng-disabled="true">nogo</a>
and for other tags as well:
<span ui-sref="go" ng-disabled="true">nogo</span>

angularjs directive calls image effect script

I have a directive to make news item to have an effect like usatoday.com when user hover on the news. I'm new to angularjs :D
Directive:
app.directive('hotEvent', ['$rootScope', function ($rootScope) {
return {
restrict: 'EA',
templateUrl: '/App/Main/views/home/events.html',
link: function (scope, iElement, attrs) {
//attrs references any attributes on the directive element in html
var dathumb = $(iElement).find('.da-thumbs > li a');
//$(function () {
dathumb.each(function () {
$(this).hoverdir();
});
//});
}
};
}]);
View: /App/Main/views/home/events.html
<ul class="row da-thumbs">
<li ng-repeat="news in featuredEvents">
<a href="/">
<img src="abc.jpg" /> >> no effect ???
<div>
<h4>aaa</h4>
<p>
bbb
</p>
</div>
</a>
</li>
<li>
<a href="/">
<img src="abc.jpg" /> >> show effect
<div>
<h4>bbb</h4>
<p>
ccc
</p>
</div>
</a>
</li>
</ul>
On Home.html: (which already binded with controller)
<div hot-event></div>
It works when i don't bind data from the controller <li ng-repeat="news in featuredEvents">, now the effect just doesn't show up. Console.log show 0 error.
UPDATED: i ended up using document ready
app.directive('hotEvent', function () {
return {
restrict: 'EA',
templateUrl: '/App/Main/views/home/events.html',
link: function ($scope, iElement, attrs) {
angular.element(document).ready(function () {
var dathumb = $(iElement).find('.da-thumbs > li a');
dathumb.each(function () {
$(this).hoverdir();
});
});
}
}
});
If you debug your code you'd see that your directive didn't find any elements.
It happens because when the template loads, the directive link function gets called, but the ng repeat didn't have time to populate it self (it starts off empty), there for it's no where to be found.
An easy workaround is to use the jquery find method in a setTimeout 0 function, or use $scope.evalAsync on the function that does the jquery find (it requires angular 1.3).
But the best solution would be to fix the code to actually not require jquery.
P.s. when you see your self using selectors in a directive you are usually doing things wrong in angular (note: usually), please refer to this "Thinking in AngularJS" if I have a jQuery background? awesome question and answer.
note that he actually says that when you first learn angular, don't use jquery at all :)

AngularJS directive for accessing ng-repeat scope of child

Okay, so let me start off saying that I have a directive that creates a JQueryUI tab setup with a backing field that populate the tab name and the tab contents basically.
<div id="summaryTabPanel">
<ul>
<li ng-repeat="category in SummaryCategories">
{{category.Name}}
</li>
</ul>
<div ng-repeat="category in SummaryCategories" id="tabs-{{$index + 1}}">
{{category.Content}}
</div>
</div>
So as you can see, I have a <ul> with an ng-repeat in the <li>.
As I will be applying JQueryUI Tab function to the parent container id="summaryTabPanel", I need to wait until all the dom is rendered.
So I do some research, and find out I need to create a directive that kind of looks like the following:
Application.Directives.directive("repeatDone", function () {
return {
restrict: "A",
link: function (scope, element, attrs) {
scope.$watch("repeatDone", function () {
if (scope.$last) {
$("#summaryTabPanel").tabs({
event: "mouseover"
});
}
;
});
}
};
});
This watches to see if the last repeated item is found, and then applies the DOM changes via the statement $("#summaryTabPanel").tabs()
This is all fine and good, but it only works if I apply the directive to the last ng-repeated child item of the summaryTabPanel container.
<div id="summaryTabPanel">
<ul>
<li ng-repeat="category in SummaryCategories">
{{category.Name}}
</li>
</ul>
<div ng-repeat="category in SummaryCategories" id="tabs-{{$index + 1}}" repeat-done>
{{category.Content}}
</div>
</div>
If I move it to the previous ng-repeat item things work but not quite right. If I move it to the parent container, things don't work at all.
It seems wrong to me to apply this directive to the last child, and instead have a way to apply it to the root and somehow accomplish the same thing.
<div id="summaryTabPanel" repeat-done>
Is this possible somehow with Angular?
According to this link you should write your directive like this
Application.Directives.directive("repeatDone", function () {
return {
restrict: "A",
link: function (scope, element, attrs) {
$timeout( function () {
$("#summaryTabPanel").tabs({
event: "mouseover"
});
});
}
});
Please have a look at the jsfiddle I created to illustrate your case.

Angular UI Router: How do I get parent view to be "active" when navigating to nested view?

I'm working on a project that has implemented the UI router and it's using ui-sref-active="active" to add the active class to the navigation menu item when that item is the current route. However, when you navigate to a nested view within that view, the parent menu item is no longer active. See the following Plunker:
http://plnkr.co/edit/2CoEdS?p=preview
By default (or if you click on it) Route 1 is "active". When you click on "Show List," you will see that Route 1 is no longer active.
Edit:
The only difference between this example and my actual project is that the navigation menu in my actual project has its own controller and so does not use the same scope as the controller for "route1".
EDIT For updated ui-router 0.2.13:
ui-sref-active="active" now sets the 'active' class when the current state is the ui-sref's state or any child
ui-sref-active-eq="active" behaves as the previous iterations of ui-sref-active, and only sets the class for the exact state
Original Answer:
See open ui-router issues:
https://github.com/angular-ui/ui-router/issues/704 and 818
A general workaround people are suggesting is:
ng-class="{active:$state.includes('route1')}"
Of course, $state must be added to $scope. See updated plunk: http://plnkr.co/edit/KHLDJP?p=preview
You are having a wrong understanding of ui-sref-active="active"
<li ui-sref-active="active"><a ui-sref="route1">Route 1</a></li>
This will show special css highlighting only when you are in state route1 (reference https://github.com/angular-ui/ui-router/wiki/Quick-Reference#wiki-ui-sref-active). This is the case when you click on route 1. But when you click on "Show list" you are no longer in route1.
Rather you are in state "route1.list" . You can verify this by writing the following code. This is strictly for understanding how state works.
js
inside controller
$rootScope.currentState = $state.$current.name //using rootScope so that you can access the variable anywhere inside html
inside html
{{currentState}}
If you look closely at documentation of ui-sref-active, it not only looks at stateName but also stateParams, hence when you go to substate it no longer changes css. From the sourcecode it becomes clearer.
function update() {
if ($state.$current.self === state && matchesParams()) {
$element.addClass(activeClass);
} else {
$element.removeClass(activeClass);
}// route1===route1.list will not be true.
to solve the problem, remember scope variables are inherited in nested views.
inside controller of route.
$scope.route1Active = true;
in html
<li ng-class={active:route1Active}><a ui-sref="route1">Route 1</a></li>
Angular UI router now supports this natively. See commit https://github.com/angular-ui/ui-router/commit/bf163ad6ce176ce28792696c8302d7cdf5c05a01
My solution was to set:
<li ng-class="{ 'active': state.current.name.indexOf('route1') != -1 }">
The state was already previously added to the controller's scope:
$scope.state = $state;
You do not need to do any thing in the controllers. Here is my example code:
<ul class="nav nav-pills nav-stacked" role="tablist">
<li ui-sref-active="active"><a ui-sref="Booking.Step1" href="#/Booking/Step1">Step1</a></li>
<li ui-sref-active="active"><a ui-sref="Booking.Step2" href="#/Booking/Step2" >Step2</a></li>
<li ui-sref-active="active"><a ui-sref="Booking.Step3" href="#/Booking/Step3">Step3</a></li>
<li ui-sref-active="active"><a ui-sref="Booking.Step4" href="#/Booking/Step4">Step4</a></li>
<li ui-sref-active="active"><a ui-sref="Booking.Step5" href="#/Booking/Step5">Step5</a></li>
</ul>
In route configuration:
$stateProvider.state('Booking', {
abstract: true,
url: '/Booking',
templateUrl: "TourApp/Templates/Booking/SideMenu.html",
controller: "SideMenuController"
});
$stateProvider.state('Booking.Step1', {
url: "/Step1",
views: {
'content': {
templateUrl: "TourApp/Templates/Booking/Step1.html",
controller: "Step1Controller"
}
}
});
$stateProvider.state('Booking.Step2', {
url: "/Step2",
views: {
'content': {
templateUrl: "TourApp/Templates/Booking/Step2.html",
controller: "Step2Controller"
}
}
});
Now they have updated and the new way to do that is
<a ui-sref-active="{'active': 'main.app.manage.**'}" ui-sref="main.app.manage.people.list"></a>
Below is the state file
angular.module('manage.people', ['people.invite'])
.config(['$stateProvider', function($stateProvider) {
$stateProvider
.state('main.app.manage.people', {
url: '/people',
abstract: true,
cache: false,
views: {
'tabContent': {
template: '<div ui-view="peopleContent"></div>',
controller: 'peopleController'
}
}
})
.state('main.app.manage.people.list', {
url: '/list',
views: {
'peopleContent': {
templateUrl: '/internal/main/app/manage/people/views/people.html',
controller: 'peopleListController'
}
}
});
We already have a solution without any "hack" HERE
That's the way to do:
HTML >
<li ui-sref-active="active" >
<a href="#" class="submenu" ui-sref="bands">
<i class="fa fa-location-arrow" aria-hidden="true"></i>
Bands
<span class="fa fa-chevron-down"></span>
</a>
<ul class="nav child_menu">
<li ui-sref-active="active">
<a ui-sref="bands.nirvana">
Nirvana
</a>
</li>
<li ui-sref-active="active">
<a ui-sref="bands.iron">
Iron
</a>
</li>
<li ui-sref-active="active">
<a ui-sref="bands.metalica">
Metalica
</a>
</li>
</ul>
Our router config will be like this >
$stateProvider.state('bands', {
abstract: true,
url: '/bands',
templateUrl: "myapp/categories/template.bands.html", //<ui-view></ui-view>
controller: "SomeController as vm"
}).state('bands.nirvana', {
url: '/nirvana',
templateUrl: "myapp/categories/band.nirvana.html",
controller: "SomeController as vm"
}).state('bands.iron', {
url: '/iron',
templateUrl: "myapp/categories/band.iron.html",
controller: "SomeController as vm"
}).state('bands.meatlica', {
url: '/metalica',
templateUrl: "myapp/categories/band.metalica.html",
controller: "SomeController as vm"
})
I have come here 2 years later the question was asked but angular-ui-router has much proficient approach in solving this issue. It worked for nested states too.
<ul>
<li ui-sref-active="active" class="item">
<a ui-sref="home">Home</a>
</li>
<!-- ... -->
</ul>
When app navigates to home state, this is how resulting HTML will appear :
<ul>
<li ui-sref-active="active" class="item active">
<a ui-sref="home">Home</a>
</li>
<!-- ... -->
</ul>
ui-sref-active quick reference :
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.
Complete documentation.
I hope this is what you have been looking for.Place the parent url in the list class ,now whenever you navigate to child class parent class will be active
Step 1: Add a Controller for your nav bar orin your existing controller where nav bar is included add the following
app.controller('navCtrl', ['$scope', '$location', function($scope, $location) {
$scope.isActive = function(destination) {
return destination === $location.path();
}
}]);
Step2: In your nav bar
<li ng-class="{active: isActive('/home')}"><a ui-sref="app.home">Browse Journal</a></li>
Thats it.

Resources