injector error when trying to inject $scope into a service - angularjs

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);

Related

Hi I am learning about angularjs. I want to get the TEMP from one json and display that value in my component. I dont konw what I am doing wrong

I m learning js fetch.
I am trying to get the value of temp from a respons and display that value in my component.js
weather.component.js
```
myApp.component('weatherComponent', {
template:"<p>Weather: {{vm.weather}}</p>",
controllerAs: 'vm',
controller: function WeatherController($scope,$element, $attrs) {
vm = this;
var apikey = "";
var city = "London";
fetch("https://api.openweathermap.org/data/2.5/weather?q="+city+"&appid=" + apikey)
.then(function(response) {
vm.weather = response.main.temp;
console.log("weather: "+ vm.weather);
return response.json();
})
.then(function(myJson) {
console.log(myJson);
});
}
});
```
index.html
```
<weather-component></weather-component>
<script src="utils.js"></script>
<script src="weather.component.js"></script>
```
In utils it is initialized var myApp = angular.module('myApp', []);
The API Response url: https://openweathermap.org/current
To get the element from the json:
controller.weather = response.weather[0].description;
The way to reolve this:
create a service, so I could get the data & change the component.
I discard the idea about to use "fetch".
weather.service.js
```myApp.service('weatherService', function($http, $q) {
this.getWeather = function (apikey, city) {
var deferred = $q.defer();
// HTTP call
$http.get("https://api.openweathermap.org/data/2.5/weather?q="+city+"&appid=" + apikey)
.then(function(response) {
deferred.resolve(response.data);
}, function(response){
deferred.reject(response);
});
return deferred.promise;
};
});
```
weather.component.js
```
myApp.component('weatherComponent', {
template:"<p>Weather: {{weather}}</p>",
controllerAs: 'vm',
controller: function WeatherController($scope, weatherService, $element, $attrs) {
var apikey = "b441194a96d8642f1609a75c1441793f";
var city = "London";
var controller = $scope;
controller.weather = "None";
weatherService.getWeather(apikey, city).then(function(response) {
controller.weather = response.weather[0].description;
});
}
});
```

How to call MVC Web API Controller after AngularJS popup completes

I have an AngularJS module with code that calls a modal popup. I also have code that calls an MVC Web API controller method. Both of these functions work independently of one another right now. What I would like to happen is after the user clicks the OK button on the modal, I want to get the value of the modal text box and send it to the API controller as a parameter. The code I have so far is below:
app.js:
(function () {
'use strict';
var app = angular.module("CRNApp", ['ui.bootstrap','trNgGrid']);
var MainController = function ($scope, $http, $log, $uibModal) {
$scope.showGrid = false;
$scope.showPolicyScreen = false;
$scope.CRNViewModel = {
policyId: 0
};
$scope.openPolicyId = function () {
$uibModal.open({
templateUrl: 'templates/popupGetPolicy.cshtml',
backdrop: false,
windowClass: 'modal',
controller: function ($scope, $uibModalInstance, $log, CRNViewModel) {
$scope.CRNViewModel = CRNViewModel;
$scope.submit = function () {
}
$scope.cancel = function () {
$uibModalInstance.dismiss('cancel');
};
},
resolve: { CRNViewModel: function () { return $scope.CRNViewModel; } }
});//end of modal.open
}; // end of scope.open fu
$scope.policyLookup = function (policyNumber) {
$scope.loading = true;
$scope.CRNViewModel.policyId = policyNumber; //"WCZ25999"
$http.post("/api/Policy"
, $scope.CRNViewModel
, { header: { 'Content-Type': 'application/json' } })
.then(function (response) {
$scope.policy = response.data;
$scope.loading = false;
$scope.showGrid = false;
$scope.showPolicyScreen = true;
})
.catch(function (error) {
console.log(error);
$scope.loading = false;
$scope.showGrid = false;
});
};
};
app.controller("MainController", MainController);
}());
The MVC API Controller method:
// POST: api/Policy
public IHttpActionResult Post([FromBody]CRNViewModel policy)
{
CRNViewModel _crnVM = new CRNViewModel();
IConditionalRenewalNotices _crn = new ConditionalRenewalNoticesRepository();
_crnVM = _crn.GetPolicyByPolicyId(policy.PolicyId);
return Json(_crnVM);
}
Return the textbox value when you close the $uibModalInstance instance and then add a callback for the modal result:
var modal = $uibModal.open({
templateUrl: 'templates/popupGetPolicy.cshtml',
backdrop: false,
windowClass: 'modal',
controller: function($scope, $uibModalInstance, $log, CRNViewModel) {
$scope.CRNViewModel = CRNViewModel;
$scope.submit = function () {
// pass in the value you want to return
$uibModalInstance.close('WCZ25999');
}
$scope.cancel = function() {
$uibModalInstance.dismiss('cancel');
};
},
resolve: { CRNViewModel: function() { return $scope.CRNViewModel; } }
});
modal.result.then(function (value) {
$scope.policyLookup(value);
});

