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);
}
Related
I have a scope variable which is set after a few request and after some logic execution like this
$scope.record = {};
$scope.record.field = "custom_value";
Now i have a different function which executes independently of this execution .
function test(){ if($scope.record.field == 'some_value') { // do something }}
The problem is that if condition should be checked only after $scope.record.field has a value .
I tried putting it in $q like this $q.all($scope.record.field).then(func) but it did not work .
I also tried using when $q.when($scope.record.field).then(func) but it still did not work.
Both the times i got undefined value .
I don't want to put any code in part where $scope.record.field is set which signifies the completion of assignment because i want to keep both parts separate and independent . Also both of them have access to the same scope .
How do i make sure some part of my code is executed only after $scope.record.field has a value ?
Snippet of possible implementation
//one of the ways you could do that
'use strict';
(function() {
function MyService($q, Util) {
var trackThis;
var deferred;
var methods = {
setValue(val){
trackThis = val;
if (deferred) deferred.resolve(val);
},
listenForValue(val){
deferred = $q.defer();
return deferred.promise;
}
}
return methods;
}
angular.module('myApp')
.factory('MyService', MyService);
})();
EDIT
Assume u have a controller as follows:
'use strict';
(function() {
class MyController {
constructor(MyService, AnotherService) {
MyService.listenForValue().then(function(value){
//do whatever you want with the value
});
// Now introduce have another guy setting the value
AnotherService.fetchValue(function(value){
MyService.setValue(value); //this guys will resolve the previous promise
});
}
}
angular.module('myApp')
.controller('MyController', MyController);
})();
Note: Add validation to prevent possible issues
Remark: Basically you create a service which allows u to share states among different active controllers, MyService allows u to create a promise, which can be resolved at any time by setting the value. Do note though that resolve can be invoked only once per promise
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
}
}]);
My problem is I need "det" value applied to the controller and reload it.
Anyway nevermind it and continue reading first so you will understand my question.
I have this controller below.
At first load, the xxx isn't going to exist in the object then det value will be null. So it is expected that the controller's service will have an error telling that it can't be find. (See Controller code below)
However, when I click a button on my page (buttons html code is not here, I don't think it is necessary), it fills the object in and I'm wishing to reload the controller so I will see my expected output.
The HTML below is the one who loads the controller, what I'm expecting is that the
data-ng-model="{{$parent.$root.ParentItems['xxx'].xxx}}" will update the xxx value in controller. And it actually does because I'm using "<span>{{$parent.$root.ParentItems['xxx'].detnumber}}</span>" to test it.
Now, again,
My problem is I need the "det" value applied to the controller and reload it.
What I'm thinking is to create a new controller but I will just repeat the code.
//html
<div data-ng-switch-when="thisIsIt" ControllerOne data-ng-model="{{$parent.$root.ParentItems['xxx'].xxx}}"></div>
<span>{{$parent.$root.ParentItems['xxx'].xxx}}</span>
//Attribute ControllerOne
controller: function ($scope, $element, $http) {
function par() {
var xxx= null;
xxx = $scope.$parent.$root.ParentItems['xxx'].xxx;
var det = { xxx: xxx};
return det;
}
$http.post('/api/values/entries/GoHere', par()).success(function (salData) {
var buildSHGraph = function (shData) {
//code code codes...
}
$scope.Array1 = [];
angular.forEach(salData, function (evt) {
//Code Code Codes
});
buildSHGraph($scope.Array1);
});
}
I thing you can use $rootScope and pass your value to it. Then the value can be accessible globally by your application.
When your value is downloaded from ajax, the scope/html will be updated.
When you define variable with 'var xxx' cannot access it outside of the scope of this function, in your case 'par'.
function par() {
this.xxx = null;
this.xxx = $scope.$parent.$root.ParentItems['xxx'].xxx;
}
When you try to change view from callback from async task such as $http.post you need to '$digest' or '$apply' the scope
//Do this in success callback function in your ajax request
$timeout(function() { //$timeout must be setup as dependency in constructor such as $scope and $http
$scope.$digest();
});
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.
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.