Scroll to first error not working - javascript

I have the following functionin my controller
$scope.progress = function () {
var form = $scope.coverDetails;
for (i in form) {
if ($scope.coverDetails[i].hasOwnProperty('$valid') && !$scope.coverDetails[i].$valid) {
$location.hash(i + '-label');
break;
}
};
$scope.submitted = true;
$scope.validateForm();
if ($scope.coverDetails.$valid) {
$location.path('/zones');
}
$anchorScroll();
};
This is kind of working. But not really.
When I click on the submit button (which calls the above function) it successfully updates the hash, however, none of the ng-class or ng-show directives update, until the 2nd press of the button, assuming $location.hash hasn't changed between the first and second.
an example of one of the bits not working is
<label id="reg-label" for="reg" ng-class="{'error': coverDetails.reg.$invalid && submitted}">
Number plate
</label>
in this example, the class 'error' isn't applied, but it will scroll to the label if the field is invalid.
anyone able to help?

So I figured out what was going on,
changing the hash causes a tempalte reload, so one has to prevent that using the following code
angular.module("ScrollToErrorPrevention", []).factory('$preventErrorReload', ['$route',
function ($route) {
return function ($scope) {
var lastRoute = $route.current;
$scope.$on('$locationChangeSuccess', function () {
if (lastRoute.$$route.templateUrl === $route.current.$$route.templateUrl) {
$rootScope.$broadcast('locationChangedWithoutReload', $route.current);
$route.current = lastRoute;
}
});
};
}
])
and including it within the app thus:
angular.module('app', ['ScrollToErrorPrevention'])
and call the factory method from the controller
angular.module('app').controller('controller', ['$scope', $preventErrorReload,
function($scope, $preventErrorReload) {
$preventErrorReload($scope);
...
}
]);
after this, using $location.hash(...) along with $anchorScroll() works fine
The work is not entirely my own, but modified from one or 2 (more?) other sources

Related

angularjs scope variables are not working but works on second click

