Google's new reCaptcha breaks angular with ui-router - angularjs

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('/');
})

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 :-)');
}
}
]);

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

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.

$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

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.

Why does this Angular ui router code cause an infinite loop in $digest?

I've boiled the code down as much as I can. Something about nested states and the event handling/broadcasting is causing an infinite loop. In Chrome I can pause it and see that it is looping forever in Angular's $digest function. Any idea why? Is it a bug in my example code, or a bug in Angular, or the UI Router?
<!doctype html>
<html ng-app='bugapp' ng-controller='BugAppCtrl'>
<head>
<script src='//code.jquery.com/jquery-1.10.1.min.js'></script>
<!-- Angular 1.2.11 -->
<script src='//ajax.googleapis.com/ajax/libs/angularjs/1.2.11/angular.js'></script>
<!-- UI router 0.2.8 -->
<script src='//cdn.jsdelivr.net/angular.ui-router/0.2.8/angular-ui-router.js'></script>
<script>
angular.module('bugapp', ['ui.router'])
.run(function ($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
})
.config(function ($locationProvider, $stateProvider, $urlRouterProvider) {
$locationProvider.html5Mode(false);
$stateProvider
.state("root", {
abstract: true,
url: "/servletContext?asUser",
template: '<div ui-view></div>' // ???
})
.state("root.home", {
abstract: true,
url: "/home",
template: "<div ng-if='hasData()' ui-view ></div>"
})
.state("root.home.profile", {
url: "/profile",
template: '<div>whatever</div>'
})
})
.controller('BugAppCtrl', function ($scope, $state, $stateParams, $log, $location) {
$log.log('BugAppCtrl: constructor');
$scope.hasData = function() {
var res = !!$scope.foo;
// $log.log("hasData called, returing " + res + " foo is " + $scope.foo);
return res;
};
$scope.$on('$stateChangeSuccess', function () {
$log.log("State changed! (to " + $state.current.name + ")");
$scope.foo = 'junk';
$scope.$broadcast("resetfoo");
});
$state.go('root.home.profile');
});
</script>
</head>
<body>
<div ui-view></div>
</body>
</html>
I suspect this is a bug in the UI Router, for two reasons:
I tried your code and then tried downgrading the version of UI
Router to 0.2.7. When I used 0.2.7, it worked.
Even if you keep using version 0.2.8 of UI Router, if you perform the state change through $location instead of $state, it works. Here's an example of using $location instead of the $state.go call:
$location.path('/servletContext/home/profile');
Although I use and recommend UI Router (can't do without nested views), if you find in the future that you want to do any sort of intercepting or redirecting when the user tries to go to certain pages, I recommend using $location.path instead of $state, for reasons I described in a blog post
Edit: I haven't tried with parameters before but just tried with the code you posted (I created a 2nd controller and assigned it to your 'root.home.profile' state), and it works. Instructions from UI Router are here. But basically if you set your URL in your state definition the same way you would with UI Router:
url: "/profile/:foo",
then in your $location.path call add the parameter in the path:
$location.path('/servletContext/home/profile/12');
and you can access the 12 in the controller from
$stateParams.foo

Resources