Currently I have a factory and a controller. The factory updates with items from an endpoint and the number of pages of data. My data array is being recognized fine, but my pageCount (int) update never actually changes. I have checked to make sure it isn't actually returning 0.
.factory('myService', function($http) {
return {
data: [],
update: update,
pageCount: 0
};
function update() {
return $http.get('path/to/endpoint')
.then(function(res) {
angular.copy(res.data.itemsArray, this.data);
angular.copy(res.data.pageCount, this.pageCount);
// also tried this.pageCount = res.data.pageCount;
}.bind(this));
}
})
.controller('myCtrl', function(myService) {
myService.update();
$scope.data = myService.data;
$scope.pageCount = myService.pageCount;
});
<div>{{pageCount}}</div> // This does not update at all
<div ng-repeat="item in data">{{item}}</div> // This works fine
You are returning a promise with the update() function so you can use then to handle the result (this will give a more consistent result):
.factory('myService', function($http) {
return {
update: update
};
function update() {
return $http.get('path/to/endpoint')
.then(function(res) {
var result = {
data: [],
update: update,
pageCount: 0
};
result.data = res.data.itemsArray;
result.pageCount = res.data.pageCount;
return result;
});
}
})
.controller('myCtrl', function(myService) {
$scope.data = [];
myService.update().then(function(result) {
$scope.data = result.data;
$scope.pageCount = result.pageCount;
});
});
When you assign the primitive from the service, the reference was lost. Try fetching pageCount from a getter in the service instead. Trying to override the service value is a totally different value to the one in the scope.
It doesn't happen to the array because it's a reference and you used copy.
factory('myService', function($http) {
var pc = 0;
return {
data: [],
update: update,
pageCount: function() {
return pc;
}
};
function update() {
return $http.get('path/to/endpoint')
.then(function(res) {
angular.copy(res.data.itemsArray, this.data);
pc = res.data.pageCount;
}.bind(this));
}
})
.controller('myCtrl',
function(myService) {
myService.update();
$scope.data = myService.data;
$scope.pageCount = myService.pageCount;
});
<div>{{pageCount()}}</div> // This does not update at all
<div ng-repeat="item in data">{{item}}</div> // This works fine
Related
I'm doing a app for Ionic. Based on answers in a question made by me at $http.get not working at .factory, I wrote the following code:
services.js
angular.module('starter.services', [])
.factory('Chats', function($http) {
// Might use a resource here that returns a JSON array
var factory = {
chats: null,
all: function() { return chats; },
get: function(chatId) {
for (var i = 0; i < chats.length; i++) {
if (chats[i].id === parseInt(chatId)) {
return chats[i];
}
}
return null;
}
};
$http.get("http://lucassmuller.com/work/projetoblog/api.php?action=posts").then(function(data) {
factory.chats = data;
console.log('data ok');
});
return factory;
});
controllers.js
angular.module('starter.controllers', [])
.controller('DashCtrl', function($scope) {})
.controller('ChatsCtrl', function($scope, Chats) {
// With the new view caching in Ionic, Controllers are only called
// when they are recreated or on app start, instead of every page change.
// To listen for when this page is active (for example, to refresh data),
// listen for the $ionicView.enter event:
//
//$scope.$on('$ionicView.enter', function(e) {
//});
$scope.chats = Chats.all();
})
.controller('ChatDetailCtrl', function($scope, $stateParams, Chats) {
$scope.chat = Chats.get($stateParams.chatId);
})
.controller('AccountCtrl', function($scope) {
$scope.settings = {
enableFriends: true
};
});
But when I run it to do a ng-repeat with data, shows an error saying that chats is not defined. How can I fix it?
The request that you make to any HTTP resource won't be resolved right away. It returns a promise object which is literally a promise saying that Hey, I don't know the result right now, but I will give you a result later regardless it succeeds or not. The promise object is resolved in the controller using its .then property which takes two functions as parameters, onSuccess and onFailure.
In your case, this is how you do it.
Factory/Service
.factory('Chats', function($http) {
// returning a promise from the service
var chats = $http.get("http://lucassmuller.com/work/projetoblog/api.php?action=posts");
var factory = {
chats: null,
all: function() { return chats; },
get: function(chatId) {
for (var i = 0; i < chats.length; i++) {
if (chats[i].id === parseInt(chatId)) {
return chats[i];
}
}
return null;
}
};
return factory;
});
Controller
.controller('ChatsCtrl', function($scope, Chats) {
// resolving the promise in the controller
Chats.all().then(function (res){ // onSuccess, called when response is successfully recieved
var chats = res.data
console.log(chats);
$scope.chats = chats;
}, function (err){ // onFailure, called when error response
console.log(err);
});
})
Check Angular Documentation for $http promises.
I have code:
angular.module('admin', [])
.provider('users', function () {
this.users = 'default';
this.$get = function () {
var that = this;
return {
getUsers: function () {
return that.users;
}
}
};
})
.run(function (users, $http) {
users.users = $http('url'); // and others
})
.controller('test', function ($scope, users) {
$scope.users = users.getUsers();
});
I would like to intitalize data in .run() method (I can't use .config() method because it doesn't let to pass any services like $http). I found .run() method, but this code doesn't work... Data aren't saved in provider. Official documentation says:
"Execute this function after injector creation. Useful for application initialization."
I think it's best way to initialize data.
You may want to use an Angular Factory/Service for this kind of need. That is what I do. And pass that into the application. That service will be your singleton or source of truth about the dat.
angular.module('myData.services', [])
.factory('myData', ['$rootScope', '$http' , function($rootScope,$http) {
var factory = {
myData : {}
};
$http('/api/call', function(apiData) {
factory.myData = apiData;
});
return factory;
}]);
You could then use this in your controllers:
angular.module('myApp.controllers', [])
.controller('myCtrl', ['myData', '$scope', function(myData, $scope){
$scope.users = myData;
}]);
Check out the documentation on services: https://docs.angularjs.org/guide/services
Second attempt
angular.module('admin', [])
.factory('users', function ($http) {
var users = {};
var data = [];
$http.get('database.php')
.then(function (response) {
data = response.data;
});
users.getData = function () {
return data;
};
return users;
})
.controller('test', function ($scope, users) {
console.log(users.getData());
});
I would like to have data private. Empty Array returned, reponse comes with all data.
Provider configuration can be doable inside config block only, you can't do that inside run block
Though I don't find a reason to load users object while configuring app. I'd say that you should use either service/factory for this.
Code
angular.module('admin', [])
.service('users', function($http, $q) {
var users = [];
//make an get call to fetch users
function getUsers() {
return $http.get('database.php')
.then(function(response) {
data = response.data;
});
}
//will make a call if users aren't there
this.getData = function() {
// Handled below two conditions
// 1. If users aren't fetched the do an Ajax
// 2. If last ajax doesn't return a data then DO it again..
if (users.length > 0)
return $q.resolve(data); //do return data using dummy promise
return getUsers();
};
return users;
})
.controller('test', function($scope, users) {
users.getData().then(function(data){
console.log(data);
});
});
This seems so simple yet I have been banging my head against it for hours now...
I have an angular 1.2.29 module that uses ui-router to resolve a data dependency from a service and inject it into a named nested view controller. The service returns my data object exactly as desired and I can inject that returned data into my view controller and log it again with no issue.
What I am baffled by is that if I inspect the object in the console, I can see the array I am trying to access on one of the nested properties of the object I injected from the service (this is from within the context of the controller I am trying to use it in).
However, if I try and pass that array of data into another method in the same controller (in this case a d3 method to draw a graph) - I get nothing but a empty string where the array is.
I can access every other property on the object I passed into the method just fine, and so long as I don't try and access the array directly it is 'visible' to me in my logging, but when I try and pass the array directly in I get an empty string.
I have tried everything I can from making a deep copy or pushing the array into a new object and accessing it that way, trying to cast that property as an array (again into a new object), and a whole host of other random hacks I have run across all to no avail.
I feel like I am missing something incredibly trivial here...
These three logs are called on the same object from within the same method one after the other and you can see the issue.
I am trying to pass the data.datasources.tabledata.datum array to another method that expects an array, but all I can get is the empty string.
Note: 'data' is the object returned from the service resolve at the state transition and injected into the view controller.
> console.log(data);
// Expected object.
> Object {datasources: Object}
datasources: Object
tabledata: Object
datum: Array[9383]
source: "./data/BSGAM_Heads_Wells_Drains_Zones_Master.csv"
__proto__: Object
__proto__: Object
__proto__: Object
> console.log(data.datasources);
// Expected object.
> Object {tabledata: Object}
tabledata: Object
datum: Array[9383]
source: "./data/BSGAM_Heads_Wells_Drains_Zones_Master.csv"
__proto__: Object
__proto__: Object
> console.log(data.datasources.tabledata);
// String instead of array... WTH???
> Object {source: "./data/BSGAM_Heads_Wells_Drains_Zones_Master.csv", datum: ""}
> console.log(data.datasources.tabledata.datum);
// Returns NOTHING - not even undefined... just an empty log line #_#
>
UPDATE: Here is the service loading the data (it is my own)
(function() {
'use strict';
angular
.module('mcsdss.providers')
.factory('FormulationRetrieval', FormulationRetrieval);
FormulationRetrieval.$inject = ['$http'];
function FormulationRetrieval($http) {
FormulationRetrieval.getFormulation = function (target) {
var promise = $http
.get(target)
.then(function (response) {
return FormulationRetrieval.configureFormulation(response.data);
});
return promise;
};
FormulationRetrieval.configureFormulation = function (f) {
FormulationRetrieval.formulationContainer = f;
FormulationRetrieval.loadFormulationSourceData(FormulationRetrieval.formulationContainer);
FormulationRetrieval.loadFormulationGisData(FormulationRetrieval.formulationContainer);
return FormulationRetrieval.formulationContainer;
};
FormulationRetrieval.loadFormulationSourceData = function (fc) {
function parseFormulationDatasource(fd, destination) {
Papa.parse(fd, {
complete: function(results) {
destination.datum = results.data;
}
});
}
function loadData(target) {
var promise = $http
.get(target.source)
.then(function (response) {
parseFormulationDatasource(response.data, target);
});
return promise;
}
var datasources = [fc.datagridConfig.datasources.tabledata, fc.graphConfig.datasources.graphContextData];
angular.forEach(datasources, loadData);
};
FormulationRetrieval.loadFormulationGisData = function (fc) {
function loadGeodata(target) {
angular.forEach(target, function(value, key) {
var promise = $http
.get(value.source)
.then(function (response) {
value.datum = response.data;
});
return promise;
});
}
var datasources = [fc.mapConfig.datasources.geojson];
angular.forEach(datasources, loadGeodata);
};
FormulationRetrieval.getAnalysisConfig = function (fc) {
var analysisConfig = fc.analysisConfig;
return analysisConfig;
};
FormulationRetrieval.getMaufConfig = function (fc) {
var maufConfig = fc.maufConfig;
return maufConfig;
};
FormulationRetrieval.getGraphConfig = function (fc) {
var graphConfig = fc.graphConfig;
return graphConfig;
};
FormulationRetrieval.getTableConfig = function (fc) {
var tableConfig = fc.datagridConfig;
return tableConfig;
};
FormulationRetrieval.getMapConfig = function (fc) {
var mapConfig = fc.mapConfig;
return mapConfig;
};
return FormulationRetrieval;
}
})();
Here is the ui-router resolve on the state:
(function() {
'use strict';
angular
.module('analyze')
.config(DashboardRoutes);
DashboardRoutes.$inject = ['$stateProvider'];
function DashboardRoutes($stateProvider) {
// Define states.
var analyze_state = {
abstract: true,
url: '/analyze',
templateUrl: 'modules/analyze/views/analyze.client.view.html',
controller: 'AnalyzeViewController',
controllerAs: 'analyze',
data: {
title: 'Analyze'
},
resolve: {
analysisData: function(FormulationRetrieval) {
return FormulationRetrieval.getFormulation('./data/formulations/bs.formulation.json');
},
analysisConfig: function(FormulationRetrieval, analysisData) {
return FormulationRetrieval.getAnalysisConfig(analysisData);
},
maufConfig: function(FormulationRetrieval, analysisData) {
return FormulationRetrieval.getMaufConfig(analysisData);
},
tableConfig: function(FormulationRetrieval, analysisData) {
return FormulationRetrieval.getTableConfig(analysisData);
},
graphConfig: function(FormulationRetrieval, analysisData) {
return FormulationRetrieval.getGraphConfig(analysisData);
},
mapConfig: function(FormulationRetrieval, analysisData) {
return FormulationRetrieval.getMapConfig(analysisData);
}
}
};
var analyze_layout_state = {
abstract: false,
url: '',
views: {
'graph': {
templateUrl: 'modules/analyze/views/analyze.graph.client.view.html',
controller: 'GraphViewController'
},
'map': {
templateUrl: 'modules/analyze/views/analyze.map.client.view.html',
controller: 'MapViewController'
},
'filters': {
templateUrl: 'modules/analyze/views/analyze.filters.client.view.html',
controller: 'FiltersViewController',
controllerAs: 'filters'
},
'datatable': {
templateUrl: 'modules/analyze/views/analyze.datatable.client.view.html',
controller: 'DatatableViewController',
controllerAs: 'datatable'
}
}
};
// Populate provider.
$stateProvider
.state('analyze', analyze_state)
.state('analyze.layout', analyze_layout_state);
}
})();
And here is the controller receiving the injected data:
(function() {
'use strict';
angular
.module('analyze')
.controller('AnalyzeViewController', AnalyzeViewController);
AnalyzeViewController.$inject = ['$rootScope', '$scope', '$state', '$location', 'Authentication', 'httpq', 'analysisData', 'maufConfig', 'tableConfig', 'graphConfig', 'mapConfig'];
function AnalyzeViewController($rootScope, $scope, $state, $location, Authentication, $httpq, analysisData, analysisConfig, maufConfig, tableConfig, graphConfig, mapConfig) {
// This provides Authentication context.
$scope.authentication = Authentication;
$scope.currentRoute = 'Analyze';
// console.log($scope.currentRoute);
// ALL OF THESE ARE INJECTED AND APPEAR TO BE CORRECT IN CONSOLE.
// console.log(analysisData);
// console.log(analysisConfig);
// console.log(maufConfig);
// console.log(tableConfig);
// console.log(graphConfig);
// console.log(mapConfig);
// NOTE: At one point I was loading the data through promises inside the
//controller, but moved it into the state resolve for better SOC.
//Strangely the $broadcast of the exact same value done here in the finally()
//block of the $httpq method works - using the new injected data object!
//And yet, the same $broadcast on $stateChangeSuccess (which DOES send the correct
//data into the listening subscribers if I only send the entire object) sends only
//empty string if I specify the array.
// Manual data loading.
$scope.sourceFile_A = './data/BSGAM_Heads_Wells_Drains_Zones_Master.csv';
$httpq.get($scope.sourceFile_A)
.then(function(data) {
// ...removed because not used.
})
.catch(function(data, status) {
console.error('Load error', response.status, response.data);
})
.finally(function() {
// Works here using the injected resolved promise, does not work in stateChangeSuccess... WTH??
$scope.$broadcast('analysisDataLoaded', analysisData.datagridConfig.datasources.tabledata.datum);
});
$scope.$on('$stateChangeSuccess', function() {
// EXACT SAME BROADCAST AS ABOVE FAILS HERE - EMPTY STRING.
// $scope.$broadcast('analysisDataLoaded', analysisData.datagridConfig.datasources.tabledata.datum);
});
// extra code removed...
})();
Lastly the Nested View Controller (one of 4) that I am trying to get to use the data via the $broadcast from the abstract parent. I can also access all the injected objects here directly without the broadcast but I am trying to decouple as much as possible as several views need to update in sync from the broadcast.
(function() {
'use strict';
angular
.module('analyze')
.controller('DatatableViewController', DatatableViewController);
DatatableViewController.$inject = ['$scope', 'Authentication', '$filter', 'ngTableParams', 'AnalysisDataFactory', 'tableConfig'];
function DatatableViewController($scope, Authentication, $filter, ngTableParams, AnalysisDataFactory, tableConfig) {
// This provides Authentication context.
$scope.authentication = Authentication;
// Expose public API.
$scope.headerFilter = headerFilter;
$scope.datasetOrder = datasetOrder;
$scope.rowClicked = rowClicked;
$scope.decorateSiblings = decorateSiblings;
$scope.clearSiblings = clearSiblings;
$scope.updateView = updateView;
// Private members.
$scope.headerdata = [];
$scope.tabledata = [];
$scope.suf01 = 0;
$scope.suf02 = 0;
$scope.suf03 = 0;
$scope.muf = 0;
$scope.$on('analysisDataLoaded', function(event, args) {
console.log('analysisDataLoaded...', event, args);
$scope.updateView(args);
// THIS IS WHAT I WANT TO DO INSTEAD:
// $scope.updateView(tableConfig); // Not working yet.
});
function headerFilter(target) {
return target.visible;
}
function datasetOrder(key) {
angular.forEach($scope.headers, function(target) {
// console.log('key='+key);
if (target.data == key) {
if (target.visible) {
return target.order;
}
}
});
return -1;
}
function rowClicked(target) {
for (var key in target) {
if (target.hasOwnProperty(key)) {
console.log(key + ' -> ' + target[key]);
}
}
}
function decorateSiblings(target) {
// console.log('data row touched, sending emission.');
$scope.$emit('currentDatatableTarget', target);
}
function clearSiblings(target) {
// console.log('datarow cleared, sending all clear.');
$scope.$emit('clearDatatableTarget', target);
}
function updateView(data) {
// ngTable
$scope.dataTable = new ngTableParams({
page: 1,
count: 10
}, {
total: $scope.tabledata.length,
counts: [10, 25, 50, 100, 250],
defaultSort: 'asc',
getData: function($defer, params) {
$scope.data = params.sorting() ? $filter('orderBy')($scope.tabledata, params.orderBy()) : $scope.tabledata;
$scope.data = params.filter() ? $filter('filter')(data, params.filter()) : data;
$scope.data = data.slice((params.page() - 1) * params.count(), params.page() * params.count());
$defer.resolve($scope.data);
}
});
}
}
})();
Something simple in here - probably a JS gotcha that I am overlooking.... Thanks in advance for any input!!
Turns out it was from not properly deferring and resolving the promises before trying to bind to them. I still have not identified exactly where that timing is going wrong in my code, but I can now intermittently load the data as I intended and can see that it is clearly a function of async inconsistencies. The $broadcast event is working just fine (when the async data loads properly).
I am trying to push a new row of data to a table, after submitting the form. However, the table, which is called UrlListCtrl is different from the form, which is UrlFormCtrl.
function UrlFormCtrl($scope, $timeout, UrlService) {
$scope.message = '';
var token = '';
$scope.submitUrl = function(formUrls) {
console.log('Submitting url', formUrls);
if (formUrls !== undefined) {
UrlService.addUrl(formUrls).then(function(response){
$scope.message = 'Created!';
// I need to update the view from here
});
} else {
$scope.message = 'The fields were empty!';
}
}
In UrlFormCtrl, I am sending an array to the database to be stored, afterwards I'd like to update the view, where UrlListCtrl handles it.
function UrlListCtrl($scope, $timeout, UrlService){
UrlService.getUrls().then(function(response){
$scope.urls = response.data;
});
}
I am trying to push the new data to $scope.url. Here is the service:
function UrlService($http) {
return {
addUrl: addUrl,
getUrls: getUrls
}
function addUrl(formUrls) {
console.log('adding url...');
return $http.post('urls/create', {
original_url: formUrls.originalUrl,
redirect_url: formUrls.redirectUrl
});
}
function getUrls() {
return $http.get('urls/get');
}
}
I'm still trying to understand Angular, so this is pretty complicated for me. How can I update $scope.urls from within UrlFormCtrl?
I am not sure that I understand your question completely but I will try to answer it anyways =).
So you are trying to update a variable whose value was changed in another controller?
That is where service can be useful.
Here are the basic steps:
In the service, you have that variable:
myApp.service('ServiceName', ['$http', function(){
var urls = [];
return{
urls: urls
}
}])
Put $watch in one controller, where you want to get that new value:
myApp.controller('FirstController', function(...){
$scope.$watch(function () {
return ServiceName.urls;
}, function (newVal) {
$scope.urls = newVal;
});
})
Then, you change it from another controller:
myApp.controller('SecondController', function(...){
ServiceName.urls.push('newValue');
})
This should do it. $scope.urls will be updated in the first controller even if it was changed in the second.
The concept of $watch may be new to you. So it basically executes a callback function, whenever the first function returns a new value. That is, whenever the variable that is being watched changes.
In your case:
You will have a variable inside your service:
function UrlService($http) {
var urls = [];
function addUrl(formUrls) {
console.log('adding url...');
return $http.post('urls/create', {
original_url: formUrls.originalUrl,
redirect_url: formUrls.redirectUrl
});
}
function getUrls() {
return $http.get('urls/get');
}
return {
addUrl: addUrl,
getUrls: getUrls
urls: urls
}
}
Put a $watch inside UrlListCtrl:
function UrlListCtrl($scope, $timeout, UrlService){
$scope.$watch(function () {
return UrlService.urls;
}, function (newVal) {
$scope.urls = newVal;
});
}
Then, change the value of urls from UrlFormCtrl:
$scope.submitUrl = function(formUrls) {
if (formUrls !== undefined) {
UrlService.addUrl(formUrls).then(function(response){
$scope.message = 'Created!';
UrlService.urls = response['urls'];
});
} else {
$scope.message = 'The fields were empty!';
}
}
The $watch you put inside UrlListCtrl will insure that the new value will be assigned to $scope.urls inside UrlFormCtrl.
I have the following controllers:
angular.module('app').controller('ParentCtrl', function(Service) {
var list = []
var init = function () {
Service.query().success(function() {
list = Service.getList();
});
}
});
angular.module('app').controller('ChildCtrl', function(Service) {
var list = []
var init = function () {
list = Service.getList();
}
});
angular.module('app').factory('Service', function($http) {
list = []
return {
query : function() {
$http.get('/path/to/my/api').success(function(data){
list = data
})
},
getList: function() {
return list;
}
}
});
My HTML is as follows:
<div ng-controller="ParentCtrl as parent">
<div ng-controller="ChildCtrl as child">
</div>
</div>
So basically when I receive the AJAX request I want both the controllers to get updated with the data
The best way would be to return the promise from $http.get:
angular.module('app').factory('Service', function($http) {
var promise;
return {
getList: function() {
if (promise) {
return promise;
}
promise = $http.get('/path/to/my/api');
return promise;
}
}
});
angular.module('app').controller('ParentCtrl', function(Service) {
var list = [];
var init = function () {
Service.getList().then(function(response) {
list = response.data;
});
}
});
angular.module('app').controller('ChildCtrl', function(Service) {
var list = [];
var init = function () {
Service.getList().then(function(response) {
list = response.data;
});
}
});
You can broadcast custom message to rootScope and Your controllers will get this message and handle it.
angular.module('app').controller('ParentCtrl', function($rootScope, $scope, Service) {
$scope.list = [];
$scope.$on('Service:list', function(event, data){
$scope.list = data;
});
});
angular.module('app').controller('ChildCtrl', function($rootScope, $scope, Service) {
$scope.list = [];
$scope.$on('Service:list', function(event, data){
$scope.list = data;
});
Service.query(); // run once, get in both controllers
});
angular.module('app').factory('Service', function($rootScope, $http) {
var list;
return {
query : function() {
$http.get('/path/to/my/api').success(function(data){
list = data;
$rootScope.$broadcast('Service:list', list);
})
},
getList: function() {
return list;
}
}
});
You could handle it in many ways. One simple way is to cache the promise and return it.
Example:-
angular.module('app').factory('Service', function($http, $q) {
var listPromise;
return {
getList: function() {
//If promise is already present then
return listPromise || (listPromise = $http.get('/path/to/my/api').then(function(response){
return response.data;
})
//Use the below 2 blocks (catch and finally) only if you need.
//Invalidate in case of error
.catch(function(error){
listPromise = null;
return $q.reject(error);
})
//Use finally to clean up promise only if you only need to avoid simultaneous request to the server and do not want to cache the data for ever.
.finally(function(){
listPromise = null;
}));
}
}
});
and in controller:
angular.module('app').controller('ParentCtrl', function(Service) {
var list = [];
var init = function () {
Service.getList().then(function(data) {
list = data;
});
}
});
angular.module('app').controller('ChildCtrl', function(Service) {
var list = [];
var init = function () {
Service.getList().then(function(data) {
list = data;
});
}
});
Caching a promise will make sure that it really does not matter who makes the first call and you always make the same service call to get the data and service will manage data caching via promise and prevent any duplicate calls.
Another practice is to implement a one-way data flow using flux pattern. Where you create stores that maintains data and it will make ajax call via a dispatcher and emits event to the subscribers of change event. There is an angular library (flux-angular) that can be used as well to implement this pattern. This will really help synchronize data between multiple components regardless of whether they are parent/child or siblings and regardless of who makes the first call etc..