ui-router - controller will load twice when calling $state.go in it - angularjs

code:
<!DOCTYPE html>
<html>
<head>
<title>Ui State Demo</title>
<meta charset="utf-8">
<!--AngularJS v1.5.9-->
<script type="text/javascript" src="angular.min.js"></script>
<!--angular-ui-router v0.2.15-->
<script type="text/javascript" src="angular-ui-router.min.js"></script>
<script type="text/javascript">
var app = angular.module('uiDemo', ['ui.router']);
app.config(function($stateProvider) {
$stateProvider.state('parent', {
url: '/parent',
controller: 'ParentController'
}).state('childOne', {
controller: 'ChildOneController',
template: '<h1>Child One</h2>'
}).state('childTwo', {
controller: 'ChildTwoController',
template: '<h1>Child Two</h2>'
});
});
app.controller('ParentController', ['$state', function($state) {
console.log('Parent Controller Start');
if (Math.round(Math.random()) == 0) {
$state.go('childOne');
} else {
$state.go('childTwo');
}
}]);
app.controller('ChildOneController', function() {
console.log('Child One');
});
app.controller('ChildTwoController', function() {
console.log('Child Two');
});
</script>
</head>
<body ng-app="uiDemo">
<nav>
<a ui-sref="parent">Parent</a>
</nav>
<ui-view></ui-view>
</body>
</html>
State parent has a url while childOne and childTwo have none. They share same url with parent.
When I click Parent, it will redirect to ChildOneController or ChildTwoController by random, but will also load ParentController twice.
It works fine if I put two different urls on both ChildControllers. But I want to keep the url same as parent after redirected to ChildControllers.
Can someone help? How to avoid this twice loading issue?

But I want to keep the url same as parent after redirected to
ChildControllers. From comment: So I wonder is there a way to avoid
duplicated contoller loading and keep the 'parent' state and
controller?
Option1
You can write for parent state abstract:true so this controller will be loaded once only when state changes from parent.childOne to parent.childTwo
For example:
state('parent',
{
url: '/parent',
abstract:true,
templateUrl: 'parent.html',
controller: 'ParentController'
})
.state('parent.childOne',{ url: '/childOne', templateUrl: 'childOne.html'})
.state('parent.childTwo',{ url: '/childTwo', templateUrl: 'childTwo.html'})
Option 2
You can do some trick to avoid to call controller second time:
At beginning of controller call:
if ($state.transition) return; //hack https://github.com/angular-ui/ui-router/issues/64

Well you could make the parent not a 'state' and just have it as a page controller tied to your nav.
<nav ng-controller="ParentController">
<a ng-click="vm.loadChildState()">Parent</a>
</nav>
Make a method called loadChildState() in the 'parent' controller, which then performs as it does now, using $state.go.
It should still function the same, and clicking on the link will load up the individual child states as you currently have it, without re-loading the Parent Controller.

Related

Finding a div in an AngualrJS ui-router view, using document.querySelector

