I have two controllers, when user press the button to load SeconvView, the FirctCtrl sends event to the SecondCtrl via $rootScope.$broadcast.
// FirstCtrl.js
app.controller('FirstCtrl', ['$scope', function($rootScope, $scope) {
// ...
$scope.emitEvent = function(){
console.log("Emitting event");
$rootScope.$broadcast("EventName", "event data");
}
}]);
// SecondCtrl.js
app.controller('SecondCtrl', ['$scope', function($rootScope, $scope) {
console.log("Hola from SecondCtrl");
$rootScope.$on("EventName", function(event, data){
console.log(data);
});
}]);
Log output:
Emitting event
Hola from SecondCtrl
So, the problem is that SecndCtrl can't catch the event. I think that this is because the events sends before SecondCtrl is loaded.
With little timeout i can solve this problem, but ... common, i must use timeout for this :/
// FirstCtrl.js
app.controller('FirstCtrl', ['$scope', function($rootScope, $scope) {
// ...
$scope.emitEvent = function(){
$timeout(function() {
console.log("Emitting event");
$rootScope.$broadcast("EventName", "event data");
},100);
}
}]);
Log output:
Hola from SecondCtrl
Emitting event
event data
Some better way to solve this ?
Passing data between controllers is one of the main uses of a service / factory instead
// FirstCtrl.js
app.controller('FirstCtrl', ['$scope', function(MyFactory, $scope) {
// ...
$scope.emitEvent = function(){
console.log("Emitting event");
MyFactory.storedvalue = "event data";
}
}]);
// SecondCtrl.js
app.controller('SecondCtrl', ['$scope', function(MyFactory, $scope) {
console.log("Hola from SecondCtrl");
console.log(MyFactory.storedvalue);
});
}]);
Related
I have two controllers and a service. In the first controller I have subscribed to an event to do some stuff. The second controller preforms some actions and when it is done, broadcasts the event. Please see the example below, the timeout is just for emulation of long running actions. I would like to test that hasLoaded is set to true using Jasmine 2.0 Please advise.
var myApp = angular.module('MyApp', []);
myApp.controller('MyCtrl1', ['$scope', 'myService', function($scope, myService) {
$scope.hasLoaded = false;
$scope.fileName = '';
myService.onLoaded($scope, function(e, data){
// I want to test the following two lines, in the really the code here is much more complex
$scope.fileName = data.fileName;
$scope.hasLoaded = true;
});
}]);
myApp.controller('MyCtrl2', ['$rootScope', '$scope', '$timeout', 'myService', function($rootScope, $scope, $timeout, myService) {
$scope.isLoading = false;
$scope.title = 'Click me to load';
$scope.load = function(){
$scope.isLoading = true;
$scope.title = 'Loading, please wait...';
$timeout(function() {
$rootScope.$emit('loaded', { fileName: 'test.txt'});
}, 1000);
};
myService.onLoaded($scope, function(){
$scope.hasLoaded = true;
});
}]);
myApp.service('myService', ['$rootScope', function ($rootScope) {
this.onLoaded = function(scope, callback) {
var handler = $rootScope.$on('loaded', callback);
scope.$on('$destroy', handler);
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
<div ng-app="MyApp">
<div ng-controller="MyCtrl1">
<div ng-show="hasLoaded">{{fileName}} loaded !!!</div>
</div>
<div ng-controller="MyCtrl2">
<button ng-click="load()" ng-hide="hasLoaded" ng-disabled="isLoading" ng-bind="title"></button>
</div>
</div>
UPDATE: I have added parameter into the broadcast call to make it more closer to my case.
You really should be testing each of your pieces (controllers and services) separately. In your case, the tests for the controller that sets the hasLoaded properly really just needs to test that your register with the service correctly and that the callback does what you expect:
it("should register with the service and do the right thing when the callback is executed", inject(function ($controller, $rootScope, myService) {
var $scope = $rootScope.$new();
spyOn(myService, 'onLoaded').and.callThrough();
var ctrl = $controller('MyCtrl1', {$scope: $scope, myService: myService});
$scope.$apply();
//verify that the controller registers its scope with the service
expect(myService.onLoaded).toHaveBeenCalledWith($scope, jasmine.any(Function));
//now call the callback that was registered to see if it sets the property correctly
var mockData = {
fileName: 'some file name'
};
myService.onLoaded.calls.argsFor(0)[1]('loaded', mockData);
expect($scope.hasLoaded).toBeTruthy();
expect($scope.fileName).toBe("some file name");
}));
Then write tests for your service and other controller separately.
In the example below, how can I run getData from another controller and have the scope variable in the view updated?
var app = angular.module('app', []);
app.factory('MyService', ['$http',function($http) {
return {
getData: function() {
return $http.get('/api/endpoint');
}
};
}]);
app.controller('MyController', ['$scope', '$http', 'MyService', function($scope, $http, MyService){
MyService.getData().then(function(response){
$scope.myVarialbe = response.data;
});
}]);
app.controller('MyController2', ['$scope', '$http', 'MyService', function($scope, $http, MyService){
///// ?????? How to get $scope.myVarialbe updated from the getData call?
});
}]);
Using $broadcast and $on :
$broadcast dispatches an event name downwards to all child scopes (and their children) and notify to the registered $Scope listeners. The event life cycle starts at the scope on which $broadcast was called. All listeners for the event on this scope get notified.
$on listen on events of a given type. It can catch the event dispatched by $broadcast
app.controller('MyController', ['$scope', '$http', 'MyService', function($scope, $http, MyService){
$scope.$on('variableChanged',function(event, value) {
$scope.myVariable = value;
};
}]);
app.controller('MyController2', ['$scope', '$http', 'MyService', function($scope, $http, MyService){
MyService.getData().then(function(response){
$scope.$broadcast('variableChanged', response.data);
});
}]);
angular.module('app').controller('nav', function($scope,$http) {
$rootScope.$on("CallMethodNavController", function(){
$scope.navMethod();
});
$scope.navMethod=function(){
$http.get('/players').then(function(data) {
$scope.numOfPlayers = data.players.length;
}
});
});
then in the second controller you call this method once a player is added like so:
$rootScope.$emit("CallMethodNavController", {});
I'm newbie in Angular.
// This is my main controller
app.controller('AppCtrl', ['$rootScope', '$scope', 'cfpLoadingBar', '$http',
function ($rootScope, $scope, cfpLoadingBar, $http) {
cfpLoadingBar.start();
$rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) {
//stop loading bar on stateChangeSuccess
event.targetScope.$watch("$viewContentLoaded", function () {
// HERE I want to send request to API and on complete do
// 1. Complete loadbar
// 2. Write response to global var and use it from other controller
$http.get(
'/api/',
{}
).then(function (response) {
// Completing loadbar
cfpLoadingBar.complete();
$SOME_GLOBAL_VAR.api = response;
});
});
});
}]);
// Controller 1
app.controller('Ctrl_1', ["$scope", "$http" function ($scope, $http) {
$scope.param = 'get_data_1';
When $SOME_GLOBAL_VAR.api
console.log($SOME_GLOBAL_VAR.api);
}]);
I want to use one api request for different controllers.
So I want to load API, and when it finish - each controller will render his part.
Now I can't understand how to listen to API loading is completed?
You can achieve this in following way
$http.get(
'/api/',
{}
).then(function (response) {
// Completing loadbar
cfpLoadingBar.complete();
$rootScope.$emit("receivedApiUpdate", response);
});
angular.module("my_module")('controller1', ['$rootScope', function($rootScope) {
$rootScope.$on('receivedApiUpdate',function(event, response){
// Do your work
});
}]);
angular.module("my_module")('controller2', ['$rootScope', function($rootScope) {
$rootScope.$on('receivedApiUpdate',function(event, response){
// Do your work
});
}]);
angular.module("my_module")('controller3', ['$rootScope', function($rootScope) {
$rootScope.$on('receivedApiUpdate',function(event, response){
// Do your work
});
}]);
you can try to make a service and use your service across your controllers.
https://docs.angularjs.org/guide/services
How can I show ionicModal on app start? This code doesn't work
angular.module('testApp', ['ionic'])
.controller('MyController', function($scope, $ionicModal, $ionicPlatform) {
$ionicPlatform.ready(function(){
$scope.initApp = function() {
// some init variables here
if (someVariable){
$scope.openModal();
}
}
// init ionicModal here
$scope.openModal = function() {
$scope.modal.show();
};
$scope.initApp();
});
});
but this work
angular.module('testApp', ['ionic'])
.controller('MyController', function($scope, $ionicModal, $ionicPlatform, $timeout) {
$ionicPlatform.ready(function(){
$scope.initApp = function() {
// some init variables here
if (someVariable){
$timeout(function() {
$scope.openModal();
}, 1000);
}
}
// init Modal here
$scope.openModal = function() {
$scope.modal.show();
};
$scope.initApp();
});
});
I want open modal window on app start without delay. How can I do this? Thanks!
Edit code
As i understand application starts when device is ready.
So you can use the $ionicPlatform.ready method to attach callbacks for when the device is ready.
Trigger a callback once the device is ready, or immediately if the device is already ready. Source.
angular.module('testApp', ['ionic'])
.controller('MyController', function($scope, $ionicModal, $ionicPlatform) {
$ionicModal.fromTemplateUrl('my-modal.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.modal = modal;
});
$scope.openModal = function() {
$scope.modal.show();
};
$ionicPlatform.ready(function(){
$scope.openModal();
});
});
you should wait until device is ready, there are many methods for it, here is one I found:
$ionicPlatform.ready(function() {
$scope.openModal();
});
My test has:
it("should clear the search field when the URL changes", function() {
createController();
$scope.init();
$scope.searchTerm = 'some term';
$location.path('/source');
$rootScope.$apply();
expect($scope.searchTerm).toBe('');
});
My controller is:
angularMoonApp.controller('SearchController', ['$scope', '$location', function ($scope, $location) {
$scope.init = function() {
$scope.$on('$routeChangeStart', function(next, current) {
$scope.searchTerm = '';
});
}
$scope.init();
}]);
Seems simple enough! So why won't that trigger when I Change the location in the test?
You need to inject $route, since $routeChangeStart is an event triggered by $route.
angularMoonApp.controller('SearchController', ['$scope', '$location', '$route', function ($scope, $location, $route) {
Without knowing your use case, if you just need to detect that the url changed, you can listen for $locationChangeStart instead. $locationChangeStart is fired from $location, so you would not need to inject any new dependencies.