Angular js - Using Service - angularjs

Menu-Controller:
// Left Menu Start
'use strict';
angular.module("MainApp")
.controller('LeftMenuCtrl', function ($scope, $rootScope, actionCall) {
$scope.notify = {};
$scope.actionUrlCall = function(action)
{
actionCall.actionUrlCall(action, function(response){
$scope.notify = actionCall.notify;
console.log($scope.notify);
});
};
});
Now there are several html pages in view:
Each one of them has these notification directives:
<div class="col-sm-12">
<notification type="success"></notification>
<notification type="error"></notification>
<notification type="warning"></notification>
</div>
Which displays the notification as per the output from actionCall service.
I have console logged console.log($scope.notify); and I'm getting the required result. Problem is I don't know how to communicate this with the directives present on various different pages with different controllers and different scope.

Ok, so this is how I finally solved the issue.
I created a new service(menu-service.js) :
'use strict';
angular.module("MainApp")
.factory('menuClick', function($rootScope) {
var sharedService = {};
sharedService.notify = {};
sharedService.prepForBroadcast = function(msg) {
this.broadcastItem();
};
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
return sharedService;
});
Then I injected my service which is named as menuClick in my menu controller and added some lines in it:
angular.module("MainApp")
.controller('LeftMenuCtrl', function ($scope, $rootScope, menuClick) {
$scope.handleMenuClick = function(action) {
menuClick.notify.warningNotify = true;
menuClick.notify.errorNotify = true;
menuClick.notify.successNotify = true;
if(!action.IsEnabled)
{
menuClick.notify.warningNotify = false;
menuClick.notify.warningMessage = "This operation is disabled ( "+action.Text+" )";
menuClick.prepForBroadcast(menuClick.notify);
}
};
});
Then I injected menuClick to the controllers where I needed to listen the Broadcast data and added following lines in those controllers:
$scope.$on('handleBroadcast', function() {
$scope.notify = menuClick.notify;
});
And it started working!!

Related

How to call a method from a controller to another controller in angular js

I have a view for SidebarController like below -
<a ng-click="reachMe($event);$event.preventDefault()" ng-href="#/app/hello">
Before going to the link I want to call reachMe() to check some changes on page and need to show an alert if any changes made
function SidebarController($rootScope, $scope, $state, $location, SidebarLoader){
$scope.reachMe = function(event){
//here I want to call function isPageChanged() from StaticPageController
//something like this
// if StaticPageController.isPageChanged() return true
// then show alert
// else
// $location.url($href)
}
}
Update 1 :
Not sure about this, But give it a try.
<div ng-app="testApp" ng-controller="ControllerOne">
<button ng-click="methodA();"> Call Another Controller</button>
</div>
<script>
var app = angular.module('testApp', []);
app.controller('ControllerOne', function($scope, $rootScope) {
$scope.reachMe = function() {
var arrayData = [1,2,3];
$rootScope.$emit('callEvent', arrayData);
if($rootScope.isChanged){
// Show Alert
}else{
//Go to route
}
}
});
app.controller('ControllerTwo', function($scope, $rootScope,$state) {
$scope.checkSomethingChanged = function() {
alert("Hello");
$rootScope.isChanged = true;
}
$rootScope.$on('callEvent', function(event, data) {
console.log(data);
$scope.checkSomethingChanged();
});
});
Following method worked for me perfectly :
<div ng-app="testApp" ng-controller="ControllerOne">
<button ng-click="methodA();"> Call Another Controller</button>
</div>
<script>
var app = angular.module('testApp', []);
app.controller('ControllerOne', function($scope, $rootScope) {
$scope.methodA = function() {
var arrayData = [1,2,3];
$rootScope.$emit('callEvent', arrayData);
}
});
app.controller('ControllerTwo', function($scope, $rootScope) {
$scope.reachMe = function() {
alert("Hello");
}
$rootScope.$on('callEvent', function(event, data) {
console.log(data);
$scope.reachMe();
});
});
</script>
A controller is not the right concept for sharing functionality. Use a Factory or Service for that.
var logicFactory = function () {
return {
methodA: function () {
},
methodB: function()
{
}
};
}
You can then inject that factory into each controller where it is needed like:
var ControllerA = function ($scope,logicFactory) {
$scope.logic = logicFactory;
}
ControllerA.$inject = ['$scope', 'logicFactory'];
Another option is to use the broadcast/emit Patern. But I would use that only where really necessary:
Usage of $broadcast(), $emit() And $on() in AngularJS

