firebase with angular router never reaches controller - angularjs

I want to make sure I get the data from firebase before my app is loaded. I am using this code but it's not working: the program never reach the code deferred.resolve(); and the appCtrl does not run...
Any idea?
appCtrl :
.controller('AppCtrl', function($scope, $ionicModal, $timeout,$firebaseAuth,$state,myWordsListServ) {
$scope.myWordsList = myWordsListServ.contacts;
console.log("myWordsList" , $scope.myWordsList);
})
myWordsListServ:
.factory('myWordsListServ', function($firebase, $q) {
return {
contacts: null,
promiseToHaveMyLists: function() {
var deferred = $q.defer();
var firebaseMainRef = new Firebase("https://myApp.firebaseio.com");
var authData = firebaseMainRef.getAuth();
if(authData){
this.contacts = $firebase(new Firebase("https://myApp.firebaseio.com/users/" + authData.uid));
this.contacts.$on('value', function(loadedData) {
deferred.resolve();
});
}
else{
deferred.resolve();
}
return deferred.promise;
}
};
})
part of my app.js:
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('app', {
url: "/app",
abstract: true,
templateUrl: "templates/menu.html",
controller: 'AppCtrl',
resolve : {
myWordsList : function(myWordsListServ) {
return myWordsListServ.promiseToHaveMyLists();
}
},
})

You seem to be using a pre-1.0 version of AngularFire:
this.contacts = $firebase(new Firebase("https://myApp.firebaseio.com/users/" + authData.uid));
this.contacts.$on('value', function(loadedData) {
deferred.resolve();
});
The $firebase class was an intermediate object and (if I recall correctly) would never find events. What you're probably looking to do is:
this.contacts = $firebase(new Firebase(...)).$asObject();
This returns a $firebaseObject which does fire events.
You might want to upgrade to AngularFire 1.0, in which case you'd get the same result with:
this.contacts = $firebaseObject(new Firebase(...));

Related

Jasmine testing controller from wrong ui-router state

I have a page where everything works fine. However, my test for this page's controller FeeRuleCtrl, after it tests the code of said controller, goes on and starts testing the controller of a different state. Here's my app.js:
$stateProvider
.state('root', {
url: "/",
templateUrl: "<%= Rails.application.routes.url_helpers.client_side_path('admin/fee_suites/root') %>",
controller: 'RootCtrl',
resolve: {
feeSuites: function(FeeSuiteCrud, FeeSuite){
console.log('here');
var feeCrud = new FeeSuiteCrud(FeeSuite);
var promise = feeCrud.query();
return promise.then(function(response){
return response;
});
}
}
})
.state('fee-rule', {
abstract: true,
controller: 'FeeRuleCtrl',
template: "<ui-view/>",
resolve: {
feeTypes: function(FeeSuiteCrud, FeeType){
var feeCrud = new FeeSuiteCrud(FeeType)
var promise = feeCrud.query();
return promise.then(function(response){
return response;
})
},
feeSuites: function(FeeSuiteCrud, FeeSuite){
var feeCrud = new FeeSuiteCrud(FeeSuite);
var promise = feeCrud.query();
return promise.then(function(response){
return response;
});
}
}
})
.state('fee-rule.new', {
url: '/new',
controller: 'NewCtrl',
templateUrl: "<%= Rails.application.routes.url_helpers.client_side_path('admin/fee_suites/feeRule.html') %>",
data: { title: 'Add a New Fee Rule' }
})
.state('fee-rule.edit', {
url: "/edit/:id",
controller: 'EditCtrl',
templateUrl: "<%= Rails.application.routes.url_helpers.client_side_path('admin/fee_suites/feeRule.html') %>",
data: { title: 'Edit Fee Rule' },
resolve: {
feeRule: function(FeeSuiteCrud, FeeRule, $stateParams){
var feeCrud = new FeeSuiteCrud(FeeRule);
var promise = feeCrud.get($stateParams.id)
return promise.then(function(response){
return response;
});
}
}
});
I have an abstract state, fee-rule, because both the new and edit states share most of the same functionality.
When I go to the page's address, <host>/admin/fee_suites/new, I inspect the network tab and there are 4 server calls made:
api/v3/fee_types
api/v3/fee_suites
api/v3/fee_suites/8?association=fee_rules
api/v3/fee_types/9?association=fee_parameters
The first 2 are resolves in the fee-rule state. I take care of this like so in the test:
beforeEach(function(){
module(function($provide){
$provide.factory('feeSuites', function(FeeSuite){
feeSuite = new FeeSuite({
id: 8,
site_id: 9,
active: true
});
return [feeSuite];
});
$provide.factory('feeTypes', function(FeeType){
feeType = new FeeType({
id: 9,
name: 'Carrier Quotes',
value: 'carrier_quotes'
});
return [feeType];
});
});
inject(function($injector){
$rootScope = $injector.get('$rootScope');
$controller = $injector.get('$controller');
$httpBackend = $injector.get('$httpBackend');
scope = $rootScope.$new();
$controller("FeeRuleCtrl", {
'$scope': scope
});
});
});
The last 2 server calls are made inside FeeRuleCtrl. I test them like so:
beforeEach(function(){
var JSONResponse = {"master":[{"id":29,"fee_suite_id":8,"fee_parameter_id":1,"name":"American Express Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":30,"fee_suite_id":8,"fee_parameter_id":2,"name":"Discover Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":31,"fee_suite_id":8,"fee_parameter_id":3,"name":"MasterCard Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":32,"fee_suite_id":8,"fee_parameter_id":4,"name":"Visa Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"}]};
$httpBackend.expectGET('/api/v3/fee_suites/8?association=fee_rules').respond(JSONResponse);
JSONResponse = {"master":[{"id":25,"fee_type_id":9,"name":"UPS Published Quote","value":"ups_published_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":26,"fee_type_id":9,"name":"FedEx Published Quote","value":"fedex_published_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":27,"fee_type_id":9,"name":"UPS Negotiated Quote","value":"ups_negotiated_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":28,"fee_type_id":9,"name":"FedEx Negotiated Quote","value":"fedex_negotiated_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"}]};
$httpBackend.expectGET('/api/v3/fee_types/9?association=fee_parameters').respond(JSONResponse);
$httpBackend.flush();
});
it('should set currentFeeRuleNum', function(){
expect(scope.FeeSuite.currentFeeRuleNum).toEqual(4);
});
When I run my test I get the following error:
Error: Unexpected request: GET /api/v3/fee_suites/
I know it is coming from root state's resolve function feeSuites because the test also prints to the console log the word 'here'.
I cannot figure out why it seems like the test doesn't stop and starts testing the RootCtrl in the root state. Could it have anything to do with the fact that state fee-rule is abstract? Also NewCtrl is defined but it is empty.
After some more googling with different keywords, turns out in my test I need to mock the $state variable inside FeeRuleCtrl. That fixed the problem.

