Ui.Router Resolve Window.FB after FB Init - angularjs

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

Related

how to inject service into controller using service object name?

I have downloaded AngularJS - ToDo Application from http://www.tutorialspoint.com/angularjs/ and trying to implement same as it is. As I am new in AngularJS, I am stuck somewhere.
In this app AngularJS service " localStorage" is included by object name 'store' in controller. But when I am trying to do this it couldn't work for me.
mainApp = angular.module('mainApp', ['ngRoute']);
mainApp.config(function($routeProvider) {
'use strict';
var routeConfig = {
templateUrl: 'template/add_todo.html',
controller: 'addToDoController',
resolve: {
store: function(todoStorage) {
// Get the correct module (API or localStorage).
return todoStorage.then(function(module) {
return module;
});
}
}
};
$routeProvider
.when('/', routeConfig)
.otherwise({
redirectTo: '/'
});
});
mainApp.factory('todoStorage', function($http, $injector) {
'use strict';
// Detect if an API backend is present. If so, return the API module, else
// hand off the localStorage adapter
return $http.get('/api')
.then(function() {
//return $injector.get('api');
}, function() {
var store = $injector.get('localStorage');
return store;
});
})
mainApp.factory('localStorage', function($q) {
'use strict';
var store = {
todos: [],
insert: function(todo) {
store.todos.push(todo);
console.log(store.todos);
return store.todos;
}
};
return store;
});
mainApp.controller('addToDoController', function($scope, store) {
'use strict';
$scope.addTodo = function() {
var newTodo = {
title: $scope.newTodo.trim(),
completed: false
};
$scope.saving = false;
store.insert(newTodo)
$scope.newTodo = '';
}
});

Lazy loading angular services using require.js