display or hide loading from multiple controllers

I'm trying to create a generic loading that would be shared among controllers. So, I've created a service that will control the hide/show state:
(function () {
'use strict';
var module = angular.module('main');
var serviceId = "LoadingService";
var ShowLoading = false;
function LoadingService($rootScope) {
function showLoading(value) {
$rootScope.$broadcast(LOADING_CHANGED, value);
}
var service = {
ShowLoading: showLoading
}
return service;
}
LoadingService.$inject = ['$rootScope'];
module.service(serviceId, LoadingService);
}());
here's the loading controller:
(function () {
'use strict';
var app = angular.module("main");
var controllerId = "rbLoading.controller";
function loadingController($scope, LoadingService) {
$scope.isLoading = false;
$scope.$on(LOADING_CHANGED, function (event, data) {
$scope.isLoading = data;
});
}
app.controller(controllerId, loadingController);
loadingController.$inject = ['$scope', 'LoadingService'];
}());
the html:
<div class="loading-panel" ng-if="isLoading">
<!-- loading content-->
</div>
and the directive:
(function () {
'use strict';
var loadingDirective = function (OPTIMISATION) {
return {
restrict: 'E',
templateUrl: function (element, attr) {
return 'App/components/shared/loading/loading.html';
},
controller: 'rbLoading.controller',
controllerAs: 'loading'
};
}
var app = angular.module('main');
loadingDirective.$inject = ["OPTIMISATION"];
app.directive('rbLoading', loadingDirective);
}());
When I'm changing the LoadingService value, it's triggering the LOADING_CHANGED event. The problem is that ng-if is not working as expected, although the data variable is with the right value. What am I missing?
UPDATE
Changing to ng-show almost solve the problem. For a particular scenario is not working. I'm displaying the loading while trying to get the user position using html5 GeoLocation api, and setting a timeout after 10 seconds using.
setTimeout(function () {
console.log("timeout");
LoadingService.ShowLoading(false);
}, 10000);
this setTimeout function is being triggered, however the loading keeps being displayed.

Correct way to set property in Angular Factory

