Integrating non-Angular code? - javascript

I'm developing a Cordova/PhoneGap app, and I'm using the $cordovaPush plugin (wrapped for PushPlugin) to handle push notifications.
The code looks something like this:
var androidConfig = {
"senderID" : "mysenderID",
"ecb" : "onNotification"
}
$cordovaPush.register(androidConfig).then(function(result) {
console.log('Cordova Push Reg Success');
console.log(result);
}, function(error) {
console.log('Cordova push reg error');
console.log(error);
});
The "ecb" function must be defined with window scope, ie:
window.onNotification = function onNotification(e)...
This function handles incoming events. I'd obviously like to handle incoming events in my angular code - how can I integrate the two so that my onNotification function can access my scope/rootScope variables?

Usually, you'll wrap your 3rd party library in a service or a factory, but in the spirit of answering your particular scenario...
Here's one possibility:
angular.module('myApp').
controller('myController', function($scope, $window) {
$window.onNotification = function() {
$scope.apply(function() {
$scope.myVar = ...updates...
});
};
});
A couple of things to notice:
Try to use $window, not window. It's a good habit to get into as it will help you with testability down the line. Because of the internals of Cordova, you might actually need to use window, but I doubt it.
The function that does all of the work is buried inside of $scope.apply. If you forget to do this, then any variables you update will not be reflected in the view until the digest cycle runs again (if ever).
Although I put my example in a controller, you might put yours inside of a handler. If its an angular handler (ng-click, for example), you might think that because the ng-click has an implicit $apply wrapping the callback, your onNotification function is not called at that time, so you still need to do the $apply, as above.
...seriously... don't forget the apply. :-) When I'm debugging people's code, it's the number one reason why external libraries are not working. We all get bit at least once by this.

Define a kind of a mail controller in body and inside that controller use the $window service.
HTML:
<body ng-controller="MainController">
<!-- other markup .-->
</body>
JS:
yourApp.controller("BaseController", ["$scope", "$window", function($scope, $window) {
$window.onNotification = function(e) {
// Use $scope or any Angular stuff
}
}]);

Related

AngularJS: passing a function along with scope to factory [duplicate]

$scope is not working inside a callback function.
angular.
module('common').
controller('bidVBoxController', ['$scope', '$location','$element', 'Socket', 'Bid',
function($scope, $location, $element, Socket,Bid){
var self = this;
self.socket = new Socket("ws://192.168.225.59:8010/ws/bidData/");
$scope.placeBid = function(){
self.socket.send({
type: "place_bid",
data: {
bidId: $scope.bid.id
}
});
};
console.log($scope.bid);
$scope.bid.top_bid_data="sss";//This works.
self.socket.onmessage(function(event) {
var data = JSON.parse(event.data);
console.log($scope.bid);//This doesn't work
$scope.bid.top_bid_data=data["message"];//This doesn't work
});
}])
A callback function is passed to the self.socket.onmessage, which is supposed to update $scope variable. But it appears that doesn't work. Please help.
Update1:
This controller is used with a directive bidVBox and there are multiple elements:
<bid-v-box ng-repeat="bid in bids">
</bid-v-box>
When the callback function is executed in the first bid-v-box element's controller, $scope.bid.top_bid_data=data["message"]; updates the scope of the last element and not the first one. I have also tried using $scope.$apply(function(){$scope.bid.top_bid_data=data["message"];}). But that didn't work.
It would be wiser to move the websocket constructor to a service and use only one connection. As written the ng-repeat is creating multiple websocket connections to the server. It is up to the server side application to treat each connection differently. Evidently the server is responding to the last connection made instead of each individually. For more information, see Stackoverflow: Multiple Websockets.
See also Github WS Issue #684 - Multiple connections, but single websocket.on("message") event emitter

Cannot read property '$apply' of undefined

