In my Angular app, the app.js code retrieves a sessionID and a userID from the server side.
At the same time, my navigation controller is setting up a menu which also comes from the server side.
The problem is that my navigation controller is executing BEFORE the app.js routine. And the result is that my sessionID and userID variables are assigned yet.
Ideally, I would like to know how to get my app.js code to fire before anything.
However, in the meantime, I would like to use the JavaScript setTimeout() function to simulate a deferred call. Meaning, I only want to continue when my sessionID and userID vars are defined.
My app.js code (where the session and user vars are initialized):
(function () {
'use strict';
angular.module('rage', [
'ui.router',
'ui.bootstrap',
'ui.dashboard',
'kendo.directives'
]).run(['$rootScope', '$state', 'userService', init]);
function init($rootScope, $state, userService) {
$rootScope.rageSessionVars = {};
$rootScope.$state = $state;
userService.getInitParams().then(function (razorEnvJson) {
$rootScope.rageSessionVars = razorEnvJson;
userService.init(rzEnvJson).then(function (data) {
var response = data.status;
if (response.match(/SUCCESS/g)) {
userService.openUserSession(razorEnvJson).then(function (data) {
var sessionID = data.data[0];
$rootScope.rageSessionVars.sessionID = sessionID;
$rootScope.rageSessionVars.userID = "bobmazzo1234";
console.log("sessionID = " + sessionID);
$rootScope.rageSessionVars.currDashboardName = "Default";
});
}
});
});
}
})();
The navigation-controller.js code (where the user and session vars are not defined yet):
function activate() {
$timeout(getDashboards, 1000); // give time for app.js to init vars
}
function getDashboards() {
// GET ALL DASHBOARDS VIA API.
var timeout;
if ($rootScope.rageSessionVars.sessionID == undefined) {
?? SHOULD I DO SOMETHING HERE IN CASE ???
}
var sid = $rootScope.rageSessionVars.sessionID;
var userId = $rootScope.rageSessionVars.userID;
dashboardcontext.getDashboards(sid, userId);
}
You could use $interval in your controller to check every x number of milliseconds if the userID and sessionID have returned, like this:
var stop = $interval(function() {
if ($rootScope.rageSessionVars.sessionID != undefined) {
//SUCCESS CODE HERE - DON'T FORGET TO STOP THE INTERVAL!
$interval.cancel(stop);
}
}, 100);
I've used 100 milliseconds, change to suit your needs. $interval API
You can use watcher:
$rootScope.$watch(function () {
return $rootScope.rageSessionVars.sessionID;
}, function (newValue, oldValue) {
if (newValue !== void 0) {
foo();
}
});
But i think you need use promises.
Related
I am working with angularjs and I am trying to access a value stored in vm.userid to the server side. I want to store a value from session-storage to vm.userid and then send it with the API request. I have checked my session-storage and the value I seek is indeed there. My value doesn't show up on the server-side, but a couple of minutes later it comes back empty (userid: " ").
This is the function in the StatisticsController:
(function () {
'use strict';
angular
.module('app')
.controller('StatistikController', StatistikController);
StatistikController.$inject = ['$location', 'AdminService','FlashService'];
function StatistikController($location, AdminService, FlashService) {
var vm = this;
vm.logout = logout;
initController();
function initController() {
loadStat();
}
function loadStat() {
vm.userid = angular.fromJson(sessionStorage.getItem('userid'));
AdminService.Stat(vm.userid)
.then(function (allStats) {
vm.allStats = allStats;
});
}
This is were the userid item is set to sessionStorage (which is in a different controller):
function loadUserid() {
UserService.GetUserid($rootScope.globals.currentUser.username)
.then(function (userid) {
vm.userid = userid;
sessionStorage.setItem('userid', angular.toJson(vm.userid));
});
}
So I have a small angular app that takes in a search query, sends it to an elasticsearch node I've got set up, and then displays the result set on screen.
My problem is that when I make a new query, the results gets appended to the end of the current result set. What I would like it to do is to erase whatever is currently on the page, and reload it with only the new data, much like how searching for something on Google returns a completely new set of results.
Is there any way to do this? Code below for reference.
// this is the controller that displays the reuslts.
var displayController = function($scope, $rootScope, $window, notifyingService) {
var dataReady = function(event, data) {
$scope.resultSet = notifyingService.getData();
}
$rootScope.$on('data-ready', dataReady)
}
app.controller("displayController", ["$scope", "$rootScope", "$window", "notifyingService", displayController]);
// this is the service that's responsible for setting the data
var notifyingService = function($http, $rootScope) {
var svc = {
_data: [],
setData: setData,
getData: getData
};
function getData() {
return svc._data;
}
function setData(data) {
var base_obj = data.hits.hits
console.log("Setting data to passed in data.");
console.log('length of dataset: ' + base_obj.length);
for(var i = 0; i < base_obj.length; i++){
svc._data.push(base_obj[i]._source);
}
$rootScope.$broadcast('data-ready', svc._data);
}
return svc;
};
app.factory("notifyingService", ["$http", "$rootScope", notifyingService]);
In setData just before the loop re-initialize svc._data
svc._data = [];
Clear you svc._data before you start adding the new query.
function setData(data) {
var base_obj = data.hits.hits;
svc._data = [];//reset your array before you populate it again.
for(var i = 0; i < base_obj.length; i++){
svc._data.push(base_obj[i]._source);
}
$rootScope.$broadcast('data-ready', svc._data);
}
I'm trying to lazy-load controllers inside route by using resolve:
.when('/somepage', {
resolve: {
load: function (loadDependencies, $q) {
return loadDependencies.load(['controllers/myCtrl.js'], [], []);
}
},
templateUrl: 'views/some-template.html'
})
Here's my loadDependencies factory:
app.factory('loadDependencies', function ($q, $timeout) {
return {
load: function (Controllers,cssFiles,modules) {
var jsPath = "scripts/",
cssPath = "css/",
head = document.getElementsByTagName("head")[0],
deffered = $q.defer(),
jsReady = 0,
jsShouldBeReady = Controllers.length;
Controllers.forEach(function (arrayItem) {
var js = document.createElement("script");
js.src = jsPath + arrayItem;
head.appendChild(js);
js.onload = function () {
jsReady++;
if (jsReady == jsShouldBeReady) { // if loaded files equal to controllers length, then they all finished loading - so resolve deffered
deffered.resolve(true);
}
};
js.onerror = function() {
alert("Cannot load js files. Pleae try again later");
};
});
return deffered.promise;
}
}
});
I'm new to angular, but from my understanding - deffered.promise should wait for the promise to resolve? currently it just returns the object. I also tried this:
deffered.promise.then(function () {
// call back here
});
But i'm failing to understand how to return the resolved value back to the controller.
First - your actual problem: if you want to load a controller when the file arrives lazily, you should read this. You need to register your controllers in order to lazy load them.
As for promises:
I'm a big proponent of always promisifying at the lowest level possible. Your code, performs aggregation on its own with an asynchronous semaphore - that logic is already implemented for you through $q.all which does the same thing, only with better error handling.
function loadScript(url){
var scr = document.createElement("script");
scr.src = url;
var d = $q.defer();
scr.onload = function(){ d.resolve(scr); };
scr.onerror = function(e){ d.reject(e); };
return d.promise;
}
That code is pretty clear, now, you can load multiple promises and wait for them through $q.all:
function load(files){
return $q.all(files.map(loadScript));
}
load([url1, url2, url2]).then(function(){
// all files are loaded, just like in your example.
});
I'm newbie to js and this is my first question in stackoverflow as well. So any comment or act of downgrading is understandable.
This is the angular-js-flowchart project on github.
This is another stackoverflow topic that teachs how to use factory as a data getter involving $http.
My need is to generate data for the chart by using an Angular factory that returns a $http function. The $http talks to a php service that retrieve data from database. I have tested the service using jsonlint and its working fine. The directory of service is checked, relatively to the html file.
I copied the "factory" code from another stackoverflow question and applied to app.js in the angularjs-flowchart Github project.
The problem is that the Chrome console keeps throwing an error that I can not understand. Data is not retrieved. The error on console is "TypeError: Cannot read property 'getData' of undefined"
This is the modified-by-me app.js:
//
// Define the 'app' module.
//
angular.module('app', ['flowChart', ])
//
// Simple service to create a prompt.
//
.factory('prompt', function () {
/* Uncomment the following to test that the prompt service is working as expected.
return function () {
return "Test!";
}
*/
// Return the browsers prompt function.
return prompt;
})
//
// Application controller.
//
.controller('AppCtrl', ['$scope', 'prompt', function AppCtrl ($scope, prompt, dataFactory) {
//
// Code for the delete key.
//
var deleteKeyCode = 46;
//
// Code for control key.
//
var ctrlKeyCode = 65;
//
// Set to true when the ctrl key is down.
//
var ctrlDown = false;
//
// Code for A key.
//
var aKeyCode = 17;
//
// Code for esc key.
//
var escKeyCode = 27;
//
// Selects the next node id.
//
var nextNodeID = 10;
//
// Event handler for key-down on the flowchart.
//
$scope.keyDown = function (evt) {
if (evt.keyCode === ctrlKeyCode) {
ctrlDown = true;
evt.stopPropagation();
evt.preventDefault();
}
};
//
// Event handler for key-up on the flowchart.
//
$scope.keyUp = function (evt) {
if (evt.keyCode === deleteKeyCode) {
//
// Delete key.
//
$scope.chartViewModel.deleteSelected();
}
if (evt.keyCode == aKeyCode && ctrlDown) {
//
// Ctrl + A
//
$scope.chartViewModel.selectAll();
}
if (evt.keyCode == escKeyCode) {
// Escape.
$scope.chartViewModel.deselectAll();
}
if (evt.keyCode === ctrlKeyCode) {
ctrlDown = false;
evt.stopPropagation();
evt.preventDefault();
}
};
//
// Add a new node to the chart.
//
$scope.addNewNode = function () {
var nodeName = prompt("Enter a task name:", "New Task");
if (!nodeName) {
return;
}
//
// Template for a new node.
//
var newNodeDataModel = {
name: nodeName,
id: nextNodeID++,
x: 0,
y: 0,
inputConnectors: [
{
name: "Pre"
}
],
outputConnectors: [
{
name: "Sub"
}
],
};
$scope.chartViewModel.addNode(newNodeDataModel);
};
//
// Add an input connector to selected nodes.
//
$scope.addNewInputConnector = function () {
var connectorName = prompt("Enter a connector name:", "New connector");
if (!connectorName) {
return;
}
var selectedNodes = $scope.chartViewModel.getSelectedNodes();
for (var i = 0; i < selectedNodes.length; ++i) {
var node = selectedNodes[i];
node.addInputConnector({
name: connectorName,
});
}
};
//
// Add an output connector to selected nodes.
//
$scope.addNewOutputConnector = function () {
var connectorName = prompt("Enter a connector name:", "New connector");
if (!connectorName) {
return;
}
var selectedNodes = $scope.chartViewModel.getSelectedNodes();
for (var i = 0; i < selectedNodes.length; ++i) {
var node = selectedNodes[i];
node.addOutputConnector({
name: connectorName,
});
}
};
//
// Delete selected nodes and connections.
//
$scope.deleteSelected = function () {
$scope.chartViewModel.deleteSelected();
};
//
// Setup the data-model for the chart.
//
var chartDataModel = {};
var handleSuccess = function(data, status){
chartDataModel = data;
console.log(chartDataModel);
};
dataFactory.getData().success(handleSuccess);
//
// Create the view-model for the chart and attach to the scope.
//
$scope.chartViewModel = new flowchart.ChartViewModel(chartDataModel);
}])
.factory('dataFactory', function($http){
return {
getData : function(){
return $http.post("chart-data-retrieve.php");
}
};
});
Basically, what i added but doesn't work is
// Setup the data-model for the chart.
//
var chartDataModel = {};
var handleSuccess = function(data, status){
chartDataModel = data;
console.log(chartDataModel);
};
dataFactory.getData().success(handleSuccess);
and
.factory('dataFactory', function($http){
return {
getData : function(){
return $http.post("chart-data-retrieve.php");
}
};
});
Please help, thanks.
I tried to set the chartViewModel of the $scope directly inside the service call, so the variable chartDataModel becomes redundant. And it works.
// Create the view-model for the chart and attach to the scope.
//
myService.then(function(data) {
$scope.chartViewModel = new flowchart.ChartViewModel(data);
});
I tried to return a promise, not a $http from the factory. It works now. The controller can now use the service to retrieve data. However I still could not set the controller's variable to the data retrieved.
The following is the code:
.factory('myService', function($http, $q) {
//this runs the first time the service is injected
//this creates the service
var deferred = $q.defer();
$http.get('chart-data-retrieve.php').then(function(resp) {
deferred.resolve(resp.data);
});
return deferred.promise;
})
And the code inside controller:
var chartDataModel = {};
//get data from myService factory
myService.then(function(data) {
alert(data);
chartDataModel = data;
});
Currently, the alert() show me the data already. However, the variable chartDataModel is still unset.
I have quite a few scenarios where I need clicks, etc. to trigger behavior in another place on the page (a one-way communication scenario). I now have a need for bi-directional communication, where stuff that happens in element A can modify specific properties in the scope behind element B and vice-versa. Thus far, I've been using $rootScope.$broadcast to facilitate this but it feels like overkill, and winds up creating boilerplate in both places:
$scope.$on('event-name', function(event, someArg) {
if(someArg === $scope.someProperty) return;
$scope.someProperty = someArg;
});
$scope.$watch('someProperty', function(newValue) {
$rootScope.$broadcast('event-name', newValue);
});
Is there a better way? I'd like to tie the two (or three, or N) scopes together via a service, but I don't see a way to do that without magic event names and boilerplate.
I haven't used this myself, but this post explains basically how I would do it. Here's the code which illustrates the idea:
(function() {
var mod = angular.module("App.services", []);
//register other services here...
/* pubsub - based on https://github.com/phiggins42/bloody-jquery-plugins/blob/master/pubsub.js*/
mod.factory('pubsub', function() {
var cache = {};
return {
publish: function(topic, args) {
cache[topic] && $.each(cache[topic], function() {
this.apply(null, args || []);
});
},
subscribe: function(topic, callback) {
if(!cache[topic]) {
cache[topic] = [];
}
cache[topic].push(callback);
return [topic, callback];
},
unsubscribe: function(handle) {
var t = handle[0];
cache[t] && d.each(cache[t], function(idx){
if(this == handle[1]){
cache[t].splice(idx, 1);
}
});
}
}
});
return mod;
})();
Note the memory leak though if controllers are "deleted" without unsubscribing.
I think you can try the following service,
'use strict';
angular.module('test')
.service('messageBus', function($q) {
var subscriptions = {};
var pendingQuestions = [];
this.subscribe = function(name) {
subscriptions[name].requestDefer = $q.defer();
return subscriptions[name].requestDefer.promise; //for outgoing notifications
}
this.unsubscribe = function(name) {
subscriptions[name].requestDefer.resolve();
subscriptions[name].requestDefer = null;
}
function publish(name, data) {
subscriptions[name].requestDefer.notify(data);
}
//name = whom shd answer ?
//code = what is the question ?
//details = details abt question.
this.request = function(name, code, details) {
var defered = null;
if (subscriptions[name].requestDefer) {
if (pendingQuestions[code]) {
//means this question is already been waiting for answer.
//hence return the same promise. A promise with multiple handler will get
//same data.
defered = pendingQuestions[code];
} else {
defered = $q.defer();
//this will be resolved by response method.
pendingQuestions[code] = defered;
//asking question to relevant controller
publish(name, {
code: code,
details: details
});
}
} else {
//means that one is not currently in hand shaked with service.
defered = $q.defer();
defered.resolve({
code: "not subscribed"
});
}
return defered.promise;
}
//data = code + details
//responder does not know the destination. This will be handled by the service using
//pendingQuestions[] array. or it is preemptive, so decide by code.
this.response = function(data) {
var defered = pendingQuestions[data.code];
if (defered) {
defered.resolve(data);
} else {
//means nobody requested for this.
handlePreemptiveNotifications(data);
}
}
function handlePreemptiveNotifications() {
switch (data.code) {
//handle them case by case
}
}
});
This can be used as a message bus in multi controller communication. It is making use of the angular notify() callback of promise API.All the participating controllers should subscribe the service as follows,
angular.module('test')
.controller('Controller1', function($scope, messageBus) {
var name = "controller1";
function load() {
var subscriber = messageBus.subscribe(name);
subscriber.then(null, null, function(data) {
handleRequestFromService(data);
});
}
function handleRequestFromService(data) {
//process according to data content
if (data.code == 1) {
data.count = 10;
messageBus.respond(data);
}
}
$scope.$on("$destroy", function(event) {
//before do any pending updates
messageBus.unsubscribe(name);
});
load();
});
angular.module('test')
.controller('Controller2', function($scope, messageBus) {
var name = "controller2";
function load() {
var subscriber = messageBus.subscribe(name);
subscriber.then(null, null, function(data) {
handleRequestFromService(data);
});
}
function handleRequestFromService(data) {
//process according to data content
}
$scope.getHorseCount = function() {
var promise = messageBus.request("controller1", 1, {});
promise.then(function(data) {
console.log(data.count);
});
}
$scope.$on("$destroy", function(event) {
//before do any pending updates
messageBus.unsubscribe(name);
});
load();
});