What is the best practise to create a get/set property in an angular factory that will be set by a controller in view X and get by the same controller using view Y? Should I be using $rootScope like below?
Factory:
angular.module('start.services').factory('bluetoothFactory', ['$q', '$window', '$rootScope', function($q, $window, $rootScope) {
return {
connectedDeviceSet: function(device)
{
$rootScope.connectedDevice = device;
},
connectedDeviceGet: function()
{
return $rootScope.connectedDevice;
},
...
Controller:
angular.module('start.controllers',[]).controller('bluetoothCtrl', function($scope, $ionicModal, $timeout, bluetoothFactory)
{
...
$scope.list = function()
{
bluetoothFactory.list().then(function(data)
{
$scope.info = data;
if (data.length > 0)
{
bluetoothFactory.connectedDeviceSet = data[0];
}
},
function(error)
{
$scope.error = error;
});
};
$scope.readEPCForEncoding = function()
{
var device = bluetoothFactory.connectedDeviceGet;
....
}
You should be using a service rather than a factory. Services should be defined as a prototype -- which translates to a class in other languages. Try not to access the $rootScope in your factories or services. This means you are not properly encapsulating your properties. This will cause collisions and strange errors.
var app = angular.module('app', []);
function Bluetooth() {
this.connectedDevice;
}
Bluetooth.prototype.setConnectedDevice = function(value) {
this.connectedDevice = value;
}
Bluetooth.prototype.getConnectedDevice = function() {
return this.connectedDevice;
}
app.service('bluetooth', Bluetooth);
DeviceController.$inject = ['bluetooth'];
function DeviceController(bluetooth) {
this.bluetooth = bluetooth;
this.device;
}
DeviceController.prototype.getDevice = function() {
this.device = this.bluetooth.getConnectedDevice();
}
app.controller('DeviceController', DeviceController);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="DeviceController as vm1">
Controller 1: <br><br>
<button ng-click="vm1.bluetooth.setConnectedDevice('Device set from instance one')">Set Device</button>
<br/><br>
<button ng-click="vm1.getDevice()">Get Device</button>
<br/><br>
Device: {{vm1.device}}
<br>
Device in Service: {{vm1.bluetooth.connectedDevice}}
</div>
<br/> <br/>
<div ng-controller="DeviceController as vm2">
Controller 2: <br><br>
<button ng-click="vm2.bluetooth.setConnectedDevice('Device set from instance Two')">Set Device</button>
<br/><br>
<button ng-click="vm2.getDevice()">Get Device</button>
<br/><br>
Device: {{vm2.device}}
<br>
Device in Service: {{vm2.bluetooth.connectedDevice}}
</div>
</div>
Then in your controller if you can either proxy the set and get methods or expose the bluetooth service to the view.
Click on the buttons in the two instances of the controller and watch how the device is set.
you should write in this way.
angular.module('start.services').factory('bluetoothFactory', ['$q', '$window', '$rootScope', function($q, $window, $rootScope) {
return {
connectedDevice : null,
connectedDeviceSet: function(device)
{
this.connectedDevice = device;
},
connectedDeviceGet: function()
{
return this.connectedDevice;
},
There is no need of $rootScope as it violate global scope.
Please refer this Plunker for better understanding.Check script.js
To set
bluetoothFactory.connectedDeviceSet(dataishere);
To get
var dataishere = bluetoothFactory.connectedDeviceGet();
simple and effective if you want same data in every controller.and you dont need to store data in $rootScope its worst.
app.factory('bluetoothFactory', function($http) {
function Test() {
var context = this;
this.data= [];
this.connectedDeviceGet= function() {
return this.data;
};
this.connectedDeviceSet= function(data) {
this.data = data;
};
}
//get instance
var self;
function get() {
if (self) {
return self;
} else {
var self = new Test();
return self;
}
}
return {
get: get
};
});
//can access like this.
app.controller('testCtrl',function(bluetoothFactory){
var service = bluetoothFactory.get();
service.connectedDeviceSet([1,2]);
});

Angular, show loading when any resource is in pending

I already write a code to display a loader div, when any resources is in pending, no matter it's getting via $http.get or routing \ ng-view.
I wan't only information if i'm going bad...
flowHandler service:
app.service('flowHandler', function(){
var count = 0;
this.init = function() { count++ };
this.end = function() { count-- };
this.take = function() { return count };
});
The MainCTRL append into <body ng-controller="MainCTRL">
app.controller("MainCTRL", function($scope, flowHandler){
var _this = this;
$scope.pageTitle = "MainCTRL";
$scope.menu = [];
$scope.loader = flowHandler.take();
$scope.$on("$routeChangeStart", function (event, next, current) {
flowHandler.init();
});
$scope.$on("$routeChangeSuccess", function (event, next, current) {
flowHandler.end();
});
updateLoader = function () {
$scope.$apply(function(){
$scope.loader = flowHandler.take();
});
};
setInterval(updateLoader, 100);
});
And some test controller when getting a data via $http.get:
app.controller("BodyCTRL", function($scope, $routeParams, $http, flowHandler){
var _this = this;
$scope.test = "git";
flowHandler.init();
$http.get('api/menu.php').then(function(data) {
flowHandler.end();
$scope.$parent.menu = data.data;
},function(error){flowHandler.end();});
});
now, I already inject flowHandler service to any controller, and init or end a flow.
It's good idea or its so freak bad ?
Any advice ? How you do it ?
You could easily implement something neat using e.g. any of Bootstrap's progressbars.
Let's say all your services returns promises.
// userService ($q)
app.factory('userService', function ($q) {
var user = {};
user.getUser = function () {
return $q.when("meh");
};
return user;
});
// roleService ($resource)
// not really a promise but you can access it using $promise, close-enough :)
app.factory('roleService', function ($resource) {
return $resource('role.json', {}, {
query: { method: 'GET' }
});
});
// ipService ($http)
app.factory('ipService', function ($http) {
return {
get: function () {
return $http.get('http://www.telize.com/jsonip');
}
};
});
Then you could apply $scope variable (let's say "loading") in your controller, that is changed when all your chained promises are resolved.
app.controller('MainCtrl', function ($scope, userService, roleService, ipService) {
_.extend($scope, {
loading: false,
data: { user: null, role: null, ip: null}
});
// Initiliaze scope data
function initialize() {
// signal we are retrieving data
$scope.loading = true;
// get user
userService.getUser().then(function (data) {
$scope.data.user = data;
// then apply role
}).then(roleService.query().$promise.then(function (data) {
$scope.data.role = data.role;
// and get user's ip
}).then(ipService.get).then(function (response) {
$scope.data.ip = response.data.ip;
// signal load complete
}).finally(function () {
$scope.loading = false;
}));
}
initialize();
$scope.refresh = function () {
initialize();
};
});
Then your template could look like.
<body ng-controller="MainCtrl">
<h3>Loading indicator example, using promises</h3>
<div ng-show="loading" class="progress">
<div class="progress-bar progress-bar-striped active" style="width: 100%">
Loading, please wait...
</div>
</div>
<div ng-show="!loading">
<div>User: {{ data.user }}, {{ data.role }}</div>
<div>IP: {{ data.ip }}</div>
<br>
<button class="button" ng-click="refresh();">Refresh</button>
</div>
This gives you two "states", one for loading...
...and other for all-complete.
Of course this is not a "real world example" but maybe something to consider. You could also refactor this "loading bar" into it's own directive, which you could then use easily in templates, e.g.
//Usage: <loading-indicator is-loading="{{ loading }}"></loading-indicator>
/* loading indicator */
app.directive('loadingIndicator', function () {
return {
restrict: 'E',
scope: {
isLoading: '#'
},
link: function (scope) {
scope.$watch('isLoading', function (val) {
scope.isLoading = val;
});
},
template: '<div ng-show="isLoading" class="progress">' +
' <div class="progress-bar progress-bar-striped active" style="width: 100%">' +
' Loading, please wait...' +
' </div>' +
'</div>'
};
});
Related plunker here http://plnkr.co/edit/yMswXU
I suggest you to take a look at $http's pendingRequest propertie
https://docs.angularjs.org/api/ng/service/$http
As the name says, its an array of requests still pending. So you can iterate this array watching for an specific URL and return true if it is still pending.
Then you could have a div showing a loading bar with a ng-show attribute that watches this function
I would also encapsulate this requests in a Factory or Service so my code would look like this:
//Service that handles requests
angular.module('myApp')
.factory('MyService', ['$http', function($http){
var Service = {};
Service.requestingSomeURL = function(){
for (var i = http.pendingRequests.length - 1; i >= 0; i--) {
if($http.pendingRequests[i].url === ('/someURL')) return true;
}
return false;
}
return Service;
}]);
//Controller
angular.module('myApp')
.controller('MainCtrl', ['$scope', 'MyService', function($scope, MyService){
$scope.pendingRequests = function(){
return MyService.requestingSomeURL();
}
}]);
And the HTML would be like
<div ng-show="pendingRequests()">
<div ng-include="'views/includes/loading.html'"></div>
</div>
I'd check out this project:
http://chieffancypants.github.io/angular-loading-bar/
It auto injects itself to watch $http calls and will display whenever they are happening. If you don't want to use it, you can at least look at its code to see how it works.
Its very simple and very useful :)
I used a base controller approach and it seems most simple from what i saw so far. Create a base controller:
angular.module('app')
.controller('BaseGenericCtrl', function ($http, $scope) {
$scope.$watch(function () {
return $http.pendingRequests.length;
}, function () {
var requestLength = $http.pendingRequests.length;
if (requestLength > 0)
$scope.loading = true;
else
$scope.loading = false;
});
});
Inject it into a controller
angular.extend(vm, $controller('BaseGenericCtrl', { $scope: $scope }));
I am actually also using error handling and adding authorization header using intercepting $httpProvider similar to this, and in this case you can use loading on rootScope
I used a simpler approach:
var controllers = angular.module('Controllers', []);
controllers.controller('ProjectListCtrl', [ '$scope', 'Project',
function($scope, Project) {
$scope.projects_loading = true;
$scope.projects = Project.query(function() {
$scope.projects_loading = false;
});
}]);
Where Project is a resource:
var Services = angular.module('Services', [ 'ngResource' ]);
Services.factory('Project', [ '$resource', function($resource) {
return $resource('../service/projects/:projectId.json', {}, {
query : {
method : 'GET',
params : {
projectId : '#id'
},
isArray : true
}
});
} ]);
And on the page I just included:
<a ng-show="projects_loading">Loading...</a>
<a ng-show="!projects_loading" ng-repeat="project in projects">
{{project.name}}
</a>
I guess, this way, there is no need to override the $promise of the resource

Angularjs and qunit testing

I have a angularjs web application and want to use qunit for unit testing in it. I have a controller:
function RootCtrl($scope, $rootScope, $window, $location) {
// logger is empty at the start
$scope.logger = '';
// we have no login error at the start
$scope.login_error = '';
//
// Get values array of object
//
$rootScope.values = function (obj) {
var vals = [];
for( var key in obj ) {
if(key !== '$$hashKey' && key !== 'checked')
vals.push(obj[key]);
}
return vals;
}
}
Now i want to write unit test for values function with qunit. I included all js files to the test/index.html and qunit.css. Now my test.js has following content:
var injector = angular.injector(['ng', 'myApp']);
var init = {
setup : function () {
this.$scope = injector.get('$rootScope').$new();
}
}
module('RootCtrl', init);
test('RootCtrl', function(){
var $controller = injector.get('$controller');
$controller('RootCtrl', {
$scope : this.$scope,
$location : this.$location
});
equal(['value'], $controller.values({'key' : 'value'}))
});
But i'm getting error: http://docs.angularjs.org/error/$injector/unpr?p0=$rootElementProvider%20%3C-%20$rootElement%20%3C-%20$location%20%3C-%20$route at:
$controller('RootCtrl', {
$scope : this.$scope,
$location : this.$location
});
How to inject correctly controller and use $scope, $rootScope, $location and another services from it?
Thank you.
Try this instead of your controller
$controller('RootCtrl',['$scope', '$rootScope', '$location','$route', function ($scope, $rootScope, $location, $route) {
$scope : this.$scope,
$location : this.$location
}]);
Had similar problem, so since no other answer here.
I ended up using:
client side code:
var myApp= angular.module('myApp', []);
myApp.controller('myCtrl', function ($scope) {
//angular client side code
$scope.canSubmit = function () {
//some logic
return true;
}
}
Qunit tests:
var ctrl, ctrlScope, injector;
module("Testing the controller", {
setup: function () {
angular.module('myApp');
injector = angular.injector(['ng', 'myApp']);
ctrlScope = injector.get('$rootScope').$new();
ctrl = injector.get('$controller')('myCtrl', { $scope: ctrlScope });
ctrlScope.model = {
//model object
};
},
teardown: function () {
}
});
test("Given something happened then allow submit", function () {
ok(ctrlScope.someFunction(...), "some functionality happened");
equal(true, ctrlScope.canSubmit());
});
This blog post was useful.
One can easily inject more into the controller under test.

Resources