Ui.Router Resolve Window.FB after FB Init

Question
Hi I want my stateProvider to wait until Facebook is initialized. Then it should assign the $window.FB var to my controller.
I took this approach as an orientation:
How to detect when facebook's FB.init is complete
App.js
module.config(['$urlRouterProvider', '$stateProvider' ,function($urlRouterProvider, $stateProvider) {
$stateProvider
.state('share',{
url:'/share',
templateUrl:'modules/Share/Share.html',
controller: 'ShareCtrl',
controllerAs:'shareCtrl',
data: {
auth: "LoggedIn"
},
resolve: {
FB: ['$window', function($window){
return $window.FB.getUserID(function(response){
return 'test';
// return $window.FB;
});
}]
}
});
}]);
...
module.run(['$rootScope', '$state', 'authUserService', '$FB', function($rootScope, $state, authUserService, $FB) {
$FB.init('475092975961783', './Modules/Social/FB-Channel.html');
}]);
My Facebook Init functions is basically the same as here:
https://github.com/djds4rce/angular-socialshare/blob/master/angular-socialshare.js
Besides that I pass an url for the channel.html (which I think is not even required anymore, Is channelUrl parameter for Facebook init() deprecated?).
Controller
module.controller('ShareCtrl', ['$scope', 'myFacebookService', 'FB', function($scope, myFacebookService, FB) {
this.rootUrl = 'modules/Share/';
console.log(FB);
// console.log(myFacebookService.getUser(FB));
}]);
The variable never gets resolved and the view never gets loaded. window.FB.getUserID() in the browser console returns me my FacebookAppID.
The trick is to modify the library and add a promise. When it is resolved the object gets loaded.:
angular.module('af.angular-socialshare', [])
.factory('$FB',['$window', '$q', function($window, $q){
// data stored here exists only once per app
var loaded = $q.defer();
return {
init: function(fbId, channelUrl){
if(fbId){
this.fbId = fbId;
$window.fbAsyncInit = function() {
FB.init({
appId: fbId,
channelUrl: channelUrl,
status: true,
xfbml: true
});
loaded.resolve($window.FB);
};
(function(d){
var js,
id = 'facebook-jssdk',
ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) {
return;
}
js = d.createElement('script');
js.id = id;
js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
ref.parentNode.insertBefore(js, ref);
}(document));
}
else{
throw("FB App Id Cannot be blank");
}
},
getFB: function(){
return loaded.promise;
}
};
}])
And then resolve the FB Object
$stateProvider
.state('share',{
url:'/share',
templateUrl:'modules/Share/Share.html',
controller: 'ShareCtrl',
controllerAs:'shareCtrl',
data: {
auth: "LoggedIn"
}
,
resolve: {
FB: ['$FB', function($FB){
return $FB.getFB();
}]
}
});