Please see https://jsfiddle.net/mawg/fu9er5cy/3/
I modified an exciting Plunker (hence this is a little more complex than necessary to demonstrate my problem), and in <div ng-controller="myController">
I added <div id="myDiv">Can this be found?</div>
and in myController, I added:
if (document.querySelector('#myDiv') === null)
{
alert('Div not found !!');
}
else
{
alert('yay, Div found :-)');
}
The div can be seen by selecting "book info" and I naively thought that the controller's code, and, hence, document.querySelector() would be executed when I navigate to that state's view.
As you can see, it is evaluated immediately, and says that the div can not be found.
As you can also see, from my related question, I only want to find the div when I change state & navigate to the state which shows that view and, hence, its controller, so that I can document.querySelector the div and inject an ag-grid into it.
How can I do that?
1) Your <div id="myDiv">Can this be found?</div> exists in:
$stateProvider.state("details", {
url: "/details",
and this is not your default route: $urlRouterProvider.otherwise("/")
so let's change it to $urlRouterProvider.otherwise("/details")
2) I don't know the golal of:
getID: function($timeout) {
return $timeout(function() {
console.log("value resolved")
//$scope.Company="Cognizant";
}, 1000)
}
but it stucks your route for 1 sec. You start to render the view only after 1 sec.
so I removed this code snippet.
3) You try to check the id in the same digest cycle with view rendering, so you get a failure. to trigger the digest cycle or enter to the end of queue you can add some zero timeout:
$timeout(function(){
if (document.querySelector('#myDiv') === null)
{
alert('Div not found !!');
}
else
{
alert('yaya, Div found :-)');
}
},0)
fixed example Fiddle
Hope it will give you some input to figure out :)
You can run a check whenever something in the DOM changes, and then remove the listener, once the div is found
function func() {
if (document.querySelector('#myDiv') !== null) {
document.documentElement.removeEventListener('DOMSubtreeModified', func);
alert('yaya, Div found :-)');
}
}
document.documentElement.addEventListener('DOMSubtreeModified', func);
func();
in trying to reply to #Maxim, I coded a Plunker from scratch.
It does exactly what I want it to.
It seems that every time a router state is entered, its controller is initialized, and in the initialization code I can find the div.
There is something wrong with my much larger app, which does not find the div. So, I will start with the Plunker and drop my app into it, piece by piece, until I get it to work.
I post this answer in the hope that it will help someone in future. Here's the code:
alpha.html
<div>
<h1>Alpha</h1>
</div>
<a ui-sref="beta" ui-sref-active="beta">Beta</a>
beta.html
<div id="beta_div">
<h1>Beta</h1>
</div>
<a ui-sref="alpha" ui-sref-active="alpha">Alpha</a>
index.html
<!DOCTYPE html>
<html ng-app="app">
<head>
<link type="text/css" rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
<script type="application/javascript" src="//code.jquery.com/jquery-1.11.3.min.js"></script>
<script type="application/javascript" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script type="application/javascript"
src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.min.js"></script>
<script type="application/javascript"
src="//rawgit.com/angular-ui/ui-router/0.2.13/release/angular-ui-router.js"></script>
<script type="application/javascript" src="app.js"></script>
<script type="application/javascript" src="controllers.js"></script>
</head>
<body ui-view></body>
</html>
app.js
angular.module('app', [
'ui.router'
]);
angular.module('app').config(['$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/alpha');
$stateProvider.state('alpha', {
url: '/alpha',
templateUrl: './alpha.html',
controller: 'alphaController'
});
$stateProvider.state('beta', {
url: '/beta',
templateUrl: './beta.html',
controller: 'betaController'
});
}
]);
controllers.js
angular.module('app').controller('alphaController', ['$state',
function ($state) {
}
]);
angular.module('app').controller('betaController', ['$state',
function ($state) {
if (document.querySelector('#beta_div') === null) {
alert('Div not found !!');
}
else {
alert('Yay, Div found :-)');
}
}
]);

Why can't my directive require a controller created by angular-ui-router?

I am trying to write a directive that requires a parent controller that is configured in an angular-ui-router state definition.
Directive "beep" has require: '^subController
"subController" is the controller for a view configured in angular-ui-router.
That view contains an element that uses the directive "beep".
However: There is an exception that says "subcontroller cannot be found" as soon as I navigate to the substate.
Bug in angular-ui-router? Some misunderstanding on my side?
To reproduce: Just click on "Run code snippet" and then click on the "Sub State" button:
angular
.module('foo', ['ui.router'])
.controller('subController', function () {
console.log("subController");
})
.config(function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/root");
$stateProvider
.state('root', {
url: '/root',
views: {
root: {
template: '<button ui-sref="root">Root State</button>' +
'<button ui-sref="root.sub">Sub State</button>' +
'<div ui-view="sub"></div>'
}
}
})
.state('root.sub', {
url: '/sub',
views: {
sub: {
controller: 'subController',
template: '<div beep>SUB</div>'
}
}
});
})
.directive('beep', function () {
return {
restrict: 'A',
require: '^subController',
link: function (scope, element, attributes, subController) {
console.log("beep: linked", subController);
}
};
})
;
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.3.0/angular-ui-router.js"></script>
<script src="app.js"></script>
</head>
<body ng-app="foo">
<div class="root-view" ui-view="root"></div>
<pre id="errors" style="color: #e22; margin-top: 20px;"></pre>
</body>
</html>
You can't search for a controller, you can only search for a directive in the same element or a parent element.
When you have that directive, the controller of that directive will be passed as a 4th argument of the link function.
Furthermore controller of directives and controller bind to view/state/ng-controller are differents concepts. A directive's controller has no scope, it's used to expose an API to other directives, the most known is the controller of ngModel.
If you need to be in a particular state/controller for your directive, that mean you have something wrong in your design. Tell us what functionnality you want to do and we can help you how to design it in angular.
'require' in the directive is to include parent directive not the controller.
one way of using the subController is by including controller:subController in the directive 'beep'

