setting $rootScope but value then not visible from another controller - angularjs

My application requires candidates to be set up with an agency and nationality (among other things). I want to set the list of agencies and nationalities in global variables so that they can be accessed by the profile screen rather than retrieved from the database every time (as they are currently). Thanks to the other questions here I've got so far... but a crucial puzzle piece is clearly missing. The data is being retrieved from the DB but then isn't visible elsewhere.
homecontroller.js which loads up the data...
//Home page Controller
function HomeCtrl($scope, $rootScope, SessionTimeoutService, GetAllAgencies, GetNationalityList){
GetAllAgencies.getData({}, function(agencieslist, $rootScope) {
SessionTimeoutService.checkIfValidLogin(agencieslist);
$rootScope.agencieslistglobal = agencieslist.data;
});
/* I tried hard-coding values here - that worked & was passed thro' ok
$rootScope.nationalitieslistglobal = [
{'nationality_id' : 0, 'name' : 'Unknown'},
{'nationality_id' : 1, 'name' : 'Known'},
{'nationality_id' : 2, 'name' : 'Pants'},
]; */
GetNationalityList.getData({}, function(nationalitieslist, $rootScope) {
SessionTimeoutService.checkIfValidLogin(nationalitieslist);
$rootScope.nationalitieslistglobal = nationalitieslist.data;
});
alert($rootScope.nationalitieslistglobal[8].name);
}
The alert doesn't fire at all.
From the candidatescontroller.js:
function CandidatesAddCtrl($scope, CandidateModel, GetNationalityList, SessionTimeoutService, $http, GetAllAgencies, $rootScope) {
/* commented out as this should now be done globally - this works when it's in place
GetAllAgencies.getData({}, function(agencieslist) {
SessionTimeoutService.checkIfValidLogin(agencieslist);
$scope.agencieslist = agencieslist.data;
});
*/
CandidateModel.getBlankCandidate();
$scope.candidateinfo = CandidateModel;
// get global agencies list
$scope.agencieslist = $rootScope.agencieslistglobal;
alert($scope.agencieslist); // shows 'undefined'
/*
GetNationalityList.getData({}, function(nationalitieslist) {
SessionTimeoutService.checkIfValidLogin(nationalitieslist);
$scope.nationalitieslist = nationalitieslist.data;
});
*/
// get global nationalities list
$scope.nationalitieslist = $rootScope.nationalitieslistglobal;
....
}
So... when I hard-code the data outside of the GetNationalityList.getData function (the bit that's commented out in the example above), it is populated & passed through OK & my drop-down list populates. When I don't do that, the $rootScope values are 'undefined'.
I have 2 theories -
somehow the homecontroller $rootScope isn't being recognised as the global I'm intending (that's just the name of the variable) and some other "passing back" action needs to be taken (I've tried several variations on "return nationalitieslist.data" and assigning it to other variables/handles). Also, nationalitieslist.data is in the same format as the hard-coded list, only it's longer.
I'm being caught out by the asynchronous nature of javascript and when I load the second page, the data just isn't there yet. I'm not convinced this is right as the DB call is finishing and I had an alert which showed me a random nationality name and it was there.
My frustration in part is coming from the fact that the GetNationalityList and GetAllAgencies code snippets work and assigns values correctly in the candidate controller (the bits that are now commented out), but are working subtly differently in the homecontroller.
Top tips, anyone, please?

I am sure this article can help.
http://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
I had somewhat of a similar issue but I needed to emit from a factory.
My problem was answered here.
Angular $rootScope.$on Undefined

Your 2nd point is more your issue, although it's not JavaScript that's async in nature, it's Angular services such as your GetAllAgencies service. You're making async calls but not waiting for the response. That's why when you assign it manually it all works. Try putting your alert() inside the callback function to the getData() call. This should highlight how the process should work.

Related

How to reload a controller's angularJS call from a factory initiated by another controller

I am working on an angularJS project, a music player which can select an album and then play from a list of that album's songs. Everything is working except for that after playing a song from an album(album A) and then selecting another album (album B), when I try to select the previous/next song from a player bar, which is separated from the list, the previous album's (album A) songs play.
Clearly, something is not updating. I read here from a comment by Chev that factories run only once. So, I am thinking the problem might lie with my Songplayer controller(factory).
Here's the code:
AlbumController (the controller with the directive):
....
$rootscope.getAlbumId = $stateParams.getAlbumId;
this.albumData = Fixtures.getAlbum();
this.songPlayer = SongPlayer;
....
FixturesController (factory - serving the music files):
Fixtures.getAlbum = function(){
var chosenAlbum = $rootScope.getAlbumId
return chosenAlbum
....
};
SongPlayerController (factory - playing the music):
....
$rootScope.getAlbumId = $stateParams.getAlbumId;
var currentAlbum = Fixtures.getAlbum();
....
var getSongIndex = function(song){
return currentAlbum.songs.indexOf(song);
};
PlayerBarController:
code same as albumController
It seems the PlayerBarController is not registering the new album from the SongPlayer factory. I have researched and tried using $emit/$broadcast, $watch, various other callbacks, etc... I know the answer is everywhere I have looked but I simply do not have the angular skills yet to figure this out.
Incidentally, I figure my use of $rootScope is also pretty poor, so I am happy to receive advice on that. I used $rootscope and $stateparams in order to capture and register the album id and deliver it to the Fixtures controller.
I think the problem from 'this.albumData = Fixtures.getAlbum();'
You can try:
let albumData = Fixtures.getAlbum();
this.albumData.length = 0;
albumData.map(item => this.albumData.push(item));
I was able to get the PlayerBar controller to register the new album by using $watch. I simply wrapped the call I already had in a $watch function, and voila!
SongPlayer controller:
$rootScope.$watch('getAlbumId', function(album){
currentAlbum = Fixtures.getAlbum();
});

need to find out the source of angularjs factory values modifier

In my angularjs application, I am using factory to store the values and share them across the controllers. But I am getting into a peculiar problem.
The following is my factory :
factory.quoteLinks = {allLinks : [], allLeftLinks :[], curSection : "-1", insType: "-1", test:"-1"};
factory.setQuoteLinks = function(qlinks, qleftLinks, qsubLink, qinsuranceType, testVal) {
factory.quoteLinks = { allLinks : qlinks, allLeftLinks : qleftLinks, curSection: qsubLink, insType: qinsuranceType, test:testVal};
};
factory.getQuoteLinks = function() {
return factory.quoteLinks;
};
As far as I Know, the values will be stored in factory.quoteLinks, only when I call factory.setQuoteLinks. So whenever I explicitly make call to factory.setQuoteLinks, the values are correctly getting stored. After a while debugging remaining part of the code, during debugging, I noticed even though I am not calling factory.setQuoteLinks, the values of allLinks in factory.quoteLinks is getting modified to some other values and I am not able to figure out from where this is getting modified even though I am not calling factory.setQuoteLinks to modify the allLinks at that particular point. Is there any possibility for me to track from where this value in the factory is getting modified, I mean the cause for this modification? I left with no clue how to figure it out?
Ax Max Sorin said you're probably modifying it outside of here because you're passing back the reference to it in factory.getQuoteLinks. If you need this changed use an angular copy:
factory.getQuoteLinks = function() {
return angular.copy(factory.quoteLinks);
};
This will return a copied quoteLinks.

angularjs: controller hijacking another controller's variable

I am facing a very strange issue with variable in one controller being hijacked by another controller. Here are the details:
In my HTML I have two ng-view tags. Each tag leads to a templateURL (an html) that has its own corresponding controller. Ctrl1 and Ctrl2
the two ng-views are at the same level in the html hierarchy - that is, one is NOT the child of another
Controller1 looks like this:
ngEpMod.controller('Ctrl1',[function() {
selfX = this;
abc = 'abc controller1';
console.log(abc); // break point 1
selfX.query = function() {
console.log("abc=");
console.log(abc); // break point 2
console.log("search query=");
console.log(selfX.searchQ);
lySP.searchHomes();
};
}]);
Controller2 looks like this:
ngEpMod.controller('Ctrl2',[function() {
self = this;
abc = 'abc controller2';
}]);
Both controllers are associated in the html using a "controller as" syntax.
The query() method in Ctrl1 is fired when user user clicks a button (ng-click)
Mystery: As I load the html page ($state) that has the two ng-views, I am observing the browser console. I note that abc value at break-point1 is "abc controller1", but when the query() method is fired, it mysteriously changes to "abc controller2". There is no global variable by that name! As I understand, when the page is being laid out, Ctrl1 is created first so at break-point 1 abc has the correct value, then Ctrl2 is created and somehow it high-jacks the abc variable! Stranger even is that I noticed this problem first with my self variable (self = this) and then I introduced abc just for additional check
Gurus, I am a newbie and would really appreciate your help.
By creating a variable without var (or let in ES6), you've created a global variable attached to window:
abc = 'abc controller1'; equals to window.abc = 'abc controller1';
When the 1st controller instantiates it declares the variable abc on window. When the 2nd controller instantiates, it changes the global abc variable content.
To avoid it in this case define var abc in both controllers.
To avoid it in the future add 'use strict'; to each function deceleration, for example:
ngEpMod.controller('Ctrl2',[function() {
'use strict';
self = this;
var abc = 'abc controller2';
}]);
Strict mode will throw error when you make this mistake (any many others). From MDN:
First, strict mode makes it impossible to accidentally create global
variables. In normal JavaScript mistyping a variable in an assignment
creates a new property on the global object and continues to "work"
(although future failure is possible: likely, in modern JavaScript).
Assignments which would accidentally create global variables instead
throw in strict mode:
I would drop the below code into your app above this instantiation (most modern browsers should understand this syntax for debugging). Call window.trackCtrl in every controller constructor and then pop open the console in chrome or firefox and type printCtrls() and you should get a print out of when they were created in order.
window.trackCtrl = (name) => {
var newCtrl = {}
newCtrl.name = name + '_' + performance.now()
window.trackingCtrls = window.trackingCtrls || []
window.trackingCtrls.push(newCtrl)
}
window.printCtrls = () => Array.isArray(window.trackCtrls) ? window.trackingCtrls.forEach(x => console.info(x)) : console.error('trackCtrls not defined')
This will find bugs such as the controllers getting loaded out of order or duplicate copies of your code or libraries getting loaded on the same page. Performance API helps a lot in these situations => https://developer.mozilla.org/en-US/docs/Web/API/Performance/now

Assign promise return value to an iterator (AngularJS)

Iterating over objects in CoffeScript I want to calculate and display a certain value for every entry (number of assets in pool)
Controller function:
archivedBidParts = []
for day in BidService.activeDays(bid) when DateService.isBetween(day, from, to, true, true)
splitBidPart = angular.copy(bid_part)
splitBidPart.hours = BidService.hoursActiveOnDay(day, bid)
splitBidPart.number_of_assets_in_pool = number_of_assets_in_pool(bid)
archivedBidParts.push(splitBidPart)
$scope.data.splitBidParts = sort(archivedBidParts)
Helper function:
number_of_assets_in_pool = (bid) ->
Pool.query().$promise.then(pool_memberships.bind(null, bid)).then((pool) -> pool.pool_memberships.length)
The view:
<tr ng-repeat="bid_part in data.splitBidParts">
...
<td ng-hide="bid_part.holidayName">{{ bid_part.number_of_assets_in_pool }}</td>
Problem:
The helper function returns a promise. When trying to console.log the return value inside the promise (in the last .then()-statement) the right number gets printed out in the console.
Does someone have an idea how to use the return value to be displayed properly?
Thanks in advance
I think you should change the following line in your controller
splitBidPart.number_of_assets_in_pool = number_of_assets_in_pool(bid)
To something like this:
number_of_assets_in_pool(bid).then(function(retval) {
splitBidPart.number_of_assets_in_pool = retval;
});
For your example you could assign to the splitBidPart object in the promise resolution.
If you need to know when all the promises are resolved you will need to collect them and then resolve them with a call to $q.all() (note this can be expensive to perform if there are lots of promises).
Note in the example below all the instances of splitBidPart are lost once the loop is complete so this is not a working stand alone example.
Note also you need to provide $q via injecting it into the controller.
promises = []
for day in BidService.activeDays(bid) when DateService.isBetween(day, from, to, true, true)
splitBidPart = angular.copy(bid_part)
splitBidPart.hours = BidService.hoursActiveOnDay(day, bid)
number_of_assets_in_pool(bid).then (theAnswer) ->
splitBidPart.number_of_assets_in_pool = theAnswer
$q.all(promises).then ->
console.log "all the promises are resolved"
Note that in some cases Angular can deal with promises intelligently and it will 'just work'. Would like to articulate this last point but can't really without digging into the docs.
Does the sort function preserve the binding? Can you try what happens when leaving out the sort?

Difference in accessing variables in views

I've two controllers one is "Upload" which deals with images uploads and other is "Page" whid deals with the creation of pages of CMS now if in my "Upload" controller I load both the models i.e 'image_m' which deals with image upload and "page_m" which deals with the pages creation I've highlighted the relevant code my problem is if I access the variables in the view
$this->data['images'] = $this->image_m->get(); sent by this I can access in foreach loop as "$images->image_title, $images->image_path" etc
But the variable sent by this line ***$this->data['get_with_images'] = $this->page_m->get_no_parents();*** as $get_with_images->page_name, $get_with_images->page_id etc produces given error
A PHP Error was encountered
Severity: Notice
Message: Trying to get property of non-object
Filename: upload/index.php
Line Number: 20
what is the difference between these two access levels one for $image & other for $get_with_images because I can only access its values as $get_with_images
class Upload extends Admin_Controller {
public function __construct() {
parent::__construct();
***$this->load->model('image_m');
$this->load->model('page_m');***
}
public function index($id = NULL) {
//var_dump($this->data['images'] = $this->image_m->get_with_images());
//$this->data['images'] = $this->image_m->get_with_images();
***$this->data['images'] = $this->image_m->get();***
$this->data['subview'] = 'admin/upload/index';
if ($id) {
$this->data['image'] = $this->image_m->get($id);
count($this->data['image']) || $this->data['errors'][] = 'Page Could not be found';
}
$id == NULL || $this->data['image'] = $this->image_m->get($id);
/*this calls the page_m model function to load all the pages from pages table*/
***$this->data['get_with_images'] = $this->page_m->get_no_parents();***
You are not posting all your code so its hard to tell but is it because you used $this-> in the controller, but you haven't done the same thing in the view?
In this case i would recommend not using $this-> because its not necessary. Also its much better to check for errors etc when you call the model so do something like
if ( ! $data['images'] = $this->image_m->get($id) ) {
// Failure -- show an appropriate view for not getting any images
// am showing $data in case you have other values that are getting passed
$this->load->view( 'sadview', $data ); }
else {
// Success -- show a view to display images
$this->load->view( 'awesomeview', $data ); }
so we are saying if nothing came back - the ! is a negative - then show the failure view. Else $data['images'] came back, and it will be passed to the view. note i have not had to use $this-> for anything and it won't be needed in the view.
Would also suggest using separate methods - have one method to show all images and a separate method like returnimage($id) to show an image based on a specific validated $id.
====== Edit
You can access as many models as you want and pass that data to the View. You have a different issue - the problem is that you are waiting until the View to find out - and then it makes it more difficult to figure out what is wrong.
Look at this page and make sure you understand the differences between query results
http://ellislab.com/codeigniter/user-guide/database/results.html
When you have problems like this the first thing to do is make a simple view, and echo out directly from the model method that is giving you problems. Its probably something very simple but you are having to look through so much code that its difficult to discover.
The next thing is that for every method you write, you need to ask yourself 'what if it doesn't return anything?' and then deal with those conditions as part of your code. Always validate any input coming in to your methods (even links) and always have fallbacks for any method connecting to a database.
On your view do a var_dump($get_with_images) The error being given is that you are trying to use/access $get_with_images as an object but it is not an object.
or better yet on your controller do a
echo '<pre>';
var_dump($this->page_m->get_no_parents());
exit();
maybe your model is not returning anything or is returning something but the data is not an object , maybe an array of object that you still need to loop through in some cases.

Resources