Resolve not resolved before loading controller

I am trying to fetch the value from server side before any controller loads. I am using resolvers to achieve this. Since fetched value needs to be visible to all controllers I extended routeConfig on the following way:
'use strict';
angular.module('myApp', [.....]).
config(['$routeProvider', function ($routeProvider) {
var universalResolves = {
user: function(User, $q, $rootScope) {
var deffered = $q.defer();
User.query({},
function (users) {
deffered.resolve(
$rootScope.activeUser = users[0]
)
}, function(){
deffered.reject();
}
);
return deffered.$promise;
}
};
var customRouteProvider = angular.extend({}, $routeProvider, {
when: function(path, route) {
route.resolve = (route.resolve) ? route.resolve : {};
angular.extend(route.resolve, universalResolves);
$routeProvider.when(path, route);
return this;
}
});
customRouteProvider.when('/users', {
templateUrl: 'partials/users.html',
controller: 'UserController'
});
customRouteProvider.otherwise({redirectTo: '/home'});
}]);
But when I try to print activeUser from the controller I am getting 'undefined'.
.controller('UserController', ['$scope', function ($scope) {
console.log($scope.activeUser.id);
.....
};
Here I am getting the following error:
TypeError: Cannot read property 'id' of undefined.
Why the value is not resolved before loading controller?
There is no such property $promise of deferred object, it is promise:
user: function(User, $q, $rootScope) {
var deffered = $q.defer();
User.query({}, function(users) {
deffered.resolve($rootScope.activeUser = users[0]);
}, function() {
deffered.reject();
});
return deffered.promise;
// ^ don't put $ here
}
Also it's better to inject resolved user to controller then using $rootScope:
.controller('UserController', ['$scope', 'user', function ($scope, activeUser) {
console.log(activeUser.id);
};
An finally just for better codding style, this assignment
$rootScope.activeUser = users[0]
is a little confusing. It's more readable:
$rootScope.activeUser = users[0];
deffered.resolve(users[0]);

Prevent multiple ajax calls when re-using a controller/factory

just starting out really with Angular and need some advice regarding preventing repeated ajax requests for the same data when re-using a controller with multiple view.
So I have say 6 views all referencing the same controller but different views
app.js
(function() {
var app = angular.module('myApp', ['ngRoute','ui.unique']);
app.config(function ($routeProvider) {
// Routes
$routeProvider
.when('/',
{
controller: 'SamplesController',
templateUrl: 'app/views/home.html'
})
.when('/view2/',
{
controller: 'SamplesController',
templateUrl: 'app/views/main.html'
})
.when('/view3/:rangeName',
{
controller: 'SamplesController',
templateUrl: 'app/views/samples.html'
})
.when('/view4/:rangeName',
{
controller: 'SamplesController',
templateUrl: 'app/views/samples.html'
})
.when('/view5/',
{
controller: 'SamplesController',
templateUrl: 'app/views/basket.html'
})
.when('/view6/',
{
controller: 'SamplesController',
templateUrl: 'app/views/lightbox.html'
})
.otherwise({ redirectTo: '/' });
});
}());
samplesController.js
(function() {
var SamplesController = function ($scope, SamplesFactory, appSettings, $routeParams) {
function init() {
// back function
$scope.$back = function() {
window.history.back();
};
// app settings
$scope.settings = appSettings;
// samples list
SamplesFactory.getSamples()
.success(function(data){
var returnSamples = [];
for (var i=0,len=data.length;i<len;i++) {
if (data[i].range === $routeParams.rangeName) {
returnSamples.push(data[i]);
}
}
$scope.samples = returnSamples;
})
.error(function(data, status, headers, config){
// return empty object
return {};
});
// variables for both ranges
$scope.rangeName = $routeParams.rangeName;
// click to change type
$scope.populate = function(type) {
$scope.attributeValue = type;
};
};
init();
};
SamplesController.$inject = ['$scope','SamplesFactory', 'appSettings', '$routeParams'];
angular.module('myApp').controller('SamplesController', SamplesController);
}());
samplesFactory.js
(function () {
var SamplesFactory = function ($http) {
var factory = {};
factory.getSamples = function() {
return $http.jsonp('http://www.website.com/app/index.php?callback=JSON_CALLBACK');
};
return factory;
};
SamplesFactory.$inject = ['$http'];
angular.module('myApp').factory('SamplesFactory', SamplesFactory);
}());
So with this - every time a new view is loaded the ajax request is made again - how would I re-purpose to have only a single request happen?
As always thanks in advance
Carl
UPDATE: Answer marked below but I also had success by changing the "cache" config item/property (whatever its called) to true in the jsonp request
return $http.jsonp('http://www.website.com/app/index.php?callback=JSON_CALLBACK',{cache: true});
You could change your factory in this way:
(function () {
var SamplesFactory = function ($http) {
var factory = {},
samples = $http.jsonp('http://www.website.com/app/index.php?callback=JSON_CALLBACK');
factory.getSamples = function() {
return samples;
};
return factory;
};
SamplesFactory.$inject = ['$http'];
angular.module('myApp').factory('SamplesFactory', SamplesFactory);
}());
Now getSamples() returns a promise that you should manage in your controllers.

One controller, two views - stop original controller from reloading

I have an ng-click calling $scope.get_post_Range which is to return a range of database queries. The problem is that after it is completed, My original controller main function runs again ('post_View_Ctrl'). This returns the entire database collection. How do i stop this? The strange thing is that if I click the link again, it runs the function and not the original main controller.
var blogAppViewController = angular.module ('blogAppViewController', []);
blogAppViewController.controller('post_View_Ctrl', function ($scope, $http, $routeParams, $location) {
$scope._idList=[];
var globalPAGECount=0;
$http.get('/allposts').success(function(input_data) {
$scope.posts = input_data;
var posts_Length=$scope.posts.length;
$scope.post1=input_data[0];
$scope.post2=input_data[1];
$scope.post3=input_data[2];
$scope.post4=input_data[3];
$scope.post5=input_data[4];
$scope._idList.push({"Page":1, "_id":$scope.posts[0]._id});
var count=0;
var PageCount=2;
for (var each in $scope.posts){
count++;
if (count==5){
$scope._idList.push({"Page": PageCount, "_id": $scope.posts[each]._id});
count=0;
PageCount++;
}
}
$scope._idList.push({"Page": PageCount, "_id":$scope.posts[posts_Length-1]._id});
var listLength = $scope._idList.length;
// console.log($scope._idList[listLength-1]);
if($scope._idList[listLength-1]._id == $scope._idList[listLength-2]._id ){
$scope._idList.pop();
}
console.log($scope._idList);
$scope.a=globalPAGECount+1;
$scope.a_id=$scope._idList[globalPAGECount]['_id'];
$scope.b=globalPAGECount+2;
$scope.b_id=$scope._idList[globalPAGECount+1]['_id'];
$scope.c=globalPAGECount+3;
$scope.c_id=$scope._idList[globalPAGECount+2]['_id'];
$scope.d=globalPAGECount+4;
$scope.d_id=$scope._idList[globalPAGECount+3]['_id'];
$scope.e=globalPAGECount+5;
$scope.e_id=$scope._idList[globalPAGECount+4]['_id'];
});
$scope.get_post_Range = function(high,low) {
console.log(high, low);
console.log("At least enters here");
$http.get('/get_posts/high/' + high + '/low/' + low).success(function (returned) {
$scope.posts = returned;
console.log("success");
});
};
$scope.selectSearch = function(input) {
$scope.Search1 = input;
if ($scope.Search1 == 'Title'){
$scope.searchTitle = true;
$scope.searchContent = false;
$scope.search_Hits=null;
}
if ($scope.Search1 == 'Post Content'){
$scope.searchTitle = false;
$scope.searchContent = true;
$scope.search_Hits=null;
}
};
$scope.searchDatabase = function(type, input) {
if (type === 'Title') {
$http.post('/search_post_Titles', {title: input}).success(function (response) {
$scope.search_Hits = response;
});
}
if (type === 'Post Content') {
$http.post('/search_post_Content', {post: input}).success( function (response) {
$scope.search_Hits = response;
});
}
};
});
///////////
///////////
var blogApp = angular.module('blogApp', [
'ngRoute',
'ngResource',
'blogAppViewController',
'blogSingleViewController',
'blognewEntryController',
]);
blogApp.config(['$routeProvider',
function ($routeProvider) {
$routeProvider.
when('/' , {
templateUrl: 'partials/all-posts.html',
controller: 'post_View_Ctrl'
}).
when('/post/:title' , {
templateUrl: 'partials/single-post.html',
controller: 'single_View_Ctrl'
}).
when('/get_posts/high/:high/low/:low' , {
templateUrl: 'partials/all-posts.html',
controller: 'post_View_Ctrl'
}).
when('/new_entry' , {
templateUrl: 'partials/new_Entry.html',
controller: 'entry_View_Ctrl'
}).
otherwise({
redirectTo: '/'
});
}]);
Controllers are design to execute only once per navigation change so there is no obvious reason to execute twice here.
Maybe you are declaring the controller both at $routeProvider level (as you shown) and at html level (see this question)
If it didn't solve the problem, a plunker will be needed here.

Resources