I've multiple controllers in my application, where I have some duplicate code like:
$scope.alert = null;
$scope.addAlert = function (message) {
$scope.alert = { type: 'danger', msg: message };
};
$scope.clearAlerts = function () {
$scope.alert = null;
};
What is the recommended way sharing these scope functions and variables in AngularJS? Using controller inheritance?
Create a one controller and then place common methods inside that controller scope. So that you can use that scope anywhere else and get access to method inside controller.
Controller
app.controller('commonCtrl', function($scope) {
$scope.alert = null;
$scope.addAlert = function(message) {
$scope.alert = {
type: 'danger',
msg: message
};
};
$scope.clearAlerts = function() {
$scope.alert = null;
};
});
Thereafter use scope of this controller by inject it using $controller, and then inside curly brace you could assign common controller scope to the current scope of controller.
Utilization of Common Controller
app.controller('testCtrl', function($scope, $controller) {
//inject comomon controller scope to current scope ,
//below line will add 'commonCtrl' scope to current scope
$controller('commonCtrl', { $scope: $scope });
//common controller scope will be available from here
});
Or more precise way would be using common sharable service, that exposed two method and alert data, you can use this service method by injecting service name inside your controller.
Service
app.service('commonService', function($scope) {
this.alert = null;
this.addAlert = function(message) {
this.alert = {
type: 'danger',
msg: message
};
};
this.clearAlerts = function() {
this.alert = null;
};
});
Utilization of service inside Controller
app.controller('testCtrl', function($scope, commonService) {
console.log(commonService.alert);
commonService.addAlert("Something");
console.log("Updated Alert" + commonService.alert);
});
Hope this has cleared your concept, Thanks.
My own solution for this use case was to define a type of Observer Pattern.
The code was structured in the following way:
var app = angular.module('testModule', []);
app.factory('alertService', ['$timeout', function($timeout){
var alertListeners = [];
this.register = function (listener) {
alertListeners.push(listener);
};
this.notifyAll = function (data) {
for (// each listener in array) {
var listenerObject = alertListeners[i];
try { // do not allow exceptions in individual listeners to corrupt other listener processing
listenerObject.notify(data);
} catch(e) {
console.log(e);
}
}
};
}]).
directive('myAlerts', ['alertService', function(alertService){
var alertDirectiveObserver = function($scope, alertService) {
this.notify = function(data) {
/*
* TO DO - use data to show alert
*/
};
/*
* Register this object as an event Listener. Possibly supply an event key, and listener id to enable more resuse
*/
alertService.register(this);
$scope.on('$destroy', function() {
alertService.unregister(// some listener id);
});
};
return {
restrict: 'A',
template: '<div ng-class="alertClass" ng-show="alertNeeded">{{alertMessage}}</div>',
controller: ['$scope', 'alertService', alertDirectiveObserver],
link: function(scope){
}
}
}]).
controller('alertShowingController', ['$scope', 'alertService', function($scope, alertService){
alertService.notifyAll({'warning', 'Warning alert!!!'})
]);
The alertShowingController is a simple example of how all controllers can simply inject the alertService and generate an event.
My own implementation is more elaborate in that it uses separate event keys to allow the controllers to generate other event notifications.
I could then define a single div that was in a fixed position at the top of the page that would dispay bootstrap alerts.
<div my-alerts ng-repeat="alert in alertList" type="{{alert.type}}" close="closeAlert(alertList, $index)">{{alert.msg}}</div>
Related
How can I access $scope data from view to my factory in angularjs? I can access $scope.items from my controller, but when I need to use it in my factory to use the data and generate a pdf I cannot access it.
angular.module('myApp', [])
.controller('myCtrl', function($scope, $http, testFactory) {
$scope.link = "http://localhost:3450/loading.html";
testFactory.all().then(
function(res){
$scope.link = res;
},
function(err){
console.log(err);
}
);
})
.factory('testFactory', function($q){
var pdfInfo = {
content: [
//data should be here...
]
};
var link = {};
function _all(){
var d = $q.defer();
pdfMake.createPdf(pdfInfo).getDataUrl(function(outputDoc){
d.resolve(outputDoc);
});
return d.promise;
}
link.all = _all;
return link;
});
I used factory when I click the generate button from my view, it will wait until the pdf is generated. Coz when I did not do it this way before, I need to click the button twice just to get the pdf generated.
You can just pass the data to your factory as a
function parameter.
angular.module('myApp', [])
.controller('myCtrl', function($scope, $http, testFactory) {
var pdfInfo = {
content: $scope.items
};
$scope.link = "http://localhost:3450/loading.html";
testFactory.all(pdfInfo).then(
function(res) {
$scope.link = res;
},
function(err) {
console.log(err);
}
);
})
.factory('testFactory', function($q) {
var link = {};
function _all(pdfInfo) {
var d = $q.defer();
pdfMake.createPdf(pdfInfo).getDataUrl(function(outputDoc) {
d.resolve(outputDoc);
});
return d.promise;
}
link.all = _all;
return link;
});
I did it. I forgot to send the $scope.items to my factory. So what i did is I added testFactory.all($scope.items) in my controller instead of just plain testFactory.all().
Then in my factory,
I used function _all(value), so I can used the values passed by the views through controller. I am not sure if this is the proper way, but it works. Please suggest good practice if you have.
It is a bad practice to move around $scope to other services, as they may change it and effect your controller logic. It will make a coupling between controllers to other services.
If your factory requires data from the controller, it is better to just pass those parameters to the factory's function.
EDIT: I see you managed to do that, and yes - passing $scope.items is the preferred way (and not, for example, passing $scope).
I have two controllers and in one of them I declared a $scope variable that I would like visible in the second controller.
First controller
app.controller('Ctrl1', function ($scope) {
$scope.variable1 = "One";
});
Second Controller
app.controller('Ctrl2', function ($scope, share) {
console.log("shared variable " + share.getVariable());
});
I researched the best Angular approach and I found that is the use of service. So I added a service for Ctrl1
Service
.service('share', function ($scope) {
return {
getVariable: function () {
return $scope.variable1;
}
};
});
This code return this error:
Unknown provider: $scopeProvider <- $scope <- share
So my question is: is possible share $scope variable between controllers? Is not the best Angular solution or i'm missing something?
Sorry for my trivial question but I'm an Angular beginner.
Thanks in advance
Regards
Yes, it is possible to share the scope variables between controllers through services and factory.But, the $scope variables are local to the controller itself and there is no way for the service to know about that particular variable.I prefer using factory, easy and smooth as butter. If you are using the service of factory in a separate file you need to include the file in index.html
app.controller('Ctrl1', function ($scope,myService,$state) {
$scope.variable1 = "One";
myService.set($scope.variable1);
$state.go('app.thepagewhereyouwanttoshare');//go to the page where you want to share that variable.
});
app.controller('Ctrl2', function ($scope, myService) {
console.log("shared variable " + myService.get());
});
.factory('myService', function() {
function set(data) {
products = data;
}
function get() {
return products;
}
return {
set: set,
get: get
}
})
You cannot inject $scope dependency in service factory function.
Basically shareable service should maintain shareable data in some object.
Service
.service('share', function () {
var self = this;
//private shared variable
var sharedVariables = { };
self.getSharedVariables = getSharedVariables;
self.setVariable = setVariable;
//function declarations
function getSharedVariables() {
return sharedVariables;
}
function setVariable() {
sharedVariables[paramName] = value;
}
});
Controller
app.controller('Ctrl1', function ($scope, share) {
$scope.variable1 = "One";
share.setVariable('variable1', $scope.variable1); //setting value in service variable
});
app.controller('Ctrl2', function ($scope, share) {
console.log("shared variable " + share.getSharedVariables());
});
Your try with service it quite good, but you shouldn't try to use $scope in dependency injection into the service code. Service is meant to be a data provider to you, then if you want to share some data between 2 controllers, you should make place for this data in your service
.service('share', function ($scope) {
this.variable1 = '';
return {
getVariable: function () {
return this.variable1;
}
setVariable(variable)
{
this.variable1 = variable;
}
};
});
app.controller('Ctrl1', function (share) {
$scope.variable1 = share.getVariable();
});
This is the broader description of this solution:
https://stackoverflow.com/a/21920241/4772988
Best way is really using services. Services have only access to $rootScope, they don't have their own $scope.
However, you shouldn't use scopes for this task. Just make a service with some shared variable outside any scopes.
.service('share', function ($scope) {
var variable = 42;
return {
getVariable: function () {
return variable;
}
};
});
app.controller('Ctrl', function ($scope, share) {
console.log("shared variable " + share.getVariable());
// Watch for changes in the service property
$scope.$watch(function() { return share.getVariable(); }, function(oldValue, newValue) {
// whatever
});
});
First, your service should be like this:
app.service('share', function () {
// this is a private variable
var variable1;
return {
// this function get/sets the value of the private variable variable1
variable1: function (newValue) {
if(newValue) variable1 = newValue;
return variable1;
}
};
});
To set the value of the shared variable pass the value to the function we created on the service
app.controller('Ctrl1', function ($scope, share) {
$scope.variable1 = "One";
// share the value of $scope.variable1
share.variable1($scope.variable1);
});
To get the value of the shared variable also use the function in the service without passing a value
app.controller('Ctrl2', function ($scope, share) {
console.log("shared variable " + share.variable1());
});
Update: Using $rootScope is considered bad practice.
What you need is the $rootScope. The $scope is just for one controller. Here is an example:
angular.module('myApp', [])
.run(function($rootScope) {
$rootScope.test = new Date();
})
.controller('myCtrl', function($scope, $rootScope) {
$scope.change = function() {
$scope.test = new Date();
};
$scope.getOrig = function() {
return $rootScope.test;
};
})
.controller('myCtrl2', function($scope, $rootScope) {
$scope.change = function() {
$scope.test = new Date();
};
$scope.changeRs = function() {
$rootScope.test = new Date();
};
$scope.getOrig = function() {
return $rootScope.test;
};
});
I want to use my angular JS scope data inside my google chart or JavaScript
My angular JS file given below
angular.module('reports').controller('ReportInfoCtrl', ['$scope', 'reports', '$rootScope','$location','blockUI',
function ($scope, reports, $rootScope, $location, blockUI) {
$scope.getReportDetail = function () {
blockUI.start();
reports.getReportInformation().then(function (data) {
blockUI.stop();
if (data !== undefined) {
$scope.report_details = data;
}
});
};
}]);
Yes, sure. You can access your scope variable of controller out side your angular.
var controllerElement = document.querySelector('[ng-controller="ReportInfoCtrl"]'); // You can use javascript or Jquery to select the controller's div element.
var controllerScope = angular.element(controllerElement).scope(); // Angular provided the interface to access angular's scope
var asd = controllerScope.report_details; // Now you can access all scope variable using 'controllerScope'
Update
angular.module('reports').controller('ReportInfoCtrl', ['$scope', 'reports', '$rootScope','$location','blockUI',
function ($scope, reports, $rootScope, $location, blockUI) {
$scope.getReportDetail = function () {
blockUI.start();
reports.getReportInformation().then(function (data) {
blockUI.stop();
if (data !== undefined) {
$scope.report_details = data;
}
return data;
});
};
}]);
And in your js file,
var asd = controllerScope.getReportDetail();
Asynchronous scope manipulations must occur within a $scope.apply to be noticed by angular.
I'm trying to understand how to unit test my directive in my situation below.
Basically I'm trying to unit test a directive which has a controller. On the loading of this directive the controller makes a http request by a service which brings some data to the controller again then provides this data to the directive view.
On the scenario below in my understanding I should do:
A $httpBackend to avoid an exception when the http request is done;
Populate the fake data to be able to unit test the directive with diff behaviors
Compile the directive
What I've been trying so far, as you can see, is override the Service with the fake data. What I could not make work so far.
Some doubts come up now.
As you can see in my Controller. I'm providing the whole Service to the view:
$scope.ItemsDataservice = ItemsDataservice;
What makes me believe that my approach to override the Service should work.
My question:
On scenario below I understand that I could override the Service to manipulate the data or even override the controller to manipulate the data by scope.
What's the right thing to do here?
Am I understand wrong?
Am I mixing the unit tests?
In my current unit test code, when I'm applying the fake data(or not), is not make any difference:
ItemsDataservice.items = DATARESULT;
ItemsDataservice.items = null;
Controller:
angular.module('app')
.controller('ItemsCtrl', function ($scope, $log, ItemsDataservice) {
$scope.ItemsDataservice = ItemsDataservice;
$scope.ItemsDataservice.items = null;
$scope.loadItems = function() {
var items = [];
ItemsDataservice.getItems().then(function(resp) {
if (resp.success != 'false') {
for (resp.something ... ) {
items.push({ ... });
};
ItemsDataservice.items = items;
};
}, function(e) {
$log.error('Error', e);
});
};
$scope.loadItems();
});
Service:
angular.module('app')
.service('ItemsDataservice', function ItemsDataservice($q, $http) {
ItemsDataservice.getItems = function() {
var d = $q.defer();
var deffered = $q.defer();
var url = 'http://some-url?someparameters=xxx'
$http.get(url)
.success(function (d) {
deffered.resolve(d);
});
return deffered.promise;
};
return ItemsDataservice;
});
Directive:
angular.module('app')
.directive('items', function () {
return {
templateUrl: '/items.html',
restrict: 'A',
replace: true,
controller: 'ItemsCtrl'
};
});
Unit testing directive:
ddescribe('Directive: Items', function () {
var element, scope, _ItemsDataservice_, requestHandler, httpBackend;
var URL = 'http://some-url?someparameters=xxx';
var DATARESULT = [{ ... }];
// load the directive's module
beforeEach(module('app'));
beforeEach(module('Templates')); // setup in karma to get template from .html
beforeEach(inject(function ($rootScope, ItemsDataservice) {
httpBackend = $httpBackend;
scope = $rootScope.$new();
_ItemsDataservice_ = ItemsDataservice;
requestHandler = httpBackend.when('GET', URL).respond(200, 'ok');
}));
afterEach(function() {
//httpBackend.verifyNoOutstandingExpectation();
//httpBackend.verifyNoOutstandingRequest();
});
it('Show "No Items available" when empty result', inject(function ($compile) {
_ItemsDataservice_.items = null;
element = angular.element('<div data-items></div>');
element = $compile(element)(scope);
scope.$digest();
element = $(element);
expect(element.find('.msg_noresult').length).toBe(1);
}));
it('Should not show "No Items available" when data available ', inject(function ($compile) {
_ItemsDataservice_.items = DATARESULT;
element = angular.element('<div data-items></div>');
element = $compile(element)(scope);
scope.$digest();
element = $(element);
expect(element.find('.msg_noresult').length).toBe(0);
}));
});
I sorted out the problem.
Changed this line:
element = $compile(element)(scope);
To this line:
element = $compile(element.contents())(scope);
The only diff is the jquery method .contents()
I did not get yet why. But it solved.
Update:
Another thing I've just discovered and that was really useful for me.
You can use regular expression on you httpBackend:
httpBackend.whenGET(/.*NameOfThePageXXX\.aspx.*/).respond(200, 'ok');
So, you don't need to worry to use exactly the same parameters etc if you just want to avoid an exception.
I am trying to include a library of functions, held in a factory, into a controller.
Similar to questions like this:
Creating common controller functions
My main controller looks like this:
recipeApp.controller('recipeController', function ($scope, groceryInterface, ...){
$scope.groceryList = [];
// ...etc...
/* trying to retrieve the functions here */
$scope.groceryFunc = groceryInterface; // would call ng-click="groceryFunc.addToList()" in main view
/* Also tried this:
$scope.addToList = groceryInterface.addToList();
$scope.clearList = groceryInterface.clearList();
$scope.add = groceryInterface.add();
$scope.addUp = groceryInterface.addUp(); */
}
Then, in another .js file, I have created the factory groceryInterface. I've injected this factory into the controller above.
Factory
recipeApp.factory('groceryInterface', function(){
var factory = {};
factory.addToList = function(recipe){
$scope.groceryList.push(recipe);
... etc....
}
factory.clearList = function() {
var last = $scope.prevIngredients.pop();
.... etc...
}
factory.add = function() {
$scope.ingredientsList[0].amount = $scope.ingredientsList[0].amount + 5;
}
factory.addUp = function(){
etc...
}
return factory;
});
But in my console I keep getting ReferenceError: $scope is not defined
at Object.factory.addToList, etc. Obviously I'm guessing this has to do with the fact that I'm using $scope in my functions within the factory. How do I resolve this? I notice that in many other examples I've looked at, nobody ever uses $scope within their external factory functions. I've tried injecting $scope as a parameter in my factory, but that plain out did not work. (e.g. recipeApp.factory('groceryInterface', function(){ )
Any help is truly appreciated!
Your factory can't access your $scope, since it's not in the same scope.
Try this instead:
recipeApp.controller('recipeController', function ($scope, groceryInterface) {
$scope.addToList = groceryInterface.addToList;
$scope.clearList = groceryInterface.clearList;
$scope.add = groceryInterface.add;
$scope.addUp = groceryInterface.addUp;
}
recipeApp.factory('groceryInterface', function () {
var factory = {};
factory.addToList = function (recipe) {
this.groceryList.push(recipe);
}
factory.clearList = function() {
var last = this.prevIngredients.pop();
}
});
Alternatively, you can try using a more object oriented approach:
recipeApp.controller('recipeController', function ($scope, groceryInterface) {
$scope.groceryFunc = new groceryInterface($scope);
}
recipeApp.factory('groceryInterface', function () {
function Factory ($scope) {
this.$scope = $scope;
}
Factory.prototype.addToList = function (recipe) {
this.$scope.groceryList.push(recipe);
}
Factory.prototype.clearList = function() {
var last = this.$scope.prevIngredients.pop();
}
return Factory;
});
You cannot use $scope in a factory as it is not defined. Instead, in your factory functions change the properties of the object the factory is returning, e.g.
factory.addToList = function (recipe) {
this.groceryList.push(recipe);
}
these will then get passed on to your $scope variable
$scope.addToList = groceryInterface.addToList;
// ... = groceryInterface.addToList(); would assign to `$scope.addToList` what is returned, instead of the function itself.
This isn't the exact answer for this question, but I had a similar issues that I solved by simply passing $scope as an argument to a function in my factory. So it won't be the normal $scope, but $scope at the time the function in the factory is called.
app.controller('AppController', function($scope, AppService) {
$scope.getList = function(){
$scope.url = '/someurl'
// call to service to make rest api call to get data
AppService.getList($scope).then(function(res) {
// do some stuff
});
}
});
app.factory('AppService', function($http, $q){
var AppService = {
getList: function($scope){
return $http.get($scope.url).then(function(res){
return res;
});
},
}
return AppService;
});