diff --git a/src/viewDirective.js b/src/viewDirective.js index 3610025f6..ed5c1f57f 100644 --- a/src/viewDirective.js +++ b/src/viewDirective.js @@ -203,10 +203,11 @@ function $ViewDirective( $state, $injector, $uiViewScroll) { function updateView(firstTime) { var newScope = scope.$new(), - name = currentEl && currentEl.data('$uiViewName'), + name = getUiViewName(attrs, $element.inheritedData('$uiView')), previousLocals = name && $state.$current && $state.$current.locals[name]; if (!firstTime && previousLocals === latestLocals) return; // nothing to do + latestLocals = $state.$current.locals[name]; var clone = $transclude(newScope, function(clone) { renderer.enter(clone, $element, function onUiViewEnter() { @@ -217,8 +218,6 @@ function $ViewDirective( $state, $injector, $uiViewScroll) { cleanupLastView(); }); - latestLocals = $state.$current.locals[clone.data('$uiViewName')]; - currentEl = clone; currentScope = newScope; /** @@ -249,16 +248,8 @@ function $ViewDirectiveFill ($compile, $controller, $state) { compile: function (tElement) { var initial = tElement.html(); return function (scope, $element, attrs) { - var name = attrs.uiView || attrs.name || '', - inherited = $element.inheritedData('$uiView'); - - if (name.indexOf('@') < 0) { - name = name + '@' + (inherited ? inherited.state.name : ''); - } - - $element.data('$uiViewName', name); - var current = $state.$current, + name = getUiViewName(attrs, $element.inheritedData('$uiView')), locals = current && current.locals[name]; if (! locals) { @@ -286,5 +277,14 @@ function $ViewDirectiveFill ($compile, $controller, $state) { }; } +/** + * Shared ui-view code for both directives: + * Given attributes and inherited $uiView data, return the view's name + */ +function getUiViewName(attrs, inherited) { + var name = attrs.uiView || attrs.name || ''; + return name.indexOf('@') >= 0 ? name : (name + '@' + (inherited ? inherited.state.name : '')); +} + angular.module('ui.router.state').directive('uiView', $ViewDirective); angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill); diff --git a/test/stateDirectivesSpec.js b/test/stateDirectivesSpec.js index 01514c315..962a5471a 100644 --- a/test/stateDirectivesSpec.js +++ b/test/stateDirectivesSpec.js @@ -447,3 +447,50 @@ describe('uiSrefActive', function() { expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('ng-scope'); })); }); + +describe('uiView controllers or onEnter handlers', function() { + var el, template, scope, document, count; + + beforeEach(module('ui.router')); + + beforeEach(module(function($stateProvider) { + count = 0; + $stateProvider + .state('aside', { url: '/aside', template: '
' }) + .state('A', { url: '/A', template: '
' }) + .state('A.fwd', { + url: '/fwd', views: { 'fwd@A': { + template: '
', + controller: function($state) { if (count++ < 20 && $state.current.name == 'A.fwd') $state.go(".nest"); } + }} + }) + .state('A.fwd.nest', { url: '/nest', template: '
' }); + })); + + beforeEach(inject(function($document) { + document = $document[0]; + })); + + it('should not go into an infinite loop when controller uses $state.go', inject(function($rootScope, $q, $compile, $state) { + el = angular.element('
'); + template = $compile(el)($rootScope); + $rootScope.$digest(); + + $state.transitionTo('aside'); + $q.flush(); + expect(template[0].querySelector('.aside')).toBeDefined(); + expect(template[0].querySelector('.fwd')).toBeNull(); + + $state.transitionTo('A'); + $q.flush(); + expect(template[0].querySelector('.A')).not.toBeNull(); + expect(template[0].querySelector('.fwd')).toBeNull(); + + $state.transitionTo('A.fwd'); + $q.flush(); + expect(template[0].querySelector('.A')).not.toBeNull(); + expect(template[0].querySelector('.fwd')).not.toBeNull(); + expect(template[0].querySelector('.nest')).not.toBeNull(); + expect(count).toBe(1); + })); +});