I can lazy load a controller by doing the following,
Step1: Add an additional config...
rootModule.config([
"$controllerProvider", function($controllerProvider) {
rootModule.registerController = $controllerProvider.register;
}
]);
Step2: Define the controller against the registerController defined in step 1
angular.module("rootModule").registerController("authController",
function ($scope, $location, $rootScope, authService) {
$scope.userName = "";
$scope.userPwd = "";
$scope.authenticate = function ()...
$scope.testFunction = function ()...
});
Step3: load the controller during routing by doing this,
rootModule
.config([
'$routeProvider',
function ($routeProvider) {
$routeProvider.when('/',
{
templateUrl: 'templates/Login.html',
resolve: {
load: ["$q", function($q) {
var defered = $q.defer();
require(["Controllers/authController"], function() {
defered.resolve();
});
return defered.promise;
}]
}
}).
Now, the problem is I have a service called "authService", which I would like to lazy load, how to do it? Here is the service...
define(function() {
angular.module("rootModule").service("authService", function ($http) {
return {
/////Something code here//////
});
});
It was very simple in the end, thanks to this great blog written by Dan Wahlin.
To load a service in run time according to the routing, I had to do this...
Step 1: Get a reference to $provide.service() method in my rootModule's (module which contains the routing info) config...
rootModule.config(["$controllerProvider","$provide",
"$controllerProvider", "$filterProvider","$compileProvider", function ($controllerProvider, $provide) {
rootModule.registerController = $controllerProvider.register; //for controllers
rootModule.registerService = $provide.service; //for services
rootModule.registerFilter = $filterProvider.register; //for filters
rootModule.registerDirective = $compileProvider.directive; //for directives
rootModule.registerFactory = $provide.factory; //for factory
}
]);
Step 2: Register the service to be loaded dynamically
define(function() {
angular.module("rootModule").registerService("reviewReportsService", function () {
return {
sampleData: "This is some sample data"
}
});
});
Step 3: Resolve the service script file, to load when the respective route is loaded
when('/ReviewAndSubmit',
{
controller: "reviewAndSubmitController",
templateUrl: "templates/ReviewAndSubmit.html",
resolve: {
load: ["$q", function ($q) {
var defered = $q.defer();
require(["Controllers/reviewAndSubmitController"], function () {
defered.resolve();
});
require(["Services/reviewReportsService"], function () {
defered.resolve();
});
return defered.promise;
}]
}
})
Hope this helps someone....

Angular resolve functions using same api call

I have a resolve object in UI-router working correctly; however my current setup makes two separate API calls to the same source. I don't want to cache the data because it is frequently changing, but I would like both functions to pull from one data api call. Here is my current setup.
Basically one function gets all results and one does some looping through the results to create an array.
resolve: {
recipeResource: 'RecipeResource',
getRecipeData: function($stateParams, recipeResource){
var recipeId = $stateParams.id;
var getData = function(){
var data = recipeResource.get({recipeId: recipeId}).$promise;
return data;
}
return {
recipeId: recipeId,
getData: getData
}
},
recipe: function($stateParams, recipeResource, getRecipeData){
return getRecipeData.getData();
},
subCatArray: function($stateParams, recipeResource, getRecipeData){
var data = getRecipeData.getData().then(function(value){
var ingredients = value.data.ingredients;
var log = [];
angular.forEach(ingredients, function(value, key) {
if(value){
if(value.subCategory){
log.push(value.subCategory);
};
};
}, log);
return $.unique(log.concat(['']));
});
return data;
}
}
You could use nested resolves. First get all entries and then use that resolved data in your subCatArray resolve to do additional filtering.
Then you'll only have one api call.
Please have a look at the demo below or in this jsfiddle.
angular.module('demoApp', ['ui.router'])
.factory('dummyData', function($q, $timeout) {
return {
getAll: function() {
var deferred = $q.defer(),
dummyData = ['test1', 'test2', 'test3', 'test4'];
$timeout(function() {
deferred.resolve(dummyData);
}, 1000);
return deferred.promise
}
}
})
.config(function($urlRouterProvider, $stateProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('main', {
url: '/',
template: '{{all}} {{filtered}}',
resolve: {
'allEntries': function(dummyData) {
return dummyData.getAll().then(function(data) {
return data;
});
},
'filteredEntries': function(allEntries, $filter) {
return $filter('limitTo')(allEntries, 2);
}
},
controller: function($scope, allEntries, filteredEntries) {
$scope.all = allEntries;
$scope.filtered = filteredEntries;
}
})
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.15/angular-ui-router.js"></script>
<div ng-app="demoApp">
<div ui-view=""</div>
</div>

injector error when trying to inject $scope into a service

I'm trying to learn angular by extending a sample app to use firebase. I want to modify the service object being used to call firebase instead of my webserver. I tried to inject $scope into my service object and am now getting an error. What am I doing wrong?
var app = angular.module("myApp", ['ngRoute', 'firebase']);
app.config(function($routeProvider) {
$routeProvider.when('/', {
templateUrl: "templates/home.html",
controller: "HomeController"
})
.when('/settings', {
templateUrl: 'templates/settings.html',
controller: 'SettingsController'
})
.otherwise({ redirectTo: '/' });
});
//services must return objects
app.service('mailService', ['$scope', '$http', '$firebase', function($scope, $http, $firebase) {
var mailRef = new Firebase("https://ng-book-email-client.firebaseio.com/mail");
//var getMail = function() {
// return $http({
// method: 'GET',
// url: '/api/mail'
// });
//};
$scope.email = $firebase(mailRef);
var sendEmail = function(mail) {
//var d = $q.defer();
//$http({
// method: 'POST',
// data: 'mail',
// url: '/api/send'
//}).success(function(data, status, headers) {
// d.resolve(data);
//}).error(function(data, status, headers) {
// d.reject(data);
//});
//return d.promise;
return $scope.email.$add(mail);
};
return {
//getMail: getMail,
sendEmail: sendEmail
};
}]);
app.controller('HomeController', function($scope) {
$scope.selectedMail;
$scope.setSelectedMail = function(mail) {
$scope.selectedMail = mail;
};
$scope.isSelected = function(mail) {
if($scope.selectedMail) {
return $scope.selectedMail === mail;
}
};
});
// directive that builds the email listing
app.directive('emailListing', function() {
var url = "http://www.gravatar.com/avatar/";
return {
restrict: 'EA', // E- element A- attribute C- class M- comment
replace: false, // whether angular should replace the element or append
scope: { // may be true/false or hash. if a hash we create an 'isolate' scope
email: '=', // accept an object as parameter
action: '&', // accept a function as a parameter
isSelected: '&',
shouldUseGravatar: '#', // accept a string as a parameter
gravatarSize: '#'
},
transclude: false,
templateUrl: '/templates/emailListing.html',
controller: ['$scope', '$element', '$attrs', '$transclude',
function($scope, $element, $attrs, $transclude) {
$scope.handleClick = function() {
$scope.action({selectedMail: $scope.email});
};
}
],
// if you had a compile section here, link: wont run
link: function(scope, iElement, iAttrs, controller) {
var size = iAttrs.gravatarSize || 80;
scope.$watch('gravatarImage', function() {
var hash = md5(scope.email.from[0]);
scope.gravatarImage = url + hash + '?s=' + size;
});
iElement.bind('click', function() {
iElement.parent().children().removeClass('selected');
iElement.addClass('selected');
});
}
};
});
app.controller('MailListingController', ['$scope', 'mailService', function($scope, mailService) {
$scope.email = [];
$scope.nYearsAgo = 10;
//mailService.getMail()
//.success(function(data, status, headers) {
// $scope.email = data.all;
//})
//.error(function(data, status, headers) {
//});
$scope.searchPastNYears = function(email) {
var emailSentAtDate = new Date(email.sent_at),
nYearsAgoDate = new Date();
nYearsAgoDate.setFullYear(nYearsAgoDate.getFullYear() - $scope.nYearsAgo);
return emailSentAtDate > nYearsAgoDate;
};
}]);
app.controller('ContentController', ['$scope', 'mailService', '$rootScope', function($scope, mailService, $rootScope) {
$scope.showingReply = false;
$scope.reply = {};
$scope.toggleReplyForm = function() {
$scope.reply = {}; //reset variable
$scope.showingReply = !$scope.showingReply;
console.log($scope.selectedMail.from);
$scope.reply.to = $scope.selectedMail.from.join(", ");
$scope.reply.body = "\n\n -----------------\n\n" + $scope.selectedMail.body;
};
$scope.sendReply = function() {
$scope.showingReply = false;
$rootScope.loading = true;
mailService.sendEmail($scope.reply)
.then(function(status) {
$rootScope.loading = false;
}, function(err) {
$rootScope.loading = false;
});
}
$scope.$watch('selectedMail', function(evt) {
$scope.showingReply = false;
$scope.reply = {};
});
}]);
app.controller('SettingsController', function($scope) {
$scope.settings = {
name: 'harry',
email: "me#me.com"
};
$scope.updateSettings = function() {
console.log("updateSettings clicked")
};
});
error
Error: [$injector:unpr] http://errors.angularjs.org/1.2.14/$injector/unpr?p0=<div ng-view="" class="ng-scope">copeProvider%20%3C-%20%24scope%20%3C-%20mailService
at Error (native)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js:6:450
at https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js:32:125
at Object.c [as get] (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js:30:200)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js:32:193
at c (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js:30:200)
at d (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js:30:417)
at Object.instantiate (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js:31:80)
at Object.<anonymous> (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js:31:343)
at Object.d [as invoke] (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js:30:452) angular.js:9509
The answer is that the $scope shouldn't be injected into services or factories. They're supposed to be reusable and sharable. The right way to achieve the effect is to create a $firebase object inside the service and return a function that can take any variable and scope and $bind them to the firebase object.
Inspiration comes from http://plnkr.co/edit/Uf2fB0?p=info
var app = angular.module("myApp", ['ngRoute', 'firebase']);
//services must return objects
app.service('mailService', ['$http', '$firebase', function($http, $firebase) {
var emailRef = new Firebase("https://ng-book-email-client.firebaseio.com/all");
var emails = $firebase(emailRef);
// uses firebase to bind $scope.email to the firebase mail object
var setEmails = function(scope, localScopeVarName) {
return emails.$bind(scope, localScopeVarName);
};
var sendEmail = function(mail) {
return $scope.email.$add(mail);
};
return {
setEmails: setEmails,
sendEmail: sendEmail
};
}]);
app.controller('MailListingController', ['$scope', 'mailService', function($scope, mailService) {
mailService.setEmails($scope, 'emails');
$scope.nYearsAgo = 10;
$scope.searchPastNYears = function(email) {
var emailSentAtDate = new Date(email.sent_at),
nYearsAgoDate = new Date();
nYearsAgoDate.setFullYear(nYearsAgoDate.getFullYear() - $scope.nYearsAgo);
return emailSentAtDate > nYearsAgoDate;
};
}]);
I think you should not inject $scope to a service, Service can be a common one for implementation /support for several controllers and service shouldn't know about a $scope (controller). But controllers users services to make their work done by passing parameters to a service method.
mailService.sendEmail($scope.reply)
Not sure why you need $scope inside a service here,
$scope.email = $firebase(mailRef);
are you expecting something like this,
this.email = $firebase(mailRef);

AngularJS unit test for directive that queries server returns "Unsatisfied requests"

I have a bookmarking app that takes in a url and automatically extracts a summary. When the client requests the server to add a new bookmark, the server sends back some initial information and initiates a process to extract the summary.
In the angular frontend, I have created directives for adding the bookmark and for managing each item in the bookmarks list. Within the listitem directive there is a checkSummary() method that will poll the server to get the summary.
I am having problems with the unit test for this latter directive. It fails with "Unsatisfied request" but when I log to the console at various points the $http.get() request seems to fire and I can see that $scope variables are updated so I don't really understand why it would fail with this cause. I've checked the answers to many different question but can't really find anything that would give some insight.
The code is the following:
bookmarks.js:
angular.module( 'userpages.bookmarks', [
'ui.router',
'ui.bootstrap',
'ui.validate'
])
.config(function config( $stateProvider ) {
$stateProvider.state( 'userpages.bookmarks', {
url: '/bookmarks',
templateUrl: 'userpages/bookmarks/bookmarks.tpl.html',
controller: 'BookmarksController',
data: { pageTitle: 'Bookmarks' },
});
})
.factory('bookmarksApiResource', ['$http', function ($http) {
var bookmarksUrl = '/api/v1/bookmarks/';
var api = {
getList: function() {
return $http.get( bookmarksUrl )
.then( function(response) { return response.data; });
},
getById: function(id) {
return $http.get( bookmarksUrl + id + '/' )
.then( function(response) { return response.data; });
},
addBookmark: function(articleUrl) {
return $http.post( bookmarksUrl, {article_url: articleUrl})
.then( function(response) { return response.data; });
},
checkSummary: function(id) {
return $http.get( bookmarksUrl + id + '/?fields=summary' )
.then( function(response) { return response.data; });
}
};
return api;
}])
.controller( 'BookmarksController', ['$scope', '$stateParams', 'bookmarksApiResource',
function BookmarksController( $scope, $stateParams, bookmarksApiResource) {
$scope.username = $stateParams.username;
$scope.templates = {
bookmarks: {
toolbar: 'userpages/bookmarks/bookmarks-toolbar.tpl.html',
listitem: 'userpages/bookmarks/bookmarks-listitem.tpl.html',
}
};
$scope.addBookmarkFormCollapsed = true;
$scope.bookmarks = [];
bookmarksApiResource.getList().then( function(data) {
$scope.bookmarks = data;
});
}])
.directive('newBookmark', ['bookmarksApiResource', function(bookmarksApiResource) {
var newBookmark = {
restrict: 'E',
templateUrl: 'userpages/bookmarks/bookmarks-add-form.tpl.html',
replace: true,
link: function($scope, $element, $attrs, $controller) {
$scope.addBookmark = function(articleUrl) {
var newBookmark = bookmarksApiResource.addBookmark(articleUrl);
$scope.bookmarks.push(newBookmark);
};
}
};
return newBookmark;
}])
.directive('bookmarksListitem', ['bookmarksApiResource', '$timeout',
function(bookmarksApiResource, $timeout) {
var listitem = {
restrict: 'E',
templateUrl: 'userpages/bookmarks/bookmarks-listitem.tpl.html',
replace: true,
scope: true,
link: function($scope, $element, $attrs, $controller) {
var checkSummary = function() {
if (!$scope.bookmark.summary_extraction_done) {
bookmarksApiResource.checkSummary($scope.bookmark.id).then(function(data){
if (data.summary_extraction_done) {
$scope.bookmark.summary_extraction_done = data.summary_extraction_done;
$scope.bookmark.summary = data.summary;
} else {
$timeout(checkSummary, 1000);
}
});
}
checkSummary();
}
};
return listitem;
}])
;
and the test is as follows:
bookmarks.spec.js
describe( 'userpages.bookmarks', function() {
var $rootScope, $location, $compile, $controller, $httpBackend;
var $scope, elem;
beforeEach( module( 'userpages.bookmarks', 'ui.router' ) );
... other tests ...
describe('bookmarks-listitem', function() {
var testBookmark;
beforeEach(module('userpages/bookmarks/bookmarks-listitem.tpl.html'));
beforeEach(inject(function(_$rootScope_, _$compile_, _$httpBackend_){
$rootScope = _$rootScope_;
$compile = _$compile_;
$httpBackend = _$httpBackend_;
testBookmark = {
id: 1,
title: 'Title of our first article',
url: 'http://www.example.com/some/article',
summary_extraction_done: false,
summary: '',
};
}));
it('when summary_extraction_done = false, checkSummary() should call the server and update the summary with the response', function () {
$httpBackend.when('GET', '/api/v1/bookmarks/1/?fields=summary').respond(200, {
summary_extraction_done: true,
summary: 'This is the summary for article 1.'
});
$httpBackend.expect('GET', '/api/v1/bookmarks/1/?field=summary');
$scope = $rootScope.$new();
$scope.bookmark = testBookmark;
elem = $compile('<bookmarks-listitem></bookmarks-listitem>')($scope);
$scope.$digest();
$httpBackend.flush();
expect($scope.bookmark.summary_extraction_done).toBe(true);
expect($scope.bookmark.summary).toBe('This is the summary for article 1.');
});
});
});
And the test results are:
Firefox 27.0.0 (Mac OS X 10.9) userpages.bookmarks bookmarks-listitem when summary_extraction_done = false, checkSummary() should call the server and update the summary with the respone FAILED
Error: Unsatisfied requests: GET /api/v1/bookmarks/1/?field=summary in .../frontend/vendor/angular-mocks/angular-mocks.js (line 1486)
createHttpBackendMock/$httpBackend.verifyNoOutstandingExpectation#.../frontend/vendor/angular-mocks/angular-mocks.js:1486
createHttpBackendMock/$httpBackend.flush#.../frontend/vendor/angular-mocks/angular-mocks.js:1464
#.../frontend/src/app/userpages/bookmarks/bookmarks.spec.js:6
Any suggestions would be helpful.
If you want to see what url httpBackend is using you can use following code to debug it.
$httpBackend.when("GET", new RegExp('.*')).respond(function(method, url, data) {
console.log("URL:",url);
});
Typo here:
$httpBackend.expect('GET', '/api/v1/bookmarks/1/?field=summary');
You are missing an s on the end of field.

Resources