$watch for location.hash changes

In my angular app I am trying to change the location to "#home" when the user enters an invalid route.
Here's a short but complete app demonstrating what I did.
<!DOCTYPE html>
<html>
<head>
<script src="angular.js"></script>
<script>
angular.module('app', [])
.controller('ctrl', function($scope, $location, $window) {
$scope.$watch (function() { return $location.url() }, function(url) {
if (url == '')
$window.location = '#home'
})
})
</script>
</head>
<body ng-app="app" ng-controller="ctrl"></body>
</html>
But when I do this I get an infdig error when the page loads. I don't know whats wrong.
Edit:
I don't want to use routing libraries like "ui-router". so answers like this one that uses the "angular-route" library are not helpful.
You can specify that in app.config block using $routeProvider.
app.config(function ($routeProvider) {
$routeProvider
.when('/home', {
template: 'home.html',
controller: 'homeController'
})
.otherwise('/home');
});
.otherwise method will redirect the application to /home, if user tries to access any invalid path which is not specified in config.
Update:
And, If you don't want to use angular routing library you can use native javascript event hashchange on window to listen for hash change events. and redirect accordingly.
see the below example.
function locationHashChanged() {
if (location.hash !== "#/home") {
console.log('hash is other then #home. will redirect to #home');
console.log('Full path before', document.URL);
window.location.hash = '#/home';
console.log('Full path after', document.URL);
}
}
window.addEventListener('load', locationHashChanged);
window.addEventListener('hashchange', locationHashChanged);
Home
<br/>
Some-Site
<br/>
Other

Google's new reCaptcha breaks angular with ui-router

