angular.js event emit, variable load time - angularjs

I have these 2 controllers:
app.controller('SomeCtrl', ['$scope', '$rootScope', function($scope, $rootScope){
$scope.number = 0;
$rootScope.$on('do', function(event, data) {
$scope.number += data;
});
}]);
app.controller('SecondCtrl', ['$scope', function($scope) {
this.something = function() {
$scope.$emit('do', 5);
};
}]);
and in html I'm just writing this variable:
<div ng-controller="SomeCtrl">{{number}}</div>
When I call something function it emits the do. But it's updating the number like 10 seconds. Why?

The problem was solved by calling $scope.$apply();.

Related

Fire $emit event if model property change

I'm trying to pass some data from child controller to parent controller using $emit + $on .
Here is my code.
angular.module('myApp', [])
.controller('parentCtrl', ['$scope', '$http', '$q', function ($scope, $http, $q) {
$scope.message = 'parent';
$scope.$on('EventFromChild', function (event, data) {
console.log("Event Received");
$scope.message = data;
});
}])
.controller('childCtrl', ['$scope', '$http', function ($scope, $http) {
$scope.data ={"name":"Amit","lastname":"kumar"};
$scope.$emit('EventFromChild',$scope.data.name);
$scope.update = function(){
console.log("Clicked");
$scope.data.name ="Testing";
};
}]);
When page is loaded then, Then it successfully passes the data from the child to parent, But when I update the data in child then I also want it to get reflected in parent controller, But it is not emitting the event.
Plunker
It is because you are doing nothing when the child is updated.
You have to use $watch for observing changes. For example, if you want emit changes when data.name changes:
$scope.$watch('data.name', function() {
$scope.$emit('EventFromChild',$scope.data.name);
});
Or when any property of data changes:
$scope.$watch('data', function() {
$scope.$emit('EventFromChild',$scope.data);
});
This way the come becomes:
angular.module('app.controllers', [])
.controller('parentCtrl', ['$scope', '$http', '$q', function ($scope, $http, $q) {
$scope.message = 'parent';
$scope.$on('EventFromChild', function (event, data) {
console.log("Event Received");
$scope.message = data;
});
}])
.controller('childCtrl', ['$scope', '$http', function ($scope, $http) {
$scope.data ={"name":"Amit","lastname":"kumar"};
$scope.$emit('EventFromChild',$scope.data.name);
$scope.update = function(){
console.log("Clicked");
$scope.data.name ="Testing";
};
$scope.$watch('data.name', function() {
$scope.$emit('EventFromChild', $scope.data.name);
});
}]);
Snippet:
// Code goes here
mc = angular.module('app', [
'app.controllers',
]);
angular.module('app.controllers', [])
.controller('parentCtrl', ['$scope', '$http', '$q', function ($scope, $http, $q) {
$scope.message = 'parent';
$scope.$on('EventFromChild', function (event, data) {
console.log("Event Received");
$scope.message = data;
});
}])
.controller('childCtrl', ['$scope', '$http', function ($scope, $http) {
$scope.data ={"name":"Amit","lastname":"kumar"};
$scope.update = function(){
console.log("Clicked");
$scope.data.name ="Testing";
};
$scope.$watch('data.name', function() {
$scope.$emit('EventFromChild',$scope.data.name);
});
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.13/angular.min.js"></script>
<div ng-app="app" ng-controller="parentCtrl">
<b>Parent Event:</b> {{message}}<br />
<br/><br/>
<div ng-controller="childCtrl">
<b>Child Event:</b> {{data.name}}<br />
<input type="button" ng-click="update()" value ="update"/>
</div>
</div>

How to test actions which are performed in callback using Jasmine

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.

How can I run function from another controller so scope is updated in angular?

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", {});

AngularJS: Cannot read property 'get' of undefined $http

var myApp = angular.module('myApp',[]);
myApp.controller('Controller', function($scope, $interval){
$interval(function($scope, $http){
$http.get('test.json').success(function(data){
$scope.notifications = data;
});
},5000);
});
Anyone See what exactly I'm doing wrong? The interval is working correctly, as the error repeats ever 5 seconds in Chrome's Console. Am I passing the $http correctly to the controller?
All Angular module should be injected in the constructor of the controller.
var myApp = angular.module('myApp',[]);
myApp.controller('Controller', function($http, $scope, $interval){
$interval(function(){
$http.get('test.json').success(function(data){
$scope.notifications = data;
});
},5000);
});
You need to inject $http into your controller. (The same way you inject $scope and $interval.) You may be interested to read https://docs.angularjs.org/guide/di.
var myApp = angular.module('myApp',[]);
myApp.controller('Controller', function($scope, $interval, $http){
$interval(function($scope, $http){
$http.get('test.json').success(function(data){
$scope.notifications = data;
});
},5000);
});
Like you injected the $interval service, you need to inject the $http service:
var myApp = angular.module('myApp',[]);
myApp.controller('Controller', function($scope, $interval, $http){
$interval(function($scope, $http){
$http.get('test.json').success(function(data){
$scope.notifications = data;
});
},5000);
});
you need to pass in $http in your controller, as:
var myApp = angular.module('myApp', []).
myApp.controller('ImagesCtrl', ['$scope', '$http', '$interval', function ($scope, $http, $interval) {
$interval(function(){
$http.get('test.json').success(function(data){
$scope.notifications = data;
});
},5000);
}]);

AngularJS $injector.invoke - ParentController is not defined

I have 2 controllers defined:
var myApp = angular.module('nestedControllersModule',[]);
myApp.controller('ParentController', ['$scope', function($scope) {
}]);
myApp.controller('ChildController', ['$scope', '$injector', function($scope, $injector) {
$injector.invoke(ParentController, this, {$scope: $scope});
}]);
This gives: ReferenceError: ParentController is not defined.
This code works only if ParentController is defined as:
function ParentController($scope) {}
I am trying to inject the parent in the child as then I can inherit the common functions defined in the parent.
var myApp = angular.module('nestedControllersModule',[]);
myApp.controller('ParentController', ['$scope', function($scope) {
$scope.name = 'ParentName';
$scope.Type = 'ParentType';
$scope.clickme = function() {
alert('This is parent controller "ParentController" calling');
}
}]);
myApp.controller('ChildController', ['$scope', '$injector', '$ParentController', function($scope, $injector, $ParentController) {
$injector.invoke(ParentController, this, {$scope: $scope});
$scope.name = 'Child';
}]);
myApp.controller('ParentController', ['$scope', function($scope) {
}]);
myApp.controller('ChildController', ['$scope', 'ParentController', function($scope, ParentController) {
// ok now you have ParentController
}]);
But I think you need to use Services to share data/functions between Controllers or using PubSub model:
What's the correct way to communicate between controllers in AngularJS?
This reduces coupling between parts of your app.
This is a basic workaround to achieve what you're after:
var myApp = angular.module('nestedControllersModule',[]);
myApp.factory('ParentControllerFactory', function () {
function ParentControllerFactory($scope) {
$scope.name = 'ParentName';
$scope.Type = 'ParentType';
$scope.clickme = function() {
alert('This is parent controller "ParentController" calling');
}
}
return (ParentControllerFactory);
})
.controller('ParentController', ['$scope', '$injector', 'ParentControllerFactory', function ($scope, $injector, ParentControllerFactory) {
$injector.invoke(ParentControllerFactory, this, {
$scope: $scope
});
}])
.controller('ChildController', ['$scope', '$injector', 'ParentControllerFactory', function ($scope, $injector, ParentControllerFactory) {
$injector.invoke(ParentControllerFactory, this, {
$scope: $scope
});
}]);
I say workaround because it's probably worthwhile looking into properly implementing a service to manage any commonality as previously mentioned (or better yet, splitting commonality into directives, clickme for example is a good candidate)
...also note that $injector.invoke(ParentControllerFactory as it is above will most likely chuck a hissy fit if/when you minify your scripts later on, so be careful where and how it used.
Consider using the Mixin pattern possible by using the $controller service.
In your example, you would replace the $injector service with the $controller service:
var myApp = angular.module('nestedControllersModule',[]);
myApp.controller('ParentController', ['$scope', function($scope) {
$scope.name = 'ParentName';
$scope.Type = 'ParentType';
$scope.clickme = function() {
alert('This is parent controller "ParentController" calling');
}
}]);
myApp.controller('ChildController', ['$scope', '$controller', '$ParentController', function($scope, $controller, $ParentController) {
$controller('ParentController',{$scope: $scope})
$scope.name = 'Child';
}]);
This is a good overview of using the $controller service:
http://vadimpopa.com/split-large-angularjs-controllers-using-the-mixin-pattern/

Resources