Avoid repetition in Angular controller

I'm an angular newbie and I'm writing an Ionic app.
I finished my app and am trying to refactor my controller avoiding code repetition.
I have this piece of code that manages my modal:
angular.module('starter')
.controller('NewsCtrl', function($scope, content, $cordovaSocialSharing, $timeout, $sce, $ionicModal){
$scope.news = content;
content.getList('comments').then(function (comments) {
$scope.comments = comments;
});
$scope.addComment = function() {
};
$scope.shareAnywhere = function() {
$cordovaSocialSharing.share("Guarda questo articolo pubblicato da DDay", "Ti stanno segnalando questo articolo", content.thumbnail, "http://blog.nraboy.com");
};
$ionicModal.fromTemplateUrl('templates/comments.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.modal = modal;
});
$scope.showComment = function() {
$scope.modal.show();
};
// Triggered in the login modal to close it
$scope.closeComment = function() {
$scope.modal.hide();
};
$scope.$on('modal.shown', function() {
var footerBar;
var scroller;
var txtInput;
$timeout(function() {
footerBar = document.body.querySelector('#commentView .bar-footer');
scroller = document.body.querySelector('#commentView .scroll-content');
txtInput = angular.element(footerBar.querySelector('textarea'));
}, 0);
$scope.$on('taResize', function(e, ta) {
if (!ta) return;
var taHeight = ta[0].offsetHeight;
if (!footerBar) return;
var newFooterHeight = taHeight + 10;
newFooterHeight = (newFooterHeight > 44) ? newFooterHeight : 44;
footerBar.style.height = newFooterHeight + 'px';
scroller.style.bottom = newFooterHeight + 'px';
});
});
});
I have added this same code in 6 controllers.
Is there a way to avoid the repetition?
Probably what you are looking for is an angular service. This component is a singleton object, that you inject in every controller you need to execute this code.
Angular Services
Regards,
Below is an example of a service I created to retrieve address data from a Json file. Here is the working Plunk. http://plnkr.co/edit/RRPv2p4ryQgDEcFqRHHz?p=preview
angular.module('myApp')
.factory('addressService', addressService);
addressService.$inject = ['$q', '$timeout', '$http'];
function addressService($q, $timeout, $http) {
var addresses = [];
//console.log("Number of table entries is: " + orders.length);
var promise = $http.get('address.data.json');
promise.then(function(response) {
addresses = response.data;
// console.log("Number of table entries is now: " + orders.length);
});
return {
GetAddresses: getAddresses
};
function getAddresses() {
return $q(function(resolve, reject) {
$timeout(function() {
resolve(addresses);
}, 2000);
});
}
}
Here's an example of how I added dependencies for it and another service to my controller (This is NOT the only way to do dependency injection, but is my favorite way as it is easier to read). I then called my addressService.GetAddresses() from within my controller.
var app = angular.module('myApp', ['smart-table']);
app.controller('TableController', TableController);
TableController.$inject = [ "orderService", "addressService"];
function TableController( orderService, addressService) {
addressService.GetAddresses()
.then(function(results) {
me.addresses = results;
// console.log(me.addresses.length + " addresses");
},
function(error) {})
.finally(function() {
me.loadingAddresses = false;
});
});}
I also had to include my .js tag in a script element on my index.html.
<script src="addressdata.service.js"></script>

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.

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