When I tick the recaptcha box, ui-router seems to break down. All links with ui-sref attributes on the page stops working, but I get no error messages. Please look at this plunker that I've set up. Do you have any idea of what might be the cause?
script.js
var app = angular.module('app', ['ui.router'])
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/',
templateUrl: 'home.html',
controller: function() {
grecaptcha.render('captcha', {
sitekey: "--sitekey--"
})
}
})
.state('other', {
templateUrl: 'other.html'
});
$urlRouterProvider.otherwise('/');
})
index.html
<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angular.js#*" data-semver="1.4.0-rc.0" src="https://code.angularjs.org/1.4.0-rc.0/angular.js"></script>
<script data-require="ui-router#*" data-semver="0.2.13" src="//rawgit.com/angular-ui/ui-router/0.2.13/release/angular-ui-router.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
</head>
<body>
<ui-view></ui-view>
</body>
home.html
<h1>Home</h1>
<p>Click the re-captcha, then try to change state by clicking the link below.</p>
<p><a ui-sref="other">Go to other state</a></p>
<div id="captcha"></div>
other.html is irrelevant.
UPDATE
I've noticed that the state transition fails only when the target state has a url defined, and I've managed to narrow it down to the following piece of code in the ui-router source (ui-sref directive):
element.bind("click", function(e) {
var button = e.which || e.button;
if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr('target')) ) {
// HACK: This is to allow ng-clicks to be processed before the transition is initiated:
var transition = $timeout(function() {
$state.go(ref.state, params, options);
});
e.preventDefault();
// if the state has no URL, ignore one preventDefault from the <a> directive.
var ignorePreventDefaultCount = isAnchor && !newHref ? 1: 0;
e.preventDefault = function() {
if (ignorePreventDefaultCount-- <= 0)
$timeout.cancel(transition);
};
}
});
As you can see, the e.preventDefault function is being overridden to cancel the timeout that will fire the state change. If you comment out this piece of code in the above binder the state transition works:
//e.preventDefault = function() {
// if (ignorePreventDefaultCount-- <= 0)
// $timeout.cancel(transition);
//};
Normally ui-router calls e.preventDefault() once per click on each anchor link, but when the reCaptcha is ticked it seems to be called twice. I am guessing that this might be connected to the problem, but I'm not 100% sure. Maybe someone with better debugging skills can understand what is happening. Here's a plunker that demonstrated this behaviour.
I've found a simple way to solve the problem with any kind of router on angular. when grecaptha got undefined, it's means that we've in your case a synchronization problem. So in my/ your controller you should load the script by using for example << $.getScript("URL") >>
SO :
In HTML
<div class="g-recaptcha" data-sitekey="YourSiteKey"></div>
And in your Controller :
$.getScript("https://www.google.com/recaptcha/api.js");
Here your let grecaptcha do this work like if you were loading your page for the first time.
I got the same error when using angular routing.
You can solve this problem by placing this code :
in index.html
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit"></script>
in your script.js :
var app = angular.module('app', ['ui.router']);
app.config(function($stateProvider, $urlRouterProvider) {
$stateProvider.state('home',
{
url: '/',
templateUrl: 'home.html',
controller: function() {
function onloadCallback() {
grecaptcha.render('yourHTMLElementId', {
'sitekey' : 'yourSiteKey'
});
}
onloadCallback();
}
})
.state('other', {
templateUrl: 'other.html'
});
$urlRouterProvider.otherwise('/');
})

angular and ui-router example is not working

Hello I'm trying a simple application with Angular and UI-Router...
But for some reason, it's not working at all.
In chrome, there is no error in the console, but there is even no output at all
Maybe some one has some clue on what's happening... I definitely have no idea.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<script src="/Scripts/angular.min.js"></script>
<script src="/Scripts/angular-ui-router.js"></script>
</head>
<body>
<div>
Route 1
<div ui-view="viewSidebar"></div>
<div ui-view="viewContent"></div>
</div>
<script>
var app = angular.module('app', ['ui.router']);
app.config(
['$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/media/list');
$stateProvider.state('mediaList', {
views: {
url: "/media/list",
'viewSidebar': {
template: '<p>SideBar</p>',
controller: 'SidebarControllerView'
},
'viewContent': {
template: '<p>Thumb view</p>',
controller: 'MediaControllerView'
}
}
});
}]);
app.controller('MediaControllerView', ['$scope', MediaControllerView]);
app.controller('SidebarControllerView', ['$scope', SidebarControllerView]);
function MediaControllerView($scope) {
$scope.model = 1;
};
function SidebarControllerView($scope) {
$scope.model = 1;
};
</script>
</body>
</html>
There is a working plunker
One important wrong setting is the URL. It does not belong to the view, but to the state:
...
$stateProvider.state('mediaList', {
// url belongs to the state
url: "/media/list",
views: {
// url does not belong to the views
// url: "/media/list",
'viewSidebar': {
template: '<p>SideBar</p>',
controller: 'SidebarControllerView'
},
'viewContent': {
template: '<p>Thumb view</p>',
controller: 'MediaControllerView'
}
}
});
...
Check it here
And also, as DTing spotted we have to provide ng-app:
<html ng-app="app" ng-strict-di>
...
NOTE: ng-strict-di is not a must but... it is a must - to avoid later issues...
if this attribute is present on the app element, the injector will be created in "strict-di" mode. This means that the application will fail to invoke functions which do not use explicit function annotation (and are thus unsuitable for minification), as described in the Dependency Injection guide, and useful debugging info will assist in tracking down the root of these bugs.

Resources