I'm trying to reset a $scope variable in my controller when a url parameter changes, but I'm having some trouble with it holding on to old values.
I have a site I'm building for a law firm and if I click to one of the attorney's bios from any page except the bio page itself, it works fine. But, if I try to click to a new attorney's bio page when I'm already on the bio page, it doesn't seem to reset my $scope.thisAttorney variable but rather creates a second instance of it.
The problem with this is that I have a box with rotating quotes about the current attorney that's set up with a timeout function. So, when this problem hits, it has two sets of quotes rotating in that box. I need it to forget about the first attorney when I click on the second attorney's bio.
Here are what I think are relevant files. Please just ask if you need to see something else.
app.js
var app = angular.module("LKSU", ['ngRoute']);
app.config(function($routeProvider) {
$routeProvider
// route for the home page
.when('/', {
templateUrl: 'content.php',
controller: 'HomeController'
})
.when('/bios/:user_id?', {
controller: 'AttorneyController',
templateUrl: 'bio.php'
})
.otherwise({
redirectTo: '/'
});
});
AttorneyController.js
app.controller('AttorneyController', ['$scope', '$location', 'attorneys', '$sce', '$routeParams', function($scope, $location, attorneys, $sce, $routeParams) {
$scope.myFunctions = {};
var practiceareas = {
altdispute: "Alternative Dispute Resolution",
businesscorp: "Businesses & Corporations",
estateplanning: "Estate Planning",
futures: "Futures & Derivatives",
litigation: "Litigation",
productliability: "Product Liability",
realestate: "Real Estate",
securities: "Securities"
};
function quoteflip(quotelist, id, total){
clearTimeout(timeoutQuotes);
alert($scope.thisAttorney.name + " 1"); // This is how I know it's holding onto the first attorney in $scope.thisAttorney
var idno = (id + 1) % total;
$("#bio_quotes").html(quotelist[id]);
var src1 = quotelist[idno];
$("#bio_quotes").fadeOut(500, function(){
$("#bio_quotes").html(src1).fadeIn(500);
});
timeoutQuotes = window.setTimeout(function(){quoteflip(quotelist, idno, quotelist.length);}, 5000);
}
var timeoutQuotes = "";
attorneys.success(function(data){
if($routeParams.user_id > 0){
var matches = $.grep(data.attorneys, function(obj) { return obj.id == $routeParams.user_id; });
if (matches.length === 1) {
$scope.thisAttorney = "";
$scope.thisAttorney = matches[0];
$scope.$apply();
var src = $scope.thisAttorney.quotes[0];
$("#bio_quotes").html(src).fadeIn(500);
clearTimeout(timeoutQuotes);
$scope.attorneys = data.attorneys;
$scope.practiceareas = practiceareas;
timeoutQuotes = window.setTimeout(function(){quoteflip($scope.thisAttorney.quotes, 0, $scope.thisAttorney.quotes.length);}, 5000);
}
}else{
$scope.myFunctions.bio_id = 0;
};
});
}]);
Thoughts?
For the record, I tried to put quoteflip in the main script.js but timeout call couldn't find it so I had to bring it back into the Controller. If anyone has a fix for that, i.e.: sees my problem, please feel free to comment on that as well. Thanks.
I would suggest using Angular's $timeout service documentation can be found here
You just need to pass this in as an additional controller dependency:
app.controller('AttorneyController', ['$scope', '$location', 'attorneys', '$sce', '$routeParams', function($scope, $timeout, $location, attorneys, $sce, $routeParams) { .... }
So
window.setTimeout(function(){quoteflip(quotelist, idno, quotelist.length);}, 5000);
Becomes
$timeout(function(){quoteflip(quotelist, idno, quotelist.length);}, 5000)
If you could provide a plunk with the relevant code this would be helpful also.
Have you tried using $timeout rather than window.setTimeout? You probably need it because your $scope isn't being applied-on this non-angular async service.
Use $timeout. That has an automatic $apply build in so your values will be digested and updated on screen.
Related
I have a variable that i use almost everywhere in my app, in many different controllers.
I'm looking for the best possible way of setting that variable knowing that :
-The variable will need to get updated from a controller and resulting in an update on the whole app ( other controllers mainly).
-Some of those function are instance of an object with function in itself that have a callback , does that cause any issue?
So far there seems to be 2 way of doing that :
rootScope but that rarely advised apparently:
myApp.run(function ($rootScope) {
$rootScope.var = "string";
});
And building a custom directive ( but what about setting it's variable ?)
angular.module('myApp').factory('test', function() {
return {
test : 'string'
};
});
Can anyone point me in the right direction and help me choose betweem the two?
I would recommend using a service or factory save that value in someService.variable and the broadcast n event that this value has been changed. I am attaching sample code. It may contain some syntactical error but I want to give you an idea
angular.module("myapp")
.controller('myCtrl', function($scope, $rootScope, myService) {
$scope.change = function() {
myService.var = 'abc';
$rootScope.broadcast('varChanged');
};
})
.controller('myOtherCtrl', function($scope, $rootScope, myService) {
$rootScope.on('varChanged', function(e) {
use(myService.var);
});
})
.service('mySerice', function() {
this.var = '';
});
My English is very bad,at first Controller,I post data form The server,and i got a $rootScope.YD for Transfer data .but when i use the YD in the second page,it does't work.can you help me ?
.controller('yd_homeCtrl',function($rootScope, $scope, $state, $http,$ionicActionSheet, $ionicModal)
{
$scope.getReadList = function ()
{
var url = $rootScope.rootUrl + "/read.php";
var data = {
"func":"getReadList",
"unionid":$rootScope.userinfo.unionid,
"fr":1
};
encode(data);
$rootScope.LoadingShow;
$http.post(url, data).success(function (response)
{
$rootScope.LoadingHide();
$rootScope.YD=response.data.result[0];
$state.go('yd_improve')
}).error(function (response, status)
{
$rootScope.LoadingHide();
$rootScope.Alert('连接失败![' + response + status + ']');
return;
});
}
})
.controller("yd_improveCtrl",function($rootScope, $scope, $state, $http, $ionicActionSheet, $ionicModal, $stateParams, $interval, $sce, $ionicHistory,$ionicSlideBoxDelegate)
{
$scope.text="";
angular.forEach($rootScope.YD,function(value,key){
if(key==0)
{
//alert(1111111);
//alert(value.text);
$scope.text=value.text;
alert($scope.text);
}
});
});
there is app.js state:
.state('yd_improve', {
cache: false,
url: '/yd_improve/:id',
onExit: function ()
{
var v = document.getElementById("audio");
v.pause();
v.src = "";
},
templateUrl: 'templates/yd_improve.html',
controller: 'yd_improveCtrl'
})
Use $broadcast service. It's the most reliable solution for broadcasting events and passing parameters between controllers.
Don't rely on storing data in service because on page refresh, service also gets refreshed and you will lose the data stored in service's variable. So maintaining state in service is highly unrecommended. Use $broadcast instead.
You have a couple of options. Create a service which stores the information. In first controller set the data. And in second controller get the data.
Or you can use $broadcast. This publishes an event with a name you give it and the data you parse through. You do this on first controller. And on second controller listen for the event using $rootScope.on.
But I'd recommend using a service its best solution in terms of memory and performance.
I believe I am experiencing the same issue mentioned here: $anchorScroll and $location only work after second try
I reviewed the plunker that works and I have routing in place, but it is still taking two clicks. I am using ng-route and not ui-router. How can I prevent it taking two clicks to get anchorScroll to work? As the first wants to cause a route to be established versus scrolling to the appropriate anchor.
Here is the click:
<a data-ng-click="gotoRequests()">Requests</a>
Here is the destination:
<div id="pendingrequests"></div>
Here is the function in my controller:
$scope.gotoRequests = function() {
// set the location.hash to the id of
// the element you wish to scroll to.
$location.hash('pendingrequests');
// call $anchorScroll()
$anchorScroll();
};
I was able to solve it using one of the answers here: How to handle anchor hash linking in AngularJS
by creating the following function:
$scope.scrollTo = function(id) {
var old = $location.hash();
$location.hash(id);
$anchorScroll();
//reset to old to keep any additional routing logic from kicking in
$location.hash(old);
};
I would call this as follows:
Yipee
<div id="pendingrequests"></div>
Latest Update
From AngularJS 1.4.0 $anchorScroll allows you to directly pass the id as a parameter without the need to update the URL with the hash.
During click
<div data-ng-click="gotoRequests(pendingrequests)"> </div>
In Controller
$scope.gotoRequests = function(divId) { $anchorScroll(divId); }
I also had the same issue with angular 1 and I solved it using $timeout. Here is an example of how I did it
angular.module('app').controller('MyTestController', ['$scope', '$location', '$anchorScroll', '$timeout', function($scope, $location, $anchorScroll, $timeout) {
function scrollToElement (element, offset){
$timeout(function() {
$anchorScroll.yOffset = offset; // add extra pixels to scroll initially
var old = $location.hash();
$location.hash(element);
$anchorScroll();
$location.hash(old);
});
}
scrollToElement('element ID', 100);
}]);
You need to add $timer for 300 like:
this.gotoBottom = function(scrollId) {
$timeout(function() {
$location.hash(scrollId); $anchorScroll(scrollId);
}, 300);
}
I am newbie for angularjs.I have list of persons and each person have edit and delete button. when i click to edit button ng-dialog box was open and show person details and person can change and save information on database,behind save button ajax call trigger and update information on database.
Updating information on database work well but on UI side my view doesn't reflect my database changes.
I had tried to apply "$scope.$apply();" method but i got error message "$digest already in progress".
Please help me,how can refresh my scope after ajax call.
You can use shared service for that and broadcast any event through this service. Broadcasted event can be listened in any controller with $scope.$on.
For example:
angular.module("app", []).factory("sharedService", function($rootScope){
var mySharedService = {};
mySharedService.values = {};
mySharedService.personWasUpdated = function(){
$rootScope.$broadcast('update');
}
return mySharedService;
});
Ctrl for person editing.
app.controller('personEditController', ['$scope', 'sharedService', '$http', function ($scope, sharedService, $http) {
$scope.updatePerson = function(newPerson){
$http.post("../some URL/..", {person: newPerson})
.success(function(data){
sharedService.personWasUpdated(); //event broadcasing
})
};
}
Ctrl for displaying list of persons.
app.controller('personController', ['$scope', 'sharedService', '$http', function ($scope, sharedService, $http) {
var loadPersonsData = function(){
$http.get("../some URL/..").
.success(function(data){
$scope.persons = data;
})
};
loadPersonsData(); //first load
$scope.$on('update', function () {
loadPersonsData(); // load after update of any person
});
}
Try with $scope.$digest(); or use $http instead jQuery ajax or others
I develop a HTML 5 app, and have a view where user comments are loaded. It is recursive: any comment can have clickable sub-comments which are loading in the same view and use the same controller.
Everything is OK. But, when I want to go back, comments are loading again, and I lose my position and sub-comments I loaded.
Can I save the state of the view when I go back? I thought I can maybe use some kind of trick, like: append a new view any time I click on sub-comment and hide the previous view. But I don't know how to do it.
Yes, instead of loading and keeping state of your UI inside your controllers, you should keep the state in a service. That means, if you are doing this:
app.config(function($routeProvider){
$routeProvider.when('/', {
controller: 'MainCtrl'
}).when('/another', {
controller: 'SideCtrl'
});
});
app.controller('MainCtrl', function($scope){
$scope.formData = {};
$scope./* other scope stuff that deal with with your current page*/
$http.get(/* init some data */);
});
you should change your initialization code to your service, and the state there as well, this way you can keep state between multiple views:
app.factory('State', function(){
$http.get(/* init once per app */);
return {
formData:{},
};
});
app.config(function($routeProvider){
$routeProvider.when('/', {
controller: 'MainCtrl'
}).when('/another', {
controller: 'SideCtrl'
});
});
app.controller('MainCtrl', function($scope, State){
$scope.formData = State.formData;
$scope./* other scope stuff that deal with with your current page*/
});
app.controller('SideCtrl', function($scope, State){
$scope.formData = State.formData; // same state from MainCtrl
});
app.directive('myDirective', function(State){
return {
controller: function(){
State.formData; // same state!
}
};
});
when you go back and forth your views, their state will be preserved, because your service is a singleton, that was initialized when you first injected it in your controller.
There's also the new ui.router, that has a state machine, it's a low level version of $routeProvider and you can fine grain persist state using $stateProvider, but it's currently experimental (and will ship on angular 1.3)
Use a Mediator
If you use a Mediator you'll be decreasing your Out-Degree (Fan-Out) by a factor of 2.
Benefits:
You're not coupling your module directly to your server ($http).
You're not coupling your module to an additional service (State).
Everything you need for state-persistence is right there in your controller ($scope / $scope.$on, $emit, $broadcast).
Your Mediator knows more and can direct the application more efficiently.
Downside(?):
Your modules need to fire interesting events ($scope.$emit('list://added/Item', $scope.list.id, item))
mediator.js
angular.module('lists.mediator', ['lists', 'list', 'item']).run(function mediate($rootScope){
var lists = [];
$rootScope.lists = lists;
$rootScope.$watch('lists', yourWatcher, true);
function itemModuleOrControllerStartedHandler(e, itemId, disclose){
if(!lists.length){
$http.get(...).success(function(data){
lists.push.apply(lists, data);
var item = getItem(lists, itemId);
disclose(item); // do not copy object otherwise you'll have to manage changes to stay synchronized
});
} else {
var item = getItem(lists, itemId);
disclose(item);
}
}
$rootScope.$on('item://started', itemModuleOrControllerStartedHandler);
});
// angular.bootstrap(el, ['lists.mediator'])
item-controller.js
var ItemController = function ItemController($scope, $routeParams){
var disclosure = $scope.$emit.bind($scope, 'item://received/data', (+new Date()));
$scope.itemId = $routeParams.id;
$scope.item = { id: -1, name: 'Nameless :(', quantity: 0 };
function itemDataHandler(e, timestamp, item){
$scope.item = item;
}
$scope.$on('item://received/data', itemDataHandler);
$scope.$emit('item://started', $scope.id, disclosure);
};