I have research through the internet with this popular error and I have found no solution to my problem.
What I have is a jQuery iframe post message function that receive strings from a different domain. When it get the string it will need to store it to Angular and save to the database. What I am having the trouble is, trying to update angular so that recognizes the changes.
So here is my code:
.controller('jobOrderController', function(Jobs, socketio) {
var vm = this;
var myImage;
$.receiveMessage(
function(e) {
myImage = e.data;
vm.$apply(function() {
vm.orderData.guideImage = e.data
});
},
'http://aaa.com'
);
vm.createOrders = function() {
vm.message = '';
Jobs.createOrders(vm.orderData)
.success(function(data) {
vm.orderData = '';
vm.message = data.message;
});
};
})
$.receiveMessage will listen for incoming string data and then when it receive it should just save it to my "controller, vm". I know that my message is being received as I can alert them.
I know that I am going it wrong but everything I read is using $scope.apply so I thought it would work the same way using "this". But it doesn't seem to be updated to angular.
I see that you are using john papa's guideline to avoid the use of $scope.
You just forgot to declare vm (standing for viewmodel) at the beginning of your controller:
var vm = this;
EDIT: the guideline also says :
"Consider using $scope in a controller only when needed. For example
when publishing and subscribing events using $emit, $broadcast, or
$on."
For $apply as well, you need to explicitly use $scope.$apply
You can just do this:
.controller('jobOrderController', function($scope, Jobs, socketio) {
var vm = this;
$.receiveMessage(
function(e) {
myImage = e.data;
$scope.$apply(function() {
vm.orderData.guideImage = e.data
});
},
'http://aaa.com'
);
});
The this works differently inside the Angular's controller. It's not always same as the $scope and $scope is the object which contains $apply method.
For $apply, you can not use this. $scope and this are different instance so this can not access $apply.
Check snippet:
angular.module('myApp', []).controller('MyCtrl', function($scope) {
var vm = this;
setTimeout(function() {
$scope.$apply(function() {
vm.text = 'Submit';
});
}, 0);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl as ctrl">
<button>{{ctrl.text}}</button>
</div>
The controllerAs syntax is just (currently) sugar for $scope.foo (if your controllerAs is set to foo). As a result, this will actually point at $scope.foo in this instance and not $scope, which is why you will be unable to invoke any of $scope's actions through this. To use any of those, you will have to explicitly use $scope.
The above answers all answer with a solution on how to fix the short-term problem, that is, you using $scope.$apply in the controller.
Honestly, the issue here isn't that you can't use $scope.$apply - I'm not sure exactly what you are trying to do but one of the main rules of Angular is that you have to do everything through Angular; $.receiveMessage is decidedly not Angular. Wrap your $.receiveMessage into a service; that service should also handle the $scope.$apply. This will help reduce code duplication and make your controller agnostic to the implementation of the service.
There may already be a library that exists that does this that avoids using the heavy requirement of jQuery.

AngularJS and WebSockets: Callback function needs $scope.$apply() to work

I am using AngularJS and a phone web service to make calls through WebSockets.
The web service has several callbacks such as Phone.onIncomingCall
When I use this function to set a $scope variable the view is not updated automatically except if I use $scope.$apply right after.
Phone.onIncomingCall = function(){
$scope.myVar = "newValue";
$scope.$apply(); // only works if I call this line
};
What is the reason for this behaviour (is it expected) and is there a way around using $scope.apply() in each function?
Angular is "unaware" of the update to the scope variable you've made, since you're updating it from outside of the Angular context. From the docs for $apply:
$apply() is used to execute an expression in angular from outside of
the angular framework. (For example from browser DOM events,
setTimeout, XHR or third party libraries). Because we are calling into
the angular framework we need to perform proper scope life cycle of
exception handling, executing watches.
By running $apply on the scope, $digest is called from $rootScope, which will trigger any $watchers registered on $scope.myVar. In this case, if you're using the variable in your view via interpolation, this is where the $watcher was registered from.
It is the expected behavior, angular works like that internally.
I recommend the following:
Phone.onIncomingCall = function () {
$scope.$apply(function () {
$scope.myVar = 'newValue';
});
}

update service from directive that uses it

It is possible for a directive to update a service and then use the updated version?
In my service (cfg), I have a variable and an update function...
var test = "unfired";
function updateTest(){
console.log("LOG:","updateTest is firing");
test = "fired";
}
In the linking function of my directive I have
scope.$watch(watcher, function(newVal, oldVal) {
console.log("Before:",cfg.test);
cfg.updateTest();
console.log("After:",cfg.test);
}); //scope.$watch
Even though the updateTest function is firing, the console logs the same value before and after.
Now if cfg were a controller instead of a service I would do something like
function updateTest(){
console.log("LOG:","updateTest is firing");
test = "fired";
cfg.$apply() //or cfg.$digest()
}
But obviously that won't work. I have also tried injecting cfg to the controller and and $apply() to the link function...
console.log("Before:",cfg.test);
scope.$apply(function(){
cfg.updateTest()
});
console.log("After:",cfg.test);
which did trigger updateTest(), but it did not update the cfg service as the directive understands it.
Perhaps another way to say it is that I would like to "reinject" the service into the directive.
If you are wondering why I'd like to do this, it's because I have a bunch of d3.js animations as directives that share the same scales, and I'd like certain events to trigger changes in the scales' domains from one directive to the others.
Rather than using a service to communicate between directives. Try using "broadcast". You can throw an event into the air and anybody listening will run whatever function you want. It works like this.
Directive 1:
$rootScope.$broadcast('event:updateTest');
Directive 2:
$rootScope.$on("event:updateTest", function (event, next, current) { ... }
Then you can deal with local instances of your 'test' variable, rather than a service 'global' variable.

Attaching global functions and data to $rootScope on initialization in AngularJS

I'd like to have a "Global function" called the first time I launch my AngularJS application, or every time I refresh the page.
This function will call my server with $http.get() to get global information necessary to use my application. I need to access $rootScope in this function. After that, and only after this request finished, I'm using app.config and $routeProvider.when() to load the good controller.
app.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/',
{
/**/
});
}]);
I don't want the application do something before this action is finished. So I guess I have to use a "resolve", but I don't really know how to use it.
Any idea?
Thanks!
It's not the best way to solve your given problem, but here is a proposed solution for your question.
Anything inside run(...) will be run on initialization.
angular.module('fooApp').run(['$http', '$rootScope' function($http, $rootScope) {
$http.get(...).success(function(response) {
$rootScope.somedata = response;
});
$rootScope.globalFn = function() {
alert('This function is available in all scopes, and views');
}
}]);
Now an alert can be triggered in all your views, using ng-click="globalFn()".
Be aware that directives using a new isolate scope will not have access to this data if not explicitly inherited: $scope.inheritedGlobalFn = $rootScope.globalFn
As a first step for your solution, I think that you could monitor the $routeChangeStart event that is triggered before every route change (or page refresh in your case).
var app = angular.module('myApp').run(['$rootScope', function($rootScope) {
$rootScope.$on("$routeChangeStart", function (event, next, current) {
if (!$rootScope.myBooleanProperty)) {
$location.path('/');
}
else {
$location.path('/page');
}
});
});
You should have a look at this article about Authentification in a Single Page App. I think you could work something similar.
Please consider this answer:
https://stackoverflow.com/a/27050497/1056679
I've tried to collect all possible methods of resolving dependencies in global scope before actual controllers are executed.

Categories

Resources