How can I send an object with $broadcast? - angularjs

I have the following:
$scope.$watch('tableForm.$pristine', function (newValue) {
$rootScope.$broadcast("tableDataUpdated",
{ state: $scope.tableForm.$pristine });
});
I also tried:
$scope.$watch('tableForm.$pristine', function (newValue) {
var tableForm = { pristine: $scope.tableForm.$pristine };
$rootScope.$broadcast("tableDataUpdated", tableForm);
});
When the tableForm $pristine state changes then the value of $scope.tableForm.$pristine is set to False and this message is broadcast.
However when I try to receive the message the value of "state" is not defined:
$rootScope.$on("tableDataUpdated", function (args) {
alert(args.state);
});
I also tried:
$rootScope.$on("tableDataUpdated", function (args) {
alert(args.tableForm);
});
Still I seem not to be able to send the object and have it received

That's because listener function has two arguments being passed into it, event, and args. See the angular docs.
Try:
$rootScope.$on("tableDataUpdated", function (event, args) {
alert(args.state);
});

Related

Why the execution of my code gets stuck as it encounters the function call?

I have created a function named calculateFaceLocation(). And I am trying to call calculateFaceLocation() from another function named onButtonClick(). But the execution gets stopped as soon as the function call line is encountered. I have proved this by using console.log()
calculateFaceLocation = (response) =>{
console.log('I HAVE BEAEN CALLED');
}
onButtonClick =(event) =>{
this.setState({
ImageUrl:this.state.input
})
app.models.predict(Clarifai.FACE_DETECT_MODEL, this.state.input).then(
function(response) {
// do something with response
console.log(response.outputs[0].data.regions[0].region_info.bounding_box)
this.calculateFaceLocation();
console.log('returning after call')
},
function(err) {
// there was an error
}
);
}
Usually, JavaScript functions will not have the context (this) in which they were called. You can either bind the context to the functions using bind(), or, the easier way: use arrow functions instead of normal anonymous functions, just like you did when creating onButtonClick.
Arrow functions preserve the context they were called in, so the this inside the function will the same as whatever it was in that block.
You'd end up with something like
onButtonClick =(event) =>{
this.setState({
ImageUrl:this.state.input
})
app.models.predict(Clarifai.FACE_DETECT_MODEL, this.state.input).then(
(response) => {
// do something with response
console.log(response.outputs[0].data.regions[0].region_info.bounding_box)
this.calculateFaceLocation();
console.log('returning after call')
},
(err) => {
// there was an error
}
);
}
Reason:
Context. Using this in a function will refer only to the function from where it is called.
Explanation:
In your code, the function calculateFaceLocation is declared in the parent context and hence it is not accessible from the child function's this.
Solution:
You can bind the value of this in the child funciton using bind.
You can use arrow functions which does this for you.
Bind Variable Examples:
var module = {
sayHello: function() {
return "Hello";
},
sayHelloName: function(name) {
return this.sayHello() + " " + name;
}
};
console.log(module.sayHelloName('Ram')); // Prints Hello Ram
// Now, Lets pass it to another function like in your code.
// This funciton is bound with the context of module.
// So accessing sayHello will work.
boundSayHelloName = module.sayHelloName.bind(module);
// This function doesn't have access to the sayHello function.
// It will throw Error.
unboundSayHelloName = module.sayHelloName;
console.log(boundSayHelloName('Ram')); // Prints Hello Ram
console.log(unboundSayHelloName()); // Throws Error
Code - Using arrow function:
calculateFaceLocation = (response) => {
console.log('I HAVE BEAEN CALLED');
}
onButtonClick = (event) => {
this.setState({
ImageUrl:this.state.input
})
app.models.predict(Clarifai.FACE_DETECT_MODEL, this.state.input).then(
(response) => {
// do something with response
console.log(response.outputs[0].data.regions[0].region_info.bounding_box)
this.calculateFaceLocation();
console.log('returning after call')
},
(err) => {
// there was an error
}
);
}

AngularJS component bindings not passing object

Receiving error cannot read property 'type' of undefined at PollListCtrl.runQuery.
I'm not sure why this is coming up undefined. I've console logged profile-polls.controller.js where listConfig is created to make sure there is an object here. The response is
{type: "all", filters: {pollsCreated: Array(3)}}
But it is coming up undefined when I try to pass it to poll-list component in the profile-polls.html. Below is the gist for the relevant files. Can anyone tell me why this is not passing correctly?
https://gist.github.com/RawleJuglal/fa668a60e88b6f7a95b456858cf20597
I think you need to define watcher for your component. On start listConfig is undefined and only after some delay (next digest cycle) it gets value. So we create watcher and cancel it after if statement.
app.component('pollList', {
bindings: {
listConfig: '='
},
templateUrl: 'poll-list.html',
controllerAs: 'vm',
controller: PollListCtrl
});
function PollListCtrl($scope) {
var vm = this;
function run(){
console.log(vm.listConfig);
}
var cancelWatch = $scope.$watch(function () {
return vm.listConfig;
},
function (newVal, oldVal) {
if (newVal !== undefined){
run();
cancelWatch();
}
});
}
Demo plunkr
You should never use $scope in your angularjs application.
You can use ngOnInit() life cycle hook to access "bindings" value instead constructor.
ngOnInit(){
$ctrl.listConfig = { type: 'all' };
$ctrl.limit = 5;
}

Update property of object in callback function of Angular component

