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

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.

Related

Angular Resolve Scope Issues

I'm a junior developer and have been learning the ins and outs of Angular.js. I recently started using the resolve feature of the route provider to get my Project data from my service before the page loads. Before that I was calling my service inside the controller to get the data. My problem is that when I use resolve, the data gets to the controller just fine, but my scope doesn't take in my functions present at the bottom of the code and I'm not particularly sure why. If I move the function declarations to the top it works fine. My guess is that since the page now loads with the data, it doesn't have time to check the entire controller and instead just runs down it in order. Can anyone confirm why it's occurring and a solution so I can keep my code nice and readable? Thanks
My Route Provider
.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/details/:id', {
templateUrl: '/HtmlViews/Details.html',
controller: 'detailController',
resolve: {
project: function (projectService, $rootScope, $route) {
$rootScope.loading = true;
return projectService.getProjectById($route.current.params.id);
}
}
});
}])
My Controller (stripped to just the important stuff) In it's state below, the program can't find function getDateAt in the scope when it needs to.
.controller('detailController', function ($scope, $http, $rootScope, projectService, project) {
$rootScope.loading = false;
$scope.id = project.ID;
$scope.project = project;
$scope.sprintCountRounded = projectService.roundSprintCount($scope.project.SprintCount, $scope.project.RoundUp);
// Check and alter data depending on if a start date is present on the project
$scope.isDateMissing = $scope.project.StartDate === undefined || $scope.project.StartDate === null;
if (!$scope.isDateMissing) {
$scope.startDate = $scope.getDateAt(0);
$scope.finalSprintStart = $scope.getDateAt($scope.sprintCountRounded);
$scope.finalSprintEnd = $scope.getDateAt($scope.sprintCountRounded + 1);
}
$scope.NumberOfLoggedSprints = $scope.project.Sprints.length;
$scope.getDateAt = function (sprintNum) {
return projectService.getDateAt(sprintNum, $scope.project.SprintDuration, $scope.project.StartDate);
}
});
JavaScript processes code 'top down' (somewhat), so to speak, as it is an interpreted language. So as your code is processed line by line, once it reaches the lines inside your conditional that have $scope.getDateAt(), it attempts to call the function on your scope object. But the problem is this: your function hasn't been defined yet, as JavaScript hasn't processed the line where your function is defined.
As you noticed, by placing your function definition at the top, it works perfectly fine, as that code is processed first. This typically happens when you declare inline functions.
The exception to this is explicit function definitions. Were you to do the following code instead, it would work regardless of where you place the getDateAt() function:
function getDateAt(sprintNum) {
return projectService.getDateAt(sprintNum, $scope.project.SprintDuration, $scope.project.StartDate);
}

Integrating non-Angular code?

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
}
}]);

AngularJS: Custom $anchorScroll provider execution

I need to scroll to a specific anchor tag on page reload. I tried using $anchorScroll but it evaluates $location.hash(), which is not what I needed.
I wrote a custom provider based on the source code of $anchorScrollProvider. In it, it adds a value to the rootScope's $watch list, and calls an $evalAsync on change.
Provider:
zlc.provider('scroll', function() {
this.$get = ['$window', '$rootScope', function($window, $rootScope) {
var document = $window.document;
var elm;
function scroll() {
elm = document.getElementById($rootScope.trendHistory.id);
if (elm) elm.scrollIntoView();
}
$rootScope.$watch(function scrollWatch() {return $rootScope.trendHistory.id;},
function scrollWatchAction() {
if ($rootScope.trendHistory.id) $rootScope.$eval(scroll);
});
return scroll;
}];
});
Now, when I try to call the scroll provider in my controller, I must force a digest with $scope.$apply() before the call to scroll():
Controller:
//inside function called on reload
$scope.apply();
scroll();
Why must I call $scope.$apply()? Why isn't the scroll function evaluating in the Angular context when called inside the current scope? Thank you for your help!
I'm not sure what your thinking is behind using $rootScope.$eval(scroll) - since the scroll() function is already executing in a context where it has direct access to the $rootScope.
If I understand correctly, you want to be able to scroll to a particular element as denoted by an id which is stored in $rootScope.trendHistory.id.
When that id is changed, you want to scroll to that element (if it exists on the page).
Assuming this is a correct interpretation of what you are trying to achieve, here is how I might go about implementing it:
app.service('scrollService', function($rootScope) {
$rootScope.trendHistory = {};
$rootScope.$watch('trendHistory.id', function(val) {
if (val) {
elm = document.getElementById($rootScope.trendHistory.id);
if (elm) elm.scrollIntoView();
}
});
this.scrollTo = function(linkId) {
$rootScope.trendHistory.id = linkId;
}
});
This is a service (like your provider, but using the simpler "service" approach) which will set up a $watch on the $rootScope, looking for changes to $rootScope.trendHistory.id. When a change is detected, it scrolls to the element indicated if it exists - that bit is taken directly from your code.
So to use this in a controller, you'd inject the scrollService and then call its scrollTo() method with the ID as an argument. Example:
app.controller('AppController', function($scope, scrollService) {
scrollService.scrollTo('some_id');
});
In your question, you mention this needing to occur on reload, so you'd just put the call into your reload handler. You could also just directly modify the value of $rootScope.trendHistory.id from anywhere in the app and it would also attempt to scroll.
Here is a demo illustrating the basic approach: http://plnkr.co/edit/cJpHoSemj2Z9muCQVKmj?p=preview
Hope that helps, and apologies if I misunderstood your requirements.

AngularJS: Sharing scope behavior with sibling or descendant scope

Is it possible to share scope behavior from one of my controllers higher up in my app hierarchy so that it's able to manage data from an unrelated/uninherited scope as a sort of abstract and separate 'remote control'?
This is how I have things setup in psudo-angular:
//Looking to share the ManagedScope1 and ManagedScope2 "changeChannel()" behavior with this controller
<RemoteControlCtrl>
<ng-click="managedScope1.changeChannel()"></ng-click>
<ng-click="managedScope2.changeChannel()"></ng-click>
</RemoteControlCtrl>
//ManagedScopes inherit ChangeChannelCtrl scope behaviors
<ChannelChangeCtrl with $scope.changeChannel() method>
<ManagedScope1></ManagedScope1>
<ManagedScope2></ManagedScope2>
</ChannelChangeCtrl>
The $scope.changeChannel() method is inherited in both managed scopes, and can act on their own data accordingly.
You need a ChannelService....
.service('ChannelService', function () {
return {
changeChannel: function (scopeData, callback) {
//change the channel
if (angular.isFunction(callback)) {
callback();
}
}
};
});
Usage:
.controller('MyController', ['$scope', 'ChannelService', function ($scope, ChannelService) {
$scope.dataForChangingChannel = {};
$scope.changeChannel = function () {
//do UI stuff here
ChannelService.changeChannel($scope.dataForChangingChannel, function () {
//or UI stuff here, after the channel has been changed
});
}
}]);
It would depends on what kind of component that generated the scope.
If it is a completely unrelated scope, you should use $broadcast. You can require $rootScope as dependancies in remote controller and $rootScope.$broadcast('eventName', someData). In your channel controller:
$scope.$on('eventName', function(event, data) {
// Do something here
})
Another good idea would be using a service, but that would still be hard to call method on another scope. I would say event broadcasting is a nice approach for your problem.

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.

Categories

Resources