Hi I am developing web application in angularjs. I have implemented print functionality. I am binding values to view using scope variables. When i click first time on print my values are not binding. When i click second time all my values will bind. Below is the print functionality.
$scope.Print = function () {
if ($scope.period == null) {
toastr.error($filter('translate')('Please select Year.'));
}
else {
$scope.vehiclepriceprint = $cookieStore.get("carpriceprint");
$scope.downpaymentprint = $cookieStore.get("downpayment");
}
First time also i will be having values in cookie but not binding to view. Whenever i click second time on print button everything will be working
Below is the print button.
<input type="button" value="{{'Print' | translate}}" class="blue-button" ng-click="Print()" id="btnPrint">
Below is the html code of print.
<span id="lblDownPayment">{{downpaymentprint}}</span>
<span id="lblFinancialAmount">{{vehiclepriceprint}}</span>
May i know what is the issue i am facing here? Any help would be appreciated. Thank you.
Try using $scope.$apply(); after updating the model values inside the controller. Now the view will get updated after that. This is a sample.
$scope.Print = function () {
if ($scope.period == null) {
toastr.error($filter('translate')('Please select Year.'));
}
else {
$scope.vehiclepriceprint = $cookieStore.get("carpriceprint");
$scope.downpaymentprint = $cookieStore.get("downpayment");
$scope.$apply(); // To update view
}
}
Try using $apply. Although this is not a good approach.
$scope.Print = function () {
if ($scope.period == null) {
toastr.error($filter('translate')('Please select Year.'));
}
else {
$scope.vehiclepriceprint = $cookieStore.get("carpriceprint");
$scope.downpaymentprint = $cookieStore.get("downpayment");
$timeout(function(){//inject $timeout as dependency
$scope.$apply();
})
}
Edit 1: Try this approach. here I have used Object (key:value) ways to update the print data.
$scope.printData = {};
$scope.Print = function () {
if ($scope.period == null) {
toastr.error($filter('translate')('Please select Year.'));
}
else {
$scope.printData.vehiclepriceprint = $cookieStore.get("carpriceprint");
$scope.printData.downpaymentprint = $cookieStore.get("downpayment");
}
HTML:
<span id="lblDownPayment">{{printData.downpaymentprint}}</span>
<span id="lblFinancialAmount">{{printData.vehiclepriceprint}}</span>
Analysis
Actually, ngClick calls $scope.$apply() under-the-hood (in the safest way possible). So you should not call it explicitly in your Controller.
Problem is that the variables you are trying to display/update are attached directly to the $scope, and this approach usually causes this issue.
Solution
$scope.vm = {};
$scope.Print = function() {
$scope.vm.vehiclepriceprint = $cookieStore.get("carpriceprint");
};
<span id="lblFinancialAmount">{{ vm.vehiclepriceprint }}</span>

AngularJS service is not updating the view

I have a service declared in my application.js file within my AngularJS project. It looks like this:
application.factory('interfaceService', function ($rootScope, $timeout) {
var interfaceService = {};
interfaceService.lang = "";
interfaceService.dev = ""
interfaceService.theme = "";
interfaceService.integ = "";
//For Integration Type
interfaceService.demo = function (dev, theme, integ, lang) {
this.dev = dev;
this.theme = theme;
this.integ= integ;
this.lang = lang;
this.broadcastItem();
};
interfaceService.broadcastItem = function () {
$timeout(function(){
$rootScope.$broadcast('handleBroadcast');
});
};
return interfaceService;
});
I am using the above service to pass variables between 2 of my Controllers. The controller which calls the service is here:
$scope.buildDemo = function () {
interfaceService.demo(device, template, integ, language);
$rootScope.template = template;
$rootScope.themeFolder = template;
$state.go("MainPage", {
"themeName": $rootScope.themeFolder
});
}
That function is triggered when the user clicks on a div on my view
<div id='build-btn' ng-click='buildDemo()'>Go</div>
The problem I am having is that my view is not updating when I click on the button. I have to click on it a second time to see any changes. Would anyone know what is causing this? The URL updates and the new view comes onto the page but the elements that should be showing based on the parameters set in the service are not visible until I click on the "Go" button a second time.
On first click handleBroadcast event gets broadcasted and but haven't received by anyone, because when you press "GO" button, at that time state transition occurs and it loads template and its controller. When controller instantiated, it register the listener event using $on on handleBroadcast event.
I'd suggest you that to wait to broadcast an event till controller & its underlying view gets render. This can be done by taking advantage of promise returned by $state.go method. Which completes when transition succeeds.
Code
$scope.buildDemo = function() {
//state transition started.
$state.go("MainPage", {
"themeName": $rootScope.themeFolder
}).then(function() {
//state transition completed
//controller instance is available & listener is ready to listen
interfaceService.demo(device, template, integ, language);
$rootScope.template = template;
$rootScope.themeFolder = template;
});
}

ng-hide doesn't removed from class even when ng-show is true

Requirement : To Show and Hide a div.
HTML
<div ng-show="IsSuccess">
My Div Content
</div>
HTML after page load
<div class="ng-hide" ng-show="false">
HTML after updated from controller (http post call)
<div class="ng-hide" ng-show="true">
ng-show is true but still class contains ng-hide
How to resolve this issue ?
For reference, below is my controller
myController.controller('AuthenticationController',
function AuthenticationController($scope, $location, authDataService, loginDuration) {
$scope.Login = {};
$scope.IsSuccess= false;
$scope.login = function () {
authDataService.authenticateUser($scope.Login, $scope).then(
function (status) {
if (status === 200) {
if ($scope.message == 'Login failed') {
$scope.IsSuccess= true;
}
else {
$scope.IsSuccess= false;
}
}
},
function (data) {
$scope.ErrorMessage = data.Message;
}
);
}
});
Because authDataService.authenticateUser is returning a promise that looks like it's outside of the angular context, angular doesn't know when the scope changes. In that situation, you need to add $scope.$apply()
if ($scope.message == 'Login failed') {
scope.IsSuccess= true;
}
else {
$scope.IsSuccess= false;
}
$scope.$apply();
** Edit: Extended Explanation **
Because you asked for more details about this, I'll try to explain a little further.
$scope.$apply() needs to be called when outside of the angular context. Here's what I mean by outside of the angular context:
$scope.login = function() {
// inside angular context
console.log('a');
setTimeout(function() {
// outside angular context
console.log('b');
$scope.hello = 'b';
// $scope.$apply() needs to be called
$scope.$apply();
}, 1000);
// inside angular context
console.log('c');
$scope.hello = 'c';
};
In this example, here's the output to the log:
a
c
// $scope.$apply() is assumed at this point
b
Angular knows it needs to adjust its bindings after the last line of $scope.login() is processed, and so $scope.$apply() is assumed then, but Angular doesn't know if you have any other callback functions that might be called later on through another context, another context being setTimeout or jQuery's $.ajax or $.Deferred, etc. If that different context modifies the $scope, then you need to call $scope.$apply() to manually update the Angular bindings.
If I am understanding your question correctly, I would change your HTML to show
<div ng-hide="IsSuccess">
My Div Content
</div>
and then in your angular file
$scope.login = function () {
if(<!-- logic to hide or show-->){
$scope.IsSuccess = false;
}else{
$scope.IsSuccess = true;
}
}
Hopefully this helps.
HTML code:
<button class="show-more-btn ng-hide" ng-show="hasMoreItemsToShow()" ng-click="showMoreItems()"">Show More</button>
Javascript code:
setTimeout(function(){ $('.show-more-btn').removeClass('ng-hide');
}, 3000);

"$apply already in progress" when opening confirm dialog box with Firefox

I am (sometimes) getting a weird $apply already in progress error when opening a confirm dialog box in the following and innocent looking situation :
var mod = angular.module('app', []);
mod.controller('ctrl', function($scope, $interval, $http) {
$scope.started = false;
$scope.counter = 0;
// some business function that is called repeatedly
// (here: a simple counter increase)
$interval(function() {
$scope.counter++;
}, 1000);
// this function starts some service on the backend
$scope.start = function() {
if(confirm('Are you sure ?')) {
return $http.post('start.do').then(function (res) {
$scope.started = true;
return res.data;
});
};
};
// this function stops some service on the backend
$scope.stop = function() {
if(confirm('Are you sure ?')) {
return $http.post('stop.do').then(function (res) {
$scope.started = false;
return res.data;
});
};
};
});
// mock of the $http to cope with snipset sandbox (irrelevant, please ignore)
mod.factory('$http', function ($q) {
return {
post: function() {
return $q.when({data:null});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="ctrl">
<button ng-disabled="started" ng-click="start()">Start</button>
<button ng-disabled="!started" ng-click="stop()">Stop</button>
<br/><br/>seconds elapsed : {{counter}}
</div>
</div>
The error message is :
$rootScope:inprog] $apply already in progress http://errors.angularjs.org/1.2.23/$rootScope/inprog?p0=%24apply
And the callstack is :
minErr/<#angular.js:78:12
beginPhase#angular.js:12981:1
$RootScopeProvider/this.$get</Scope.prototype.$apply#angular.js:12770:11
tick#angular.js:9040:25
$scope.start#controller.js:153:8
Parser.prototype.functionCall/<#angular.js:10836:15
ngEventHandler/</<#angular.js:19094:17
$RootScopeProvider/this.$get</Scope.prototype.$eval#angular.js:12673:16
$RootScopeProvider/this.$get</Scope.prototype.$apply#angular.js:12771:18
ngEventHandler/<#angular.js:19093:15
jQuery.event.dispatch#lib/jquery/jquery-1.11.2.js:4664:15
jQuery.event.add/elemData.handle#lib/jquery/jquery-1.11.2.js:4333:6
To reproduce :
use Firefox (I could not reproduce it with Chrome or IE)
open the javascript console
click alternatively the start and stop buttons (and confirm the dialogs)
try multiple times (10-20x), it does not occur easily
The problem goes away if I remove the confirm dialog box.
I have read AngularJS documentation about this error (as well as other stackoverflow questions), but I do not see how this situation applies as I do not call $apply nor do I interact directly with the DOM.
After some analysis, it seems to be a surprising interaction between the $interval and modal dialog in Firefox.
What is the problem ?
The callstack shows something strange : an AngularJS function tick is called within the controller's start function. How is that possible ?
Well, it seems that Firefox does not suspend the timeout/interval functions when displaying a modal dialog box : this allows configured timeout and intervals callback to be called on the top of the currently executing javascript code.
In the above situation, the start function is called with an $apply sequence (initiated by AngularJS when the button was clicked) and when the $interval callback is executed on the top the start function, a second $apply sequence is initiated (by AngularJS) => boom, an $apply already in progress error is raised.
A possible solution
Define a new confirm service (adapted from this and that blog posts) :
// This factory defines an asynchronous wrapper to the native confirm() method. It returns a
// promise that will be "resolved" if the user agrees to the confirmation; or
// will be "rejected" if the user cancels the confirmation.
mod.factory("confirm", function ($window, $q, $timeout) {
// Define promise-based confirm() method.
function confirm(message) {
var defer = $q.defer();
$timeout(function () {
if ($window.confirm(message)) {
defer.resolve(true);
}
else {
defer.reject(false);
}
}, 0, false);
return defer.promise;
}
return confirm;
});
... and use it the following way :
// this function starts some service on the backend
$scope.start = function() {
confirm('Are you sure ?').then(function () {
$http.post('start.do').then(function (res) {
$scope.started = true;
});
});
};
// this function stops some service on the backend
$scope.stop = function() {
confirm('Are you sure ?').then(function () {
$http.post('stop.do').then(function (res) {
$scope.started = false;
});
});
};
This solution works because the modal dialog box is opened within an interval's callback execution, and (I believe) interval/timeout execution are serialized by the javascript VM.
I was having the same problem in Firefox. Using window.confirm rather than just confirm fixed it for me.

Angular Js $scope

When establishing a controller, and setting the $scope to use a factory method (for GETs and POSTs), during the page load process, my POSTs are fired. Below is an example of the code. To fix this, I wrapped the "POST" in a jQuery click event function and everything works smoothly. Below is the code.
In the controller (app.js):
demoApp.controller('SimpleController', function ($scope, simpleFactory) {
$scope.customers = [];
init();
function init() {
$scope.departments = simpleFactory.getDepartments();
}
// Works fine
$('#myButton').click(function () {
simpleFactory.postDepartments();
});
// When setting the "scope" of a controller, during page load the scope factory method is fired off!
// seems like a defect.
//$scope.addDepartment = simpleFactory.postDepartments();
});
So, what is going on here is that if I uncomment the $scope.addDepartment = ... on page load, the postDepartments() factory method is called. This is not the desired behavior. Here is how I have the Html Dom element wired:
<button id="myButton" data-ng-click="addDepartment()">Add Department</button>
So, if I uncomment, like I said above, it adds the department before the user even clicks the button. However, approaching it the jQuery way, there is no issue.
Is this a known bug? Is this the intended functionality? Also, see the factory below, maybe the problem is there?
demoApp.factory('simpleFactory', function ($http) {
var departments = [];
var factory = {};
factory.getDepartments = function () {
$http.get('/Home/GetDepartments').success(function (data) {
for (var i = 0; i < data.length; i++) {
departments.push({ desc: data[i].desc, id: data[i].id });
}
})
.error(function () {
$scope.error = "An Error has occured while loading posts!";
$scope.loading = false;
});
return departments;
};
factory.postDepartments = function () {
$http.post('/Home/PostDepartment', {
cName: 'TST',
cDescription: 'Test Department'
}).success(function (data) {
departments.push({ desc: 'Test Department', id: departments.length + 1 });
})
.error(function () {
$scope.error = "An Error has occured while loading posts!";
$scope.loading = false;
});
return departments;
};
return factory;
});
Try this:
$scope.addDepartment = function() {
return simpleFactory.postDepartments();
}
This will also allow you to pass in arguments in the future, should you decide to. The way you originally had it, you were both assigning the function and calling it at the same time.
Then, you can use it in ngClick:
<button ng-click="addDepartment()">Add Department</button>
Don't use the jQuery click method in your controller, it defeats the purpose of separating the concerns into models, views, controllers, etc. That's what directives are for.

Categories