I want to share data across multiple controllers in angularjs without using $rootScope. I am learning to use services for the same. What is wrong with the following code?
This is my controllers.js :
function Ctrl1(shareData) {
shareData.setValue("my_data");
}
function Ctrl2($scope, shareData) {
$scope.value = shareData.getvalue();
}
This is my services.js :
angular.module('connectionsServices').
factory('shareData', function() {
var shareVar = 'undefined';
return {
getValue: function() {
return shareVar;
},
setValue: function(value) {
shareVar = value;
}
}
});
But it is not working.
You can't bind service variable to controller variable, your code is changing shareVar but you are unable to see it, you can update the view like this:
HTML:
<div ng-app="testApp">
<div ng-controller="Ctrl2">{{ value }}
<br />
<div ng-controller="Ctrl1">
<button type="button" ng-click="change(); update();">Change me!</button>
</div>
</div>
</div>
JS:
angular.module('testApp', []).factory('shareData', function ($window) {
var shareVar = 'undefined';
return {
getValue: function () {
return shareVar;
},
setValue: function (value) {
shareVar = value;
}
};
});
function Ctrl1($scope, $window, shareData) {
$scope.change = function () {
shareData.setValue("my_data");
};
}
function Ctrl2($scope, shareData) {
$scope.value = shareData.getValue();
$scope.update = function () {
$scope.value = shareData.getValue();
};
}
Services provide an easy way for us to share data and functionality
throughout our app. The services we create are singletons that can be
injected into controllers and other services, making them the ideal
place for writing reusable code.
Follow its link https://thinkster.io/a-better-way-to-learn-angularjs/services
Related
I have been following some online tutorials and using the angularjs-template to get started with Angular. I can't get the page (html template) to update with the controller. I think there is a problem with the way I have set up the controller as the values are not available to the html template.
I have been trying to follow some of the best practive guides which suggested to wrap my components in an 'Invoked Function Expression' and to seperate out the controller, service and service manager. However, I think I have made a bit of a hash of this and need some help to figure out what I am doing wrong.
With the console I can see that $scope.metric contains the information I want. For me this means that the controller has successfully pulled the data back from my API via the metricService. However I can't seem to have the results printed back onto the html page e.g. metric.id.
Any help appreciated - I am at the end of my wits trying to figure this out.
metric.html
<div class="panel panel-primary">
<div class="panel-body">
<!-- Try First Way to Print Results -->
Id: <span ng-bind="metric.id"></span></br>
Name:<input type="text" ng-model="metric.metadata.name" /></br>
<!-- Try Second Way to Print Results -->
<p data-ng-repeat="thing in ::MEC.metric track by $index">
{{$index + 1}}. <span>{{thing.metadata.name}}</span>
<span class="glyphicon glyphicon-info-sign"></span>
</a>
</p>
<!-- Try Third Way to Print Results -->
Id: <span ng-bind="Metric.metricId"></span></br>
Id: <span ng-bind="Metric.id"></span></br>
Id: <span ng-bind="metricService.id"></span></br>
<!-- Try Fourth Way to Print Results -->
Id: <strong>{{::MEC.metric.id}}</strong></br>
Name: <strong>{{::MEC.metric.metadata.name}}</strong></br>
Height: <strong>{{::MEC.metric.type}}</strong>
</div>
metricController.js
(function () {
'use strict';
angular.module('app.metric', ['app.metricService', 'app.metricManager'])
.controller('MetricController', MetricController)
MetricController.$inject = ['$scope', 'metricManager', '$log'];
function MetricController($scope, metricManager, $log) {
metricManager.getMetric(0).then(function(metric) {
$scope.metric = metric
$log.info('$scope.metric printed to console below:');
$log.info($scope.metric);
})
}
})();
metricService.js
(function () {
'use strict';
angular.module('app.metricService', [])
.factory('Metric', ['$http', '$log', function($http, $log) {
function Metric(metricData) {
if (metricData) {
this.setData(metricData);
}
// Some other initializations related to book
};
Metric.prototype = {
setData: function(metricData) {
angular.extend(this, metricData);
},
delete: function() {
$http.delete('https://n4nite-api-n4nite.c9users.io/v1/imm/metrics/' + metricId);
},
update: function() {
$http.put('https://n4nite-api-n4nite.c9users.io/v1/imm/metrics/' + metricId, this);
},
hasMetadata: function() {
if (!this.metric.metadata || this.metric.metadata.length === 0) {
return false;
}
return this.metric.metadata.some(function(metadata) {
return true
});
}
};
return Metric;
}]);
})();
metricManager.js
(function () {
'use strict';
angular.module('app.metricManager', [])
.factory('metricManager', ['$http', '$q', 'Metric', function($http, $q, Metric) {
var metricManager = {
_pool: {},
_retrieveInstance: function(metricId, metricData) {
var instance = this._pool[metricId];
if (instance) {
instance.setData(metricData);
} else {
instance = new Metric(metricData);
this._pool[metricId] = instance;
}
return instance;
},
_search: function(metricId) {
return this._pool[metricId];
},
_load: function(metricId, deferred) {
var scope = this;
$http.get('https://n4nite-api-n4nite.c9users.io/v1/imm/metrics/' + metricId).then(successCallback, errorCallback)
function successCallback(metricData){
//success code
var metric = scope._retrieveInstance(metricData.id, metricData);
deferred.resolve(metric);
};
function errorCallback(error){
//error code
deferred.reject();
}
},
/* Public Methods */
/* Use this function in order to get a metric instance by it's id */
getMetric: function(metricId) {
var deferred = $q.defer();
var metric = this._search(metricId);
if (metric) {
deferred.resolve(metric);
} else {
this._load(metricId, deferred);
}
return deferred.promise;
},
/* Use this function in order to get instances of all the metrics */
loadAllMetrics: function() {
var deferred = $q.defer();
var scope = this;
$http.get('ourserver/books')
.success(function(metricsArray) {
var metrics = [];
metricsArray.forEach(function(metricData) {
var metric = scope._retrieveInstance(metricData.id, metricData);
metrics.push(metric);
});
deferred.resolve(metrics);
})
.error(function() {
deferred.reject();
});
return deferred.promise;
},
/* This function is useful when we got somehow the metric data and we wish to store it or update the pool and get a metric instance in return */
setMetric: function(metricData) {
var scope = this;
var metric = this._search(metricData.id);
if (metric) {
metric.setData(metricData);
} else {
metric = scope._retrieveInstance(metricData);
}
return metric;
},
};
return metricManager;
}]);
})();
Snippet from App.routes
.state('root.metric', {
url: 'metric',
data: {
title: 'Metric',
breadcrumb: 'Metric'
},
views: {
'content#': {
templateUrl: 'core/features/metric/metric.html',
controller: 'MetricController',
controllerAs: 'MEC'
}
}
})
Console
You are mixing two concepts controller alias and $scope, in your case you are creating controller alias as MEC using controllerAs. If you are using controller alias then this will work fine for you :
function MetricController($scope, metricManager, $log) {
var MEC = this;
metricManager.getMetric(0).then(function(metric) {
MEC.metric = metric
$log.info('$scope.metric printed to console below:');
$log.info($scope.metric);
})
}
If you don't want to use controller alias and share data between view and controller via $scope then in your view you should use something like this {{::metric.metadata.name}} and controller function should stay as it is.
PS: If you are using alias then MEC in var MEC = this can be MEC or abc or any name you like but convention is to use var vm = this and controllerAs: 'vm'. If you have controllerAs: 'xyz' then in your view xyz should be used to access model.
Problem with your view HTML, you need to use proper Angular expressions while binding. When you want use ::MEC alias name you need to mark your controller with as keyowrd, like ng-controller="xyz as MEC". And checkout working Plunker
<div class="panel panel-primary">
<div class="panel-body">
<!-- Try First Way to Print Results -->
Id: <span ng-bind="metric.id"></span>
<br> Name1:
<input type="text" ng-model="metric.metadata.name" />
<br><br><br><br>
<!-- Try Second Way to Print Results -->
<p data-ng-repeat="thing in [metric] track by $index">
{{$index + 1}}. <span>{{thing.metadata.name}}</span>
<span class="glyphicon glyphicon-info-sign"></span>
</p><br><br><br>
<!-- Try Third Way to Print Results -->
Id: <span ng-bind="metric.metricId"></span>
<br> Id: <span ng-bind="metric.id"></span>
<br><br><br>
<!-- Try Fourth Way to Print Results -->
Id: <strong>{{::metric.id}}</strong>
<br> Name: <strong>{{::metric.metadata.name}}</strong>
<br> Height: <strong>{{::metric.type}}</strong>
</div>
</div>
I am working on a directive to create a custom dropdown. I have managed to bring the directive code to get it replaced.
Below is the directive tag in the html
<dropdown placeholder="Country.." list="count" selected="item" property="name"></dropdown>
Below is the html template that replaces the directive tag
<div class="dropdown-container" ng-class="{ show: listVisible }">
<div class="dropdown-list">
<div>
<div ng-repeat="item in list" ng-click="select(item)" ng-class="{ selected: isSelected(item) }">
<span>{{property !== undefined ? item[property] : item}}</span>
<i class="fa fa-check"></i>
</div>
</div>
</div>
Below is the angularjs directive
App.directive("dropdown", function ($rootScope) {
return {
restrict: "E",
templateUrl: "/app/dropdownTemplate.html",
scope: {
placeholder: "#",
list: "=",
selected: "=",
property: "#"
},
link: function (scope) {
scope.listVisible = false;
scope.isPlaceholder = true;
scope.select = function (item) {
scope.isPlaceholder = false;
scope.selected = item;
};
scope.isSelected = function (item) {
return item[scope.property] === scope.selected[scope.property];
};
scope.show = function () {
scope.listVisible = true;
};
$rootScope.$on("documentClicked", function (inner, target) {
console.log($(target[0]).is(".dropdown-display.clicked") || $(target[0]).parents(".dropdown-display.clicked").length > 0);
if (!$(target[0]).is(".dropdown-display.clicked") && !$(target[0]).parents(".dropdown-display.clicked").length > 0)
scope.$apply(function () {
scope.listVisible = false;
});
});
scope.$watch("selected", function (value) {
scope.isPlaceholder = scope.selected[scope.property] === undefined;
scope.display = scope.selected[scope.property];
});
}
}
});
I am trying to get the list of countries from another controller whose function is as follows,
addressService.getCountries().success(function (response) {
angular.copy(response, $scope.list);
}
How do I bind the values from the controller to my directive when the page loads?
EDIT: What do I do as the directive loads before the function addressService.getCountries() gets called?
you can make a factory that will be useful throughout project to make to make a variable accessible whenever and wherever, in any controller or in any directive you want to use that variable you can easily access
this is how we can make factory,
(function() {
"use strict";
angular.module('dataModule',[])
.factory('datafactory',function(){
return {
credentials : {},
};
});
})();
After making this factory you can inject this module in your module and this factory in your controller
whenever you want you can use this like
addressService.getCountries().success(function (response) {
datafactory.country = response
}
in your controller
$scope.myvar =datafactory.country
in future you use this factory for any such variable that will be accessible everywhere for example you want to store list of state globally
datafactory.state =["abc","def","xyz"]
I already have seem other topics with this kind of issue, but no one could help me... So here is my issue:
I have a navbar with a button for search, this buttons makes and get request from a webservice and returns a json object which must be apply to fill an table list. The problem is, my button and my table are in separated controllers, and it does work like I expected.
var app = angular.module('clientRest', []).controller('lista', ['$scope', 'loadLista', function($scope, loadLista) {
$scope.contatos = loadLista.getContatos();
}]).controller('pesquisa', ['$scope', '$http', 'loadLista', function($scope, $http, loadLista) {
$scope.listar = function() {
$http.get("http://localhost/wsRest/index.php/contato").success(function(response) {
loadLista.setContatos(response);
});
};
}]).service('loadLista', function() {
var contatos = [];
return {
getContatos: function() {
return contatos;
},
setContatos: function(c) {
contatos = c;
}
};
});
My code...
When I call listar() from pesquisa controller I need to send received data to $scope.contatos from lista controller to make my ng-repeat work, everything with a single click.
How can I do it?
Thanks everyone
Better to use a service to share data between two controllers / modules as this might be the best approach. You can refer the code segment given below to understand the concept.
angular.module('app.A', [])
.service('ServiceA', function() {
this.getValue = function() {
return this.myValue;
};
this.setValue = function(newValue) {
this.myValue = newValue;
}
});
angular.module('app.B', ['app.A'])
.service('ServiceB', function(ServiceA) {
this.getValue = function() {
return ServiceA.getValue();
};
this.setValue = function() {
ServiceA.setValue('New value');
}
});
In order to trigger the data receipt event, you may use
Broadcast / emit messages - with #broadcast / #emit
An angular promise with a call back
Controller initiation function to reload the previously read information from a service
.controller('MyController', function($scope, ServiceA) {
$scope.init = function() {
$scope.myValue = ServiceA.getValue();
};
// Call the function to initialize during Controller instantiation
$scope.init();
});
Use $rootScope.$emit to emit a change event when setting the variable and use $on to get the value in the lista controller. I used customListAr here just to demostrate a button click. Does this help?
var app = angular.module('clientRest', [])
.controller('lista', ['$scope', 'loadLista', '$rootScope',
function($scope, loadLista, $rootScope) {
console.log(loadLista);
$scope.contatos = loadLista.getContatos();
$rootScope.$on('change', function() {
$scope.contatos = loadLista.getContatos();
});
}
])
.controller('pesquisa', ['$scope', '$http', 'loadLista',
function($scope, $http, loadLista) {
$scope.listar = function() {
$http.get("http://localhost/wsRest/index.php/contato").success(function(response) {
loadLista.setContatos(response);
});
};
$scope.customListAr = function() {
loadLista.setContatos(["item 1" , "item 2", "item 3"]);
}
}
])
.service('loadLista', ['$rootScope',
function($rootScope) {
var contatos = [];
return {
getContatos: function() {
return contatos;
},
setContatos: function(c) {
contatos = c;
$rootScope.$emit('change');
}
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="clientRest">
<div ng-controller="lista">
<ul>
<li ng-repeat="a in contatos">{{a}}</li>
</ul>
</div>
<div ng-controller="pesquisa">
<button ng-click="customListAr()">Click Me</button>
</div>
</div>
Your problem is that when you do $scope.contatos = loadLista.getContatos(); you are setting a static value, and angular is unable to effectively create a watcher for that object because your setContatos method is creating a new object each time. To get around this, have the controller's scope hold a reference to the parent object and then it will automatically have a watcher on that object.
var app = angular.module('clientRest', [])
.controller('lista', ['$scope', 'loadLista', function($scope, loadLista) {
$scope.contatos = loadLista.contatos;
}])
.controller('pesquisa', ['$scope', '$http', 'loadLista', function($scope, $http, loadLista) {
$scope.listar = function() {
$http.get("http://localhost/wsRest/index.php/contato"
).success(function (response) {
loadLista.contatos.data = response;
});
};
}])
.service('loadLista', function() {
var lista = {
contatos: {},
};
return lista;
});
// view:
<ul>
<li ng-repeat="contato in contatos.data">
{{ contato }}
</li>
</ul>
I'm struggling to figure out how to do this. Hope anyone can help :)
I have multiple controllers in my Angular app. Like titleCtrl and SettingsCtrl
I have a service which holds a variable like this:
var myVar = {title: 'test', settings: {color: 'black', font: 'verdana'}};
I'm making a $http.get request to update the "myVar" variable from the server.
The question is, how do I update the $scope.title in titleCtrl and $scope.settings in SettingsCtrl AFTER the http request has finished? I know how to do it in a single controller, but how do I update the $scopes in multiple controllers?
Use a watch on that variable in the service. When its updated, then update your values in controller scope. Here's an example:
Inside your controller, you can watch a var myVar on YourService and when it changes, update a variable called myVarInController with the value it changed to.
$scope.$watch(
// This function returns the value being watched.
function() {
return YourService.myVar;
},
// This is the change listener, called when the value returned above changes
function(newValue, oldValue) {
if ( newValue !== oldValue ) {
$scope.myVarInController = newValue;
}
}
);
Just in you service create a object when you get data from you server copy it to that object, so all your controllers can reference to that object.
Please see here http://plnkr.co/edit/j25GJLTHlzTEVS8HNqcA?p=preview
JS:
var app = angular.module('plunker', []);
app.service('dataSer', function($http) {
var obj = {};
getData = function() {
$http.get("test.json").then(function(response) {
angular.copy(response.data, obj);
});
}
return {
obj: obj,
getData: getData
};
});
app.controller('MainCtrl', function($scope, dataSer) {
$scope.data = dataSer;
$scope.get = function() {
$scope.data.getData()
}
});
app.controller('SecondCtrl', function($scope, dataSer) {
$scope.data = dataSer;
});
HTML:
<div ng-controller="MainCtrl">
<button ng-click="get()">get data</button>
<p>Fist Controller:
<br/>{{ data.obj.title}}</p>
</div>
<div ng-controller="SecondCtrl">
<p>Second Controller:
<br/>{{data.obj.settings}}</p>
</div>
Use both factory and service to pass value to two controllers. This is the only way to pass value
angular.module('mulipleCtrlApp', [])
.service('shareService', function () {
return {
title: 'test',
settings: {
color: 'black',
font: 'verdana'
}
};
})
.controller('titleCtrl', function ($scope, shareService) {
$scope.myVar = shareService;
$scope.testchange = function () {
$scope.myVar.title = 'Completed test';
};
})
.controller('settingCtrl', function ($scope, shareService) {
$scope.myVar = shareService;
});
Egghead Link
Jsfiddler Link example
Make your service return promise object.
Then in controller you can define a success call back to fetch title in one and settings in
another controller once the promise is resolved.
Code to use promises
In your service class:
var factory = {};
var factory.fetchData = function () {
return $http({method: 'GET', url: '/someUrl'});
}
return factory;
In controller 1:
$scope.getData = function(){
factory.fetchData().success(response){
$scope.title = response.title;
}
}
Similarly you can update controller 2, to set settings data.
I've found a better and easier maintainable solution in my opinion. Simply do the following to achieve to-way data-binding between one (or more) controller(s) with a service:
Lets assume you fetch (i.e. $http) and store data in your service (serviceName) in the variable serviceData.
In your controller reference the service like this to achieve to-way data-binding:
$scope.data = serviceName
In your view/html bind to the data properties like this:
<input ng-model="data.serviceData.title">
Thats it! :) When your serviceData variable updates the view/scope does as well. This will work with multiple controllers.
How do I broadcast a message between controllers?
Here is what I have tried:
function Ctrl1($scope) {
$scope.$broadcast('Update');
}
Ctrl1.$inject = ['$scope'];
function Ctrl2($scope) {
$scope.updated = false;
$scope.$on('Update', function () {
$scope.updated = true;
});
}
Ctrl2.$inject = ['$scope'];
To see it running: view the Plnkr.
Instead of using $broadcast() a shared service and $watch() might be a better alternative.
var myApp = angular.module('myApp', []);
myApp.factory("MyService", function () {
return {
updated: false
};
});
function Ctrl1($scope, MyService, $timeout) {
$timeout(function () { //Some work occurs and sets updated to true
MyService.updated = true;
}, 1000)
}
Ctrl1.$inject = ['$scope', "MyService", "$timeout"];
function Ctrl2($scope, MyService) {
$scope.$watch(function () {
return MyService.updated;
}, function (oldValue, newValue) {
$scope.updated = MyService.updated;
});
}
Ctrl2.$inject = ['$scope', "MyService"];
Updated Plnkr
It depends on the scopes hierarchy and therefore on where you bootstrap your Ctrl1 and Ctrl2 in your dom.
Say Ctrl1 is the parent of Ctrl2. $broadcast will transmit the event to child scopes: Ctrl2 in this case will notice it (use $on).
If you need to transmit an event from Ctrl2 to Ctrl1, use $emit that transmit the event to parent scopes.