19

Let say I have nested DOMs and each has ui-sref for different angular-ui-router state. I want to click outer to only alert outer and click inner to only alert inner. Currently if I click inner, it will alert both outer and inner state.

HTML:

<!DOCTYPE html>
<html ng-app="plunker">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.2.x" src="http://code.angularjs.org/1.2.13/angular.js" data-semver="1.2.13"></script>
    <script data-require="ui-router@0.2.8" data-semver="0.2.8" src="http://angular-ui.github.io/ui-router/release/angular-ui-router.js"></script>
    <script src="app.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <a ui-sref="outer" style="width:300px;height:300px;background:yellow;">
      Outer
            <span ui-sref="inner" style="width:100px;height:100px;background:red;">Inner</span>
    </a>
  </body>

</html>

Javascript:

var app = angular.module('plunker', ['ui.router']);

'use strict';

app.config(['$stateProvider', '$urlRouterProvider', '$locationProvider', function($stateProvider, $urlRouterProvider, $locationProvider) {

  $stateProvider
    .state("outer", {
      url: "/outer",
      resolve: {
        test: function() {
          alert("Triggered state outer");
          return true;
        }
      }
    })
    .state("inner", {
      url: "/inner",
      resolve: {
        test: function() {
          alert("Triggered state inner");
          return true;
        }
      }
    });

}]);


app.controller('MainCtrl', function($scope) {
});

Link: http://plnkr.co/edit/yzgUAA3lLwkF9svzczl3?p=preview

Questions:

  1. How to prevent inner to trigger outer state?

  2. Should I implement some kind of stopImmediatePropagation for the inner DOM so it won't trigger the parent DOM's ui-sref?

  3. Are there alternatives? Maybe using $state.go manually?

UPDATE 1:

I cannot change HTML due to requirement. This is just a simplified version because in my code, the outer element is very large containing other elements.

HP.
  • 19,226
  • 53
  • 154
  • 253

6 Answers6

40

USE THIS...

 <a ui-sref="outer" style="width:300px;height:300px;background:yellow;">
 Outer
     <span ui-sref="inner" style="width:100px;height:100px;background:red;"
     ng-click="$event.stopPropagation()">Inner</span>
</a>
David_DD
  • 585
  • 1
  • 5
  • 12
6

This works

<a ui-sref="outer" style="width:300px;height:300px;background:yellow;">
  Outer
        <span ui-sref="inner" style="width:100px;height:100px;background:red;" ng-click="$event.stopPropagation()">Inner</span>
</a>

I didn't know you can add ng-click="$event.stopPropagation()" in the same element as ui-sref

HP.
  • 19,226
  • 53
  • 154
  • 253
  • 1
    It works but I appear to be getting a whole page reload which is probably not the desired behaviour ... certainly not for me. – Lewis Jan 15 '15 at 12:46
  • ui-sref updates the href item of the a tag, it has no effect on the click event. Just as with jQuery you can ```$('a').attr('href', 'http://example.com').on('click', (e) => {/*something*/})``` – Itanex Feb 06 '18 at 18:55
2

For a completely different purpose I had a stopEvent directive just waiting to be used for the nested ui-sref purpose.

<a ui-sref="outer">
  Outer
  <span ui-sref="inner" stop-event>Inner</span>
</a>

This is the directive

app.directive('stopEvent', function () {
  return {
    restrict: 'A',
    link: function (scope, element, attr) {
      element.bind('click', function (e) {
        e.stopPropagation();
      });
    }
  };
});
Danilo Cândido
  • 408
  • 1
  • 5
  • 18
Christiaan Westerbeek
  • 10,619
  • 13
  • 64
  • 89
0

The problem is not with your javascript code but your html

  <a ui-sref="outer" style="width:300px;height:300px;background:yellow;">
      Outer
            <span ui-sref="inner" style="width:100px;height:100px;background:red;">Inner</span>
    </a>

when you click anywhere inside the tag, outer state will be invoked.

your

Inner

lies inside tag so this html element is bound to both ui-sref="inner" and ui-sref="outer"

the below code should solve your problem

<a ui-sref="outer" style="width:300px;height:300px;background:yellow;">
      Outer

    </a>

<span ui-sref="inner" style="width:100px;height:100px;background:red;">Inner</span>
Kumar V
  • 8,810
  • 9
  • 39
  • 58
himangshuj
  • 730
  • 5
  • 9
  • Please see my `update 1`. I am aware of changing HTML to fix this but it's not my scope. – HP. Mar 07 '14 at 09:09
0

Should I implement some kind of stopImmediatePropagation for the inner DOM so it won't trigger the parent DOM's ui-sref?

Yes, exactly.

Alternately, you might be able to rearrange your HTML so that the don't have a parent/child relationship.

Scimonster
  • 32,893
  • 9
  • 77
  • 89
0

Add the event stop propogation/event prevent default at ur inner stateprovider :

  $stateProvider.state("outer", {
      url: "/outer",
      resolve: {
       test: function() {
      alert("Triggered state outer");
      return true;
    }
  }
}).state("inner", {
     url: "/inner",
     resolve: {
     test: function() {

      alert("Triggered state inner");
      //$event.stopPropagation();
      $event.preventDefault();
      return true;
    }
  }
})

Update 1: I tried on plunker demo and $event is available. As per angular doc also for
Event object is available as $event. DEMO Angular doc

Update 2: Above works if u press outer div first but that's not rt solution I agree. So here is another solution which works. checking the statechangestart of scope and saving last state name in var and checking if its inner then event preventdefault;

app.controller('MainCtrl', function($scope) {
   $scope.name = 'World';
   var laststate = "";
   $scope.$on('$stateChangeStart',
     function(event, toState, toParams, fromState, fromParams) {
               //alert(laststate);
      if (laststate == "inner") {
         event.preventDefault();
       }
      laststate = toState.name;
    });
  });

DEMO Plunker

Neha
  • 1,548
  • 2
  • 10
  • 13
  • I clicked Inner and it alerted both Inner a and outer, therefore not meeting requirement – HP. Mar 08 '14 at 08:18
  • This "hack" works but it's not ideal as I may have `inner1` to `inner1000`. So the solution must be generic enough to handle tons of different DOMs and states within other DOMs with their own states. – HP. Mar 10 '14 at 08:18