I want to create a new Angular compontent, which should change the status of an object/model and then call the defined callback function.
The code looks like this:
HTML
<status-circle status="obj.status" on-update="save(obj)"></status-circle>
statusCirlcle.js
angular.module('app').component('statusCircle', {
templateUrl: '/views/components/statusCircle.html',
bindings: {
status: '=',
onUpdate: '&'
},
controller: function () {
this.update = function (status) {
if(this.status !== status) {
this.status = status;
this.onUpdate();
}
};
}
});
The property will be updated correctly (two-way binding works) and the save callback function gets called, BUT the object has the old status property set.
My question: How can I update the object in the callback function?
JSFiddle: https://jsfiddle.net/h26qv49j/2/
It looks like in statusCircle.js you should change
this.onUpdate();
to
this.onUpdate({status: status});
and then in the HTML change
<status-circle status="obj.status" on-update="save(obj)"></status-circle>
To
<status-circle status="obj.status" on-update="save(status)"></status-circle>
And lastly... in your controller where your save() function is put
this.save = function(status) {this.obj.status = status};

What is event.preventDefault preventing despite the event?

One goal of my main controller is to prevent users from going to urls of other users. That works perfectly fine with listening on $locationChangeStart and using its events preventDefault method. Unfortunately calling this method has the strange side effect of somehow "interrupting" the work of the function "handleNotification" which has the goal of notifying the user for 2 seconds that she or he has done something illegitimate. If I comment out event.preventDefault(), everything works as expected. So my question is: What is the 'scope' of the 'default' preventDefault prevents that I don't have on my mind and which keeps the handleNotification function from working properly?
$scope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
ifUserIs('loggedIn', function() {
if (newUrl.split('#/users/')[1] !== $scope.user.userId) {
handleNotification('alert', 'You are not allowed to go here.');
event.preventDefault();
}
});
});
function handleNotification (type, message) {
$scope.notice = {
content: message,
type: type
};
$timeout(function() {
delete $scope.notice;
return true;
}, 2000);
}
Update below
Ok. The problem is somewhere else. In the related jsfiddle everything works fine. After finding the source which is responsible for this strange behaviour I will let you know.
<html ng-app="mapApp">
<div ng-controller="mainCtrl">
<global-error message="{{notice.content}}"></global-error>
</div>
</html>
And the code.
var mapApp = {};
mapApp = angular.module('mapApp', []);
mapApp.controller('mainCtrl', function ($scope, $location, $timeout) {
$location.path('users/2')
$scope.$on('$locationChangeStart', function (event, newUrl, oldUrl) {
handleNotification('alert', 'You are not allowed to go here.');
event.preventDefault();
});
function handleNotification(type, message) {
$scope.notice = {
content: message,
type: type
};
$timeout(function () {
delete $scope.notice;
console.log('deleted');
return true;
}, 2000);
$scope.$digest();
}
});
mapApp.directive('globalError', function () {
return {
restrict: 'E',
scope: {
message: '#',
type: '#'
},
template: "<div class=\"alert-box {{type}}\">\
<p>\
{{message}}\
</p>\
</div>"
};
});
Update
Ok. One step further. And the problem is still there. Right now I know that changing the path in the browser is something different than changing the url by putting $location.path('users/2') inside the code (see above). While $location.path('users/2') works as expected, changing the path in the browsers address bar manually just makes the address jump back to the old address without displaying the notice. So event.preventDefault() works correctly but handleNotification('alert', 'You are not allowed to go here.') isn't. Strange.
Update 2
Adding $scope.$digest() to the end of the handleNotification function did the trick.

How to stop $broadcast events in AngularJS?

Is there a built in way to stop $broadcast events from going down the scope chain?
The event object passed by a $broadcast event does not have a stopPropagation method (as the docs on $rootScope mention.) However this merged pull request suggest that $broadcast events can have stopPropagation called on them.
Snippets from angularJS 1.1.2 source code:
$emit: function(name, args) {
// ....
event = {
name: name,
targetScope: scope,
stopPropagation: function() {
stopPropagation = true;
},
preventDefault: function() {
event.defaultPrevented = true;
},
defaultPrevented: false
},
// ....
}
$broadcast: function(name, args) {
// ...
event = {
name: name,
targetScope: target,
preventDefault: function() {
event.defaultPrevented = true;
},
defaultPrevented: false
},
// ...
}
As you can see event object in $broadcast not have "stopPropagation".
Instead of stopPropagation you can use preventDefault in order to mark event as "not need to handle this event". This not stop event propagation but this will tell the children scopes: "not need to handle this event"
Example: http://jsfiddle.net/C8EqT/1/
Since broadcast does not have the stopPropagation method,you need to use the defaultPrevented property and this will make sense in recursive directives.
Have a look at this plunker here:Plunkr
$scope.$on('test', function(event) {
if (!event.defaultPrevented) {
event.defaultPrevented = true;
console.log('Handle event here for the root node only.');
}
});
I implemented an event thief for this purpose:
.factory("stealEvent", [function () {
/**
* If event is already "default prevented", noop.
* If event isn't "default prevented", executes callback.
* If callback returns a truthy value or undefined,
* stops event propagation if possible, and flags event as "default prevented".
*/
return function (callback) {
return function (event) {
if (!event.defaultPrevented) {
var stopEvent = callback.apply(null, arguments);
if (typeof stopEvent === "undefined" || stopEvent) {
event.stopPropagation && event.stopPropagation();
event.preventDefault();
}
}
};
};
}]);
To use:
$scope.$on("AnyEvent", stealEvent(function (event, anyOtherParameter) {
if ($scope.keepEvent) {
// do some stuff with anyOtherParameter
return true; // steal event
} else {
return false; // let event available for other listeners
}
}));
$scope.$on("AnyOtherEvent", stealEvent(function (event, anyOtherParameter) {
// do some stuff with anyOtherParameter, event stolen by default
}));

Resources