I've created a service to pass a boolean flag between controllers.
Service looks like this :
angular.module('MyApp')
.service('sharedProperties', function () {
var flag = false;
return {
getProperty: function () {
return flag;
},
setProperty: function(value) {
flag = value;
}
};
});
First controller where flag is set is this
angular.module('MyApp')
.controller('test1', function($scope, $location, $http, $window, sharedProperties) {
$scope.flag1 = function () {
if ($location.path() == '/') {
sharedProperties.setProperty(false);
return false;
}else {
sharedProperties.setProperty(true);
return true;
};
};
});
And controller receiving argument is this
angular.module('MyApp')
.controller('test2', function($scope, $location, $http, $window, sharedProperties) {
$scope.flag2 = sharedProperties.getProperty();
});
flag1 in test1 takes a correct value, but test2 is always false. If I initialize flag at service as true - it's always true at test2. If I don't initialize it - it's undefined at test2.
This behaviour is totally expected and is not linked to angularjs in particular, it works like this in vanilla javascript. The issue you encounter is due to the fact that javascript primitives are passed by value and not by reference. You can read more about it here: Is JavaScript a pass-by-reference or pass-by-value language?
How to fix your code? Use an object which will be passed by reference.
Could you try this:
angular.module('MyApp')
.service('sharedProperties', function () {
var flag = {
value: false
};
return {
getProperty: function () {
return flag;
},
setProperty: function(value) {
flag.value = value;
}
};
});
angular.module('MyApp')
.controller('test1', function($scope, $location, $http, $window, sharedProperties) {
$scope.flag1 = function () {
if ($location.path() == '/') {
sharedProperties.setProperty(false);
return false;
}else {
sharedProperties.setProperty(true);
return true;
};
};
});
angular.module('MyApp')
.controller('test2', function($scope, $location, $http, $window, sharedProperties) {
$scope.flag2 = sharedProperties.getProperty();
// use flag2.value in your markup
});
The reason being is I believe that Angular initializes a service with new when added to a controller.
So when manipulating in test1, its an instance of the service in that controller which is not shared in the other controller. I could be wrong though and would recommend firstly using this.flag instead of var flag. In addition, your test2 controller makes a call on angular load, rendering false but it doesn't watch for changes.
Created a JSBIN to solve it for you: http://jsbin.com/xoqup/1/edit
Related
I am having a few problems accessing my controller on a directive that I am trying to unit test with jasmine and karma testrunner. The directive looks like this:
directive
angular.module('Common.accountSearch',['ngRoute'])
.directive('accountSearch', [function() {
return {
controllerAs: 'ctrl',
controller: function ($scope, $element, $routeParams, $http) {
this.setAccount = function () {
var response = { AccountId : $scope.ctrl.searchedAccount.AccountId }
$scope.callback(response)
}
this.getAccounts = function(searchText){
return $http.get('/api/CRMAccounts', {
params: {
retrievalLimit: 10,
search: searchText
}
}).then(function(response){
return response.data;
});
}
},
scope : {
config : '=',
values : '=',
callback : '='
},
templateUrl : '/common/components/account-search/account-search.html',
restrict : 'EAC'
}
}]);
This here is the test case file so far I believe all is in order and correct (I hope):
test case file:
describe("Account search directive logic tests", function (){
var element,$scope,scope,controller,template
beforeEach(module("Common.accountSearch"))
beforeEach(inject( function (_$compile_, _$rootScope_,_$controller_,$templateCache) {
template = $templateCache.get("components/account-search/account-search.html")
$compile = _$compile_;
$rootScope = _$rootScope_;
$controller = _$controller_;
scope = $rootScope.$new();
element = $compile(template)(scope)
ctrl = element.controller
scope.$digest();
// httpBackend = _$httpBackend_;
}));
it(" sets the account and calls back.", inject(function () {
console.log(ctrl)
expect(ctrl).toBeDefined()
}));
//httpBackend.flush()
});
I have managed to print the controller of the directive ( I think) to the console which returns the following ambiguous message:
LOG: function (arg1, arg2) { ... }
I cannot access any of the functions or properties on the directive as they are all returning "undefined", what am I doing wrong?
Controllers for directives are actually fully injectable - instead of providing a constructor, you can just refer to the controller by name. See the directive definition object docs for Angular here: https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object
In your case where you want to unit test the controller you'd just do it like this:
common.accountSearch.js
angular.module('Common.accountSearch', [])
.directive('accountSearch', [function () {
return {
controller: 'accountSearchCtrl',
scope: {
config : '=',
values : '=',
callback : '='
},
templateUrl : '/common/components/account-search/account-search.html',
restrict: 'EAC'
}
}])
.controller('accountSearchCtrl', ['$scope', function ($scope) {
$scope.setAccount = function () {
var response = {
AccountId: $scope.ctrl.searchedAccount.AccountId
};
$scope.callback(response);
}
$scope.getAccounts = function (searchText) {
// Code goes here...
}
}]);
common.accountSearch-spec.js
describe("Account search directive logic tests", function () {
var controller, scope;
beforeEach(module("Common.accountSearch"));
beforeEach(inject(function (_$controller_, _$rootScope_) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
controller = _$controller_('accountSearchCtrl', { '$scope': scope });
}));
it(" sets the account and calls back.", function () {
expect(controller).toBeDefined();
});
});
This way you can just inject your controller directly into your jasmine tests like any of your other controllers.
Hope this helps.
So close!
element.controller is a function and needs to be passed the name of the directive which you're attempting to get the controller for. In this case it would be
ctrl = element.controller("accountSearch");
element.controller is an additional method to AngularJS jqLite, so when you log it you see jqLite method .toString(). You should call it and get a directive controller. Element controller manual
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 want to use a dependency in listener but the websocket was undefined
$rootScope.$on('websocket.connected', function() {
$websocket.request(.....).then();
});
and a want to call a service method (who depend on asyncron method) when it ready
app.controller('MyCtrl', function(myServ, $log) {
myServ.getInfos();
});
thank you.
Code in jsfiddle http://jsfiddle.net/8DHfY/3/ or here
var app = angular.module('myApp', ['myServ'])
.config(['$websocketProvider', function ($websocketProvider) {
$websocketProvider.setHost('ws://echo.websocket.org/');
}])
.controller('MyCtrl', function(myServ, $log) {
$log.log('I want to call myServ.getInfos() from a controler');
});
angular.module('myServ', ['websocket']).service('myServ', ['$log', '$rootScope', '$websocket', function($log, $rootScope, $websocket) {
$log.error('websocket is %o ... ???', $websocket); // return undefined
$rootScope.$on('websocket.connected', function() {
$log.error('websocket is still %o', $websocket); // return undefined
});
return {
getInfos: function() {
$websocket.request(JSON.stringify({'key': 'value'}));
}
};
}]);
angular.module('websocket', []).provider('$websocket', [function() {
var _this = this;
_this.host = '';
_this.connection = null;
_this.setHost = function(host) {
this.host = host;
return this;
};
_this.request = function(request) {
//request method for websocket
};
this.$get = ['$log', '$rootScope', function($log, $rootScope) {
_this.connection = new WebSocket(this.host);
_this.connection.onopen = function(){
$log.log('Websocket connected to %s', _this.host);
$rootScope.$emit('websocket.connected');
};
}];
}]);
Providers invoke the $get function upon injection and use the singleton of whatever is returned from that function.
This means since you do not return anything from the $get function, it uses undefined.
Here's an updated fiddle: http://jsfiddle.net/8DHfY/4/
this.$get = ['$log', '$rootScope', function($log, $rootScope) {
_this.connection = new WebSocket(this.host);
_this.connection.onopen = function(){
$log.log('Websocket connected to %s', _this.host);
$rootScope.$emit('websocket.connected');
};
return _this;
}];
I have the following controller:
'use strict';
/* Controllers */
angular.module('stocks.controllers', []).
controller('MyCtrl1', ['$scope', '$http', 'stockData', function MyCtrl1 ($scope, $http, stockData) {
$scope.submit = function() {
$scope.info = stockData.query();
console.dir($scope.info);
}
}]);
and i want to pass a bound ng-model that sits in my view called ng-model="symbol_wanted" to the following service...
'use strict';
/* Services */
angular.module('stocks.services', ['ngResource']).factory('stockData', ['$resource',
function($resource){
return $resource('http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20yahoo.finance.quotes%20where%20symbol%20in%20(%22YHOO%22)%0A%09%09&env=http%3A%2F%2Fdatatables.org%2Falltables.env&format=json', {}, {
query: {method:'GET', isArray:false}
});
}]);
how do i connect the controller's scope to get passed into the service? thanks!
how do i pass scope from controller to service in angularjs?
You can't inject $scope into services, there is no such thing as a Singleton $scope.
i want to pass a bound ng-model that sits in my view called ng-model="symbol_wanted" to the following service...
You can call the service and pass parameters this way:
.factory('stockData', ['$resource', '$q', function ($resource, $q) {
var factory = {
query: function (value) {
// here you can play with 'value'
var data = $resource('http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20yahoo.finance.quotes%20where%20symbol%20in%20(%22YHOO%22)%0A%09%09&env=http%3A%2F%2Fdatatables.org%2Falltables.env&format=json', {}, {
query: {
method: 'GET',
isArray: false
}
});
var deferred = $q.defer();
deferred.resolve(data);
return deferred.promise;
}
}
return factory;
}]);
So we call this service and get a promise back like this:
stockData.query(value) // <-- pass value
.then(function (result) {
$scope.data = result;
}, function (result) {
alert("Error: No data returned");
});
BTW, I'd suggest you use $http.get:
Demo Fiddle
Your ng-model value will automatically become a scope property. So, you can just use this in your controller to get the current value:
$scope.symbol_wanted;
So, let's say that you have a function to handle the click in your controller:
$scope.handleMyClick = function() {
stockData.query($scope.symbol_wanted);
}
You can just use the scoped property.
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.