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]);
});
Related
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
/*service */
app.service('sharedProperties', function () {
var property = 'First';
return {
getProperty: function () {
return property;
},
setProperty: function(value) {
property = value;
}
};
});
/*first contoller */
app.controller('loginCtrl',function($scope,$location,$http,$window,sharedProperties){
$scope.submit =function(){
var username=$scope.username;
var pass=$scope.password;
sharedProperties.setProperty(username);
$location.path('/userdashboard');
$window.location.reload();
});
}
});
/*second controller*/
app.controller('empController', function($route,$scope,$http,$routeParams,sharedProperties){
$scope.getEmployees = function(){
alert( sharedProperties.getProperty());
};
};
Try this, it will work for you. :
<div ng-app="myApp" ng-controller="myCtrl">
<button ng-click="sendData();"></button>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $http) {
function sendData($scope) {
var arrayData = [1,2,3];
$scope.$emit('someEvent', arrayData);
}
});
app.controller('yourCtrl', function($scope, $http) {
$scope.$on('someEvent', function(event, data) {
console.log(data);
});
});
</script>
Service does not need to return anything. You have to assign everything in this variable. Because service will create instance by default and use that as a base object.
app.service('sharedProperties', function () {
this.property = 'First';
});
Then in controller
sharedProperties.property = $scope.username;
Had you been looking to use factory
app.factory('sharedProperties', function () {
var factory = {};
factory.property = 'Hello';
factory.setProperty = function (value) {
factory.property = value;
};
return factory;
});
Then in controller you would use it
sharedProperties.setProperty($scope.username); // Setter
$scope.var = sharedProperties.property; //getter
EDIT
Working Plnkr
You can assign members to $rootScope, which will store data globally for your app. Just inject $rootScope to each controller.
For instance...
/*first controller */
app.controller('loginCtrl',function($scope,$rootScope,$location,$http,$window,sharedProperties){
$scope.submit =function(){
var username=$scope.username;
var pass=$scope.password;
$rootScope.username = username;
$location.path('/userdashboard');
$window.location.reload();
});
}
});
This will make 'username' available to any controller that injects and consumes $rootScope.
/*second controller*/
app.controller('empController', function($route,$scope,$rootScope,$http,$routeParams,sharedProperties){
$scope.getEmployees = function(){
alert($rootScope.username);
};
};
I want to use ng-infinite-scroll (https://sroze.github.io/ngInfiniteScroll/). But when I want to call function from my service (injected to controller) nothing happened (this function doesn't trigger). When I call function not from the service but from $scope of the controller - everything works fine. How to call function from the injected service in infinite-scroll directive?
My HTML structure:
<div class="col-xs-12 publications-container" ng-controller="showPublicationsCtrl">
<h3 class="publ-heading">Proposed Publications</h3>
<ul class="list-group" infinite-scroll="publicationsFactory.getPublications()" infinite-scroll-disabled='publicationsFactory.busyLoadingData'>
<li ng-repeat="publication in publications" class="list-unstyled list-group-item" ng-cloak>
<p class="text-warning">Author: <i>{{publication.author}}, {{publication.timestamp | date: 'MMM. d, y'}}</i></p>
<p ng-text-truncate="publication.text" ng-tt-words-threshold="3">{{publication.text}}</p>
<p class="text-muted">Channels: <i>{{publication.channels.join(", ")}}</i></p>
</li>
</ul>
</div>
My showPublicationsCtrl controller:
twitterApiApp.controller("showPublicationsCtrl", ['publicationsFactory', '$scope', function (publicationsFactory, $scope) {
publicationsFactory.getPublications();
$scope.publications = publicationsFactory.publications;
}]);
My publicationsFactory service:
angular.module('twitterApiApp').factory('publicationsFactory', ['$http', function($http) {
var publicationsFact = {};
publicationsFact.publications = [];
publicationsFact.busyLoadingData = false;
publicationsFact.getId = function() {
publicationsFact.id = publicationsFact.publications.length > 0 ?
publicationsFact.publications[publicationsFact.publications.length-1]['id'] : 0;
};
publicationsFact.getPublications = function () {
console.log("Triggered");
if (publicationsFact.busyLoadingData) return;
publicationsFact.busyLoadingData = true;
publicationsFact.getId();
$http.get("/Publications?id_gte=" + publicationsFact.id + "&_limit=2").then(function(response) {
for (var i = 0; i < response.data.length; i++) {
publicationsFact.publications.push(response.data[i]);
};
});
publicationsFact.busyLoadingData = false;
};
return publicationsFact;
}]);
If I create some function in my controller, for example $scope.myFunction and then in HTML structute I assign infinite-scroll attribute to myFunction() the function will be successfully executed. So, I think maybe there are some mistakes in the way I inject the service in the controller. But everything else except ng-inginite-scroll works as planned.
infinite-scroll is binded to $scope.publicationsFactory :
<ul class="list-group" infinite-scroll="publicationsFactory.getPublications()" infinite-scroll-disabled='publicationsFactory.busyLoadingData'>
but publicationsFactory is not available in your scope, you must expose it like this :
twitterApiApp.controller("showPublicationsCtrl", ['publicationsFactory', '$scope', function (publicationsFactory, $scope) {
// Expose publication factory
$scope.publicationsFactory = publicationsFactory;
publicationsFactory.getPublications();
$scope.publications = publicationsFactory.publications;
}]);
How to update $scope.publications with latest publications retrieved using Factory
Your factory can return a promise resolved with the latest publications, change your code like this :
angular.module('twitterApiApp').factory('publicationsFactory', ['$http', '$q', function($http, $q) {
var publicationsFact = {};
publicationsFact.publications = [];
publicationsFact.busyLoadingData = false;
publicationsFact.getId = function() {
publicationsFact.id = publicationsFact.publications.length > 0 ?
publicationsFact.publications[publicationsFact.publications.length - 1]['id'] : 0;
};
/**
* Get latest publications
* #returns {object} A promise
*/
publicationsFact.getPublications = function() {
var deferred = $q.defer();
console.log("Triggered");
if (publicationsFact.busyLoadingData) return;
publicationsFact.busyLoadingData = true;
publicationsFact.getId();
$http.get("/Publications?id_gte=" + publicationsFact.id + "&_limit=2").then(function(response) {
for (var i = 0; i < response.data.length; i++) {
publicationsFact.publications.push(response.data[i]);
};
// Resolve promise with updates publications list
deferred.resolve(publicationsFact.publications);
}, function(error) {
// Reject promise with error message
deferred.reject(error.message);
});
publicationsFact.busyLoadingData = false;
// Return promise
return deferred.promise;
};
return publicationsFact;
}]);
Then, in your controller :
twitterApiApp.controller("showPublicationsCtrl", ['publicationsFactory', '$scope', function (publicationsFactory, $scope) {
// Expose publication factory
$scope.publicationsFactory = publicationsFactory;
publicationsFactory.getPublications()
.then(function(results) {
$scope.publications = results;
});
}]);
You should add this line to your ctrl:
$scope.getPublications = publicationsFactory.getPublications;
And then call that ctrl function from the view.
This way, you are binding reference to the service function to the controller scope which is available to you in the view.
Edit: Another option is to bind whole service to the ctrl property as someone else already suggested
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!!
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