Hi all,
My environment : vwd 2013, sql server 2012, project template : Hot towel - Angular (by John Papa), BreezeJs, Breeze.WebApi2, Breeze.ContextProvider.EF6, Entity-Framework ADO .Net 6.
I'm making a little Web app SPA, everything used to work correctly as long as I instanciate a new BreezeManager before every Web API call with entityManagerFactory, all the web api calls are done in 2 services datacontext and authenticationservice (of my own development). Then I discover that app was going much faster with a global BreezeManager instanciated once at the start of the 2 services, and use it for all the web api calls. But apparently it's going too fast so that "Busyindicator" / togglespinner to be started, so the page appears white before being populated with data, the togglespinner and its waiting message are not displayed while the treatment.
I've well seen, in the shell.js, that the togglespinner is started at every route's change and stopped at every controller succesfull activation.
So to start correctly the app, I have created an intermediate page 'await.html' and its vm 'await.js' that's only redirecting to a new page, this page is at the root of routes.
So it should be working like this, the web app is started, going to 'await.html', then redirection to a new page, the togglespinner is started, then promise of this new page is correctly terminated and the togglespinner is stopped, but it doesn't work.
I remind you, that with a new BreezeManager for every web api call, it works.
My page is constituted (as originally) with a dynamic sidebar menu, a dynamic topnav and the page itself, in these 3 components there are web api calls, for the sidebar and topnav the web api calls are done only at the app start, maybe there is a problem of concurrency with these 3 components ?
Here is some code :
datacontext.js :
(function () {
'use strict';
var serviceId = 'datacontext';
angular.module('app').service(serviceId, ['common', 'config', 'model', 'entityManagerFactory', 'breezePartialMapper', datacontext]);
function datacontext(common, config, model, entityManagerFactory, breezePartialMapper) {
var $q = common.$q;
var manager = entityManagerFactory.newManager();
var entityNames = model.entityNames;
.
.
.
function getUser() {
//var manager = entityManagerFactory.newManager();
var query = breeze.EntityQuery.from('GetUser').expand("Membership").withParameters({ UserId: user.userId });
//var resLoc = getLocal(query);
return $q.when(manager.executeQuery(query)
.then(successCallback)
.catch(failCallback) // same as 'then(null, failCallback)'
.finally(finalCallback) // sort of like 'then(finalCallback, finalCallback)'
);
}
.
.
.
function successCallback(data) {
return data.results;
}
.
.
.
}
In the displayed page vm :
(function () {
'use strict';
var controllerId = 'FGHomePage';
angular.module('app').controller(controllerId, ['common', 'datacontext', FGHomePage]);
function FGHomePage(common, datacontext) {
var getLogFn = common.logger.getLogFn;
var log = getLogFn(controllerId);
var $q = common.$q;
var vm = this;
vm.title = 'FGHomePage';
vm.user;
vm.imageUrl = "";
activate();
function activate() {
var promises = [getUser()];
common.activateController(promises, controllerId)
.then(function () { log('Activated Admin View'); });
}
function getUser() {
datacontext.user.userId = 1;
return datacontext.getUser()
.then(function (data) {
vm.user = data[0];
if (vm.user.fileExtension != "" && vm.user.fileExtension != null) {
vm.imageUrl = vm.user.userName.replace(" ", "_") + "." + vm.user.fileExtension;
}
else {
vm.imageUrl = "NonImage.gif";
}
//return $q.when(vm.user);
return vm.user;
});
}
}
})();
Do you see something wrong in the code ?
Thanx for your help.
I found a solution to this problem.
I had to change the event starting the togglespinner.
Now to start the togglespinner I've choosen the beginning of activatecontroller before the resolution of promises.
For this :
- in config.js : I add the start activate event :
var events = {
controllerStartActivate: 'controller.startActivate',
controllerActivateSuccess: 'controller.activateSuccess',
spinnerToggle: 'spinner.toggle'
};
and :
//#region Configure the common services via commonConfig
app.config(['commonConfigProvider', function (cfg) {
cfg.config.controllerStartActivateEvent = config.events.controllerStartActivate;
cfg.config.controllerActivateSuccessEvent = config.events.controllerActivateSuccess;
cfg.config.spinnerToggleEvent = config.events.spinnerToggle;
}]);
//#endregion
Then in common.js I broadcast this event at the beginning of activateController :
function activateController(promises, controllerId) {
$broadcast(commonConfig.config.controllerStartActivateEvent);
return $q.all(promises).then(function (eventArgs) {
var data = { controllerId: controllerId };
$broadcast(commonConfig.config.controllerActivateSuccessEvent, data);
});
}
Then in shell.js I start the togglespinner on this event :
$rootScope.$on(events.controllerStartActivate,
function (data) { toggleSpinner(true); }
);
In this way the togglespinner will be started at the beginning of activatecontroller and stopped after the resolution of the promises of the controller. If there are several controllers activated at (almost) the same time, for each a togglespinner will appear (as there are instances of the same togglespinner we don't see a difference visually). This last case doesn't appear often.
Hope this will help, if you see an improvement...
Related
I have an Umbraco project with an Area section configured with Angular.
I use the Plugins to integrate the Area with the use of package.manifest like this:
Into edit.controller.js, I have this script:
'use strict';
angular.module("umbraco")
.controller('Administration.AdministrationTree.EditController', function administrationEditController($scope, $routeParams, $http) {
//set a property on the scope equal to the current route id
$scope.id = $routeParams.id;
$scope.url = "";
$scope.canShow = false;
$scope.showIframe = function () {
if ($scope.url === "") {
return false;
}
return true;
};
$scope.canShow = false;
if (!$scope.id) {
return;
}
$http.get('/umbraco/backoffice/administration/CustomSection/GetUrl/?node=' + $scope.id)
.success(function (data) {
$scope.url = JSON.parse(data);
$scope.canShow = $scope.url;
});
});
When I run the project and click on any node in this area, I receive most of the time a 404 error like if the page was not exist. I say "most of the time" because 1 out of 10, it works and the page is displayed.
However, if I put a breakpoint in the javascript function below and I click on any node and resume the javascript after the breakpoint was hitting, the node related html page is displayed correctly.
Anybody know why when I put a breakpoint, Umbraco or Angular are able to resolve 100% of the time the page but not when I don't have any breakpoint in this function?
Thanks
I really hate to answer my own questions but after 2 weeks without answers and a lot of reflections on my side, I finally found a solution to this problem.
What was causing the problem of out synching between Umbraco and Angular was due to the $http.get query which is asynchronous with Angular (no other choice I think) and after a response from the server to get a valid URL, the $scope object was not able to talk to Umbraco to redirect to the valid URL.
On my asp.net MVC controller, the GetUrl function was trying to get a valid URL doing a query to the database where I keep a structure of nodes which correspond to a tree displayed to the user. This is a slow process and the time required to respond to the HTTP get request was too long the vast majority of the time.
Here is my solution to this problem:
'use strict';
var myCompany = myCompany || {};
myCompany.myProject = myCompany.myProject || {};
myCompany.myProject.controller = (function (parent){
parent.urls = {};
function loadUrls () {
$.get('/umbraco/backoffice/administration/CustomSection/GetUrls')
.success(function (data) {
parent.urls = data;
});
};
loadUrls();
return parent;
})({});
angular.module("umbraco")
.controller('Administration.AdministrationTree.EditController', function administrationEditController($scope, $routeParams, $http) {
//set a property on the scope equal to the current route id
$scope.id = $routeParams.id;
$scope.url = "";
$scope.canShow = false;
$scope.showIframe = function () {
if ($scope.url === "") {
return false;
}
return true;
};
$scope.canShow = false;
if (!$scope.id) {
return;
}
var url = myCompany.myProject.controller.urls.find(function (element) {
return element.Key == $scope.id;
});
if (url) $scope.url = url.Value;
$scope.canShow = $scope.url;
});
Note in this case that I have an iffe function which query the server to build an array of all my URLs from the backoffice and then when Angular need a redirection, I search directly from the array.
The iffe function is calling only once when the user enters in the backoffice section which I think is nice because the structure behind rarely changes.
I'm not sure if it's a hack or the valid way to do the thing due to my lack of experience with Angular but it works like a charm.
So i'm building a web application in AngularJS that connects to an API (Qlik Sense Engine API) with QSocks.
Qsocks is a lightweight wrapper around the Qlik Sense Engine API wrapper that is written in NodeJS but can also be imported in a web environment. QSocks contains and uses the NPM package Promise package so it uses it's own non AngularJS promises.
My service looks like this:
var app_promise = (appFactory.activeConnection() ? appFactory.activeConnection() : appFactory.app());
this.getData = function(qMeasures, time) {
ratioChild.qHyperCubeDef.qMeasures[0].qDef.qDef = qMeasures;
ratioChild.qHyperCubeDef.qMeasures[0].qDef.qLabel = qMeasures;
ratioChild.qHyperCubeDef.qDimensions[4].qDef.qFieldDefs = [time];
ratioChild.qHyperCubeDef.qDimensions[4].qDef.qFieldLabels = [time];
var deferred = $q.defer();
app_promise.then(function (obj) {
obj.createChild(ratioChild).then(function (childObj) {
deferred.resolve(childObj);
});
});
return deferred.promise;
}
In simple words, when i call this service in e.g. a controller. I get an object where i can build other objects on it.
Side Note:
I do need to make a new AngularJS promise because app_promise.then and obj.createChild(ratioChild).then are the NPM promise package promises.
This is how my controller looks like (first part):
if (!$rootScope.balanceSheetFixedObj) {
var fixYearsqMeasure = "Sum({<Jaar=>}Saldo)";
balanceSheetService.getData(fixYearsqMeasure, self.time).then(function (childObj) {
$rootScope.balanceSheetFixedObj = childObj;
return childObj;
}).then(handleFixData)
} else {
handleFixData();
}
This is how my controller looks like (second part):
function handleFixData(childObj) {
childObj = (childObj) ? childObj : $rootScope.balanceSheetFixedObj;
childObj.getLayout().then(function(data) {
self.data = data;
if (data.qHyperCube.qPivotDataPages[0].qData.length > 0) {
var fixPivotData = data.qHyperCube.qPivotDataPages[0];
self.labels = fixPivotData.qLeft;
$scope.$apply(); // Here is my problem!
With $scope.$apply() my view is publishes/updated after a second.
If i leave out the $scope.$apply() it do publish/update the view but after 10-15 Seconds.. Way to late! Why is my view so slow? I would like to leave out the $scope.$apply()
I manage to solve my own problem. After looking back it was quite obvious what my problem was.
Thanks to #charlietfl i've taken a look to the childObj.getLayout(). What i saw what that the getLayout() function returns a QSocks promise and the code that updates my view was written inside of the .then() of the QSocks promise. As getLayout() is not an angular promise, this was the problem. My view was not updated properly.
My solution was to create a service function that creates an Angular Promise
this.getObjLayout = function(childObj) {
var deferred = $q.defer();
childObj.getLayout().then(function(data) {
deferred.resolve(data);
});
return deferred.promise;
}
And in the controller i invoke that function
function handleFixData(childObj) {
childObj = (childObj) ? childObj : $rootScope.balanceSheetFixedObj;
balanceSheetService.getObjLayout(childObj).then(function (data) {
self.data = data;
if (data.qHyperCube.qPivotDataPages[0].qData.length > 0) {
var fixPivotData = data.qHyperCube.qPivotDataPages[0];
self.labels = fixPivotData.qLeft;
}
})
}
In our application we fetch data from a service which happens to be a long running transaction at times (10+ seconds). Currently we invoke it as a part of the "Resolve" in config() of the module, this blocks the page until the service data is fetched completely.
Can we pre-fetch the data by invoking the service but not block the page execution?
In a not so recent project I simply let the services I called in Resolve return promises.
var resolve = {
localize : 'localize',
AttackService : 'AttackService.promise',
CharacterService : 'CharacterService.promise',
StateRestorer: 'StateRestorer'
};
~function(){
"use strict";
/**
* #class EVD.services.StateRestorer
*
* For now where we load up all the state about our hero, etc when we
* refresh the game
*
**/
EVD.modules.service
.factory('StateRestorer', ['models', 'HeroAPI', 'BattleAPI', '$rootScope','$q',
function(models, HeroAPI, BattleAPI, $rootScope, $q) {
var principal;
var fns = []; //List of functions to execute
principal = HeroAPI.principal();
fns.push(principal);
var character = HeroAPI.character();
fns.push(character.$promise);
var mounted = HeroAPI.mounted();
fns.push(mounted.$promise);
var recentKills = BattleAPI.recentKills();
fns.push(recentKills.$promise);
var deferred = $q.defer();
$q.all(fns).then(
function(data) {
$rootScope.user = data[0];
EVD.common.extractPayload(models.get('playerState.character'), data[1]);
EVD.common.extractPayload(models.get('playerState.equipment'), data[2]);
models.set('playerState.recentKills', data[3].result);
deferred.resolve();
}
);
return deferred.promise;
}])
}();
I agree with Cetia. Why not just call the service it inside the controller? Because it's a long running task you can make use of the third function for promises:
myTask.then(function(success) {
// Do stuff with the result
}, function(error) {
// Handle error
}, function(progress) {
// Publish progress to the UI
});
This way you can fetch the data but not block the UI.
I don't know what it is about injecting factories, but I am having the most difficult time.
I've simulated what I'm attempting to do via this sample plunk http://plnkr.co/edit/I6MJRx?p=preview, which creates a kendo treelist - it works fine.
I have an onChange event in script.js which just writes to the console. That's also working.
My plunk loads the following:
1) Inits the app module, and creates the main controller myCtrl (script.js)
2) Injects widgetLinkingFactory int myCtrl
3) Injects MyService into widgetLinkingFactory
The order in which I load the files in index.html appears to be VERY important.
Again, the above plunk is NOT the real application. It demonstrates how I'm injecting factories and services.
My actual code is giving me grief. I'm having much trouble inject factories/services into other factories.
For example,
when debugging inside function linking() below, I can see neither 'CalculatorService' nor 'MyService' services. However, I can see the 'reportsContext' service.
(function () {
// ******************************
// Factory: 'widgetLinkingFactory'
// ******************************
'use strict';
app.factory('widgetLinkingFactory', ['reportsContext', 'MyService', linking]);
function linking(reportsContext, MyService) {
var service = {
linkCharts: linkCharts
};
return service;
function linkCharts(parId, widgets, parentWidgetData) {
// *** WHEN DEBUGGING HERE, ***
// I CANNOT SEE 'CalculatorService' AND 'MyService'
// HOWEVER I CAN SEE 'reportsContext'
if (parentWidgetData.parentObj === undefined) {
// user clicked on root node of grid/treelist
}
_.each(widgets, function (wid) {
if (wid.dataModelOptions.linkedParentWidget) {
// REFRESH HERE...
}
});
}
}
})();
A snippet of reportsContext'service :
(function () {
'use strict';
var app = angular.module('rage');
app.service('reportsContext', ['$http', reportsContext]);
function reportsContext($http) {
this.encodeRageURL = function (sourceURL) {
var encodedURL = sourceURL.replace(/ /g, "%20");
encodedURL = encodedURL.replace(/</g, "%3C");
encodedURL = encodedURL.replace(/>/g, "%3E");
return encodedURL;
}
// SAVE CHART DATA TO LOCAL CACHE
this.saveChartCategoryAxisToLocalStorage = function (data) {
window.localStorage.setItem("chartCategoryAxis", JSON.stringify(data));
}
}
})();
One other point is that in my main directive code, I can a $broadcast event which calls the WidgetLinking factory :
Notice how I'm passing in the widgetLinkingFactory in scope.$on. Is this a problem ?
// Called from my DataModel factory :
$rootScope.$broadcast('refreshLinkedWidgets', id, widgetLinkingFactory, dataModelOptions);
// Watcher setup in my directive code :
scope.$on('refreshLinkedWidgets', function (event, parentWidgetId, widgetLinkingFactory, dataModelOptions) {
widgetLinkingFactory.linkCharts(parentWidgetId, scope.widgets, dataModelOptions);
});
I am wasting a lot of time with these injections, and it's driving me crazy.
Thanks ahead of time for your assistance.
regards,
Bob
I think you might want to read up on factories/services, but the following will work:
var app = angular.module('rage')
app.factory('hi', [function(){
var service = {};
service.sayHi = function(){return 'hi'}
return service;
}];
app.factory('bye', [function(){
var service = {};
service.sayBye = function(){return 'bye'}
return service;
}];
app.factory('combine', ['hi', 'bye', function(hi, bye){
var service = {};
service.sayHi = hi.sayHi;
service.sayBye = bye.sayBye;
return service;
}];
And in controller...
app.controller('test', ['combine', function(combine){
console.log(combine.sayHi());
console.log(combine.sayBye());
}];
So it would be most helpful if you created a plunk or something where we could fork your code and test a fix. Looking over your services it doen't seem that they are returning anything. I typically set up all of my services using the "factory" method as shown below
var app = angular.module('Bret.ApiM', ['ngRoute', 'angularFileUpload']);
app.factory('Bret.Api', ['$http', function ($http: ng.IHttpService) {
var adminService = new Bret.Api($http);
return adminService;
}]);
As you can see I give it a name and define what services it needs and then I create an object that is my service and return it to be consumed by something else. The above syntax is TypeScript which plays very nice with Angular as that is what the Angular team uses.
I know there are other similar questions on how to pass data between Angular controllers.
What I wonder is how to deal with this in a view..
Lets say I have a UserController for login, registration etc.
And an AppController for the actual app functionallity .
The UserController would be fairly easy, its sort of standalone from the rest.
But what if the app needs to know about stuff from the user controller?
Lets say the app view needs to hide/show stuff depending on if the user is logged in or not.
Or it could be if the user is male or female etc.
Should the app model keep its own copy of the user model state?
e.g. appModel.isLoggedIn , appModel.gender etc ?
feels a bit redundant, but at the same time more testable.
So what is the correct way to do this?
Short answer
Create a service, see Creating Services for details.
Long answer
Services are - per se - application-wide singletons, hence they are perfect for keeping state across views, controllers & co.:
app.factory('myService', [ function () {
'use strict';
return {
// Your service implementation goes here ...
};
}]);
Once you have written and registered your service, you can require it in your controllers using AngularJS' dependency injection feature:
app.controller('myController', [ 'myService', '$scope',
function (myService, $scope) {
'use strict';
// Your controller implementation goes here ...
}]);
Now, inside your controller you have the myService variable which contains the single instance of the service. There you can have a property isLoggedIn that represents whether your user is logged in or not.
To further specify the answer #GoloRoden gave, this is an example of how you can share state values across all controllers taking the service as a dependency.
App.factory('formState', formState);
function formState() {
var state = {};
var builder = "nope";
var search = "nope";
state.builder = function () {
return builder;
};
state.search = function () {
return search;
};
state.set = {
'builder': function (val) {
builder = val;
},
'search': function (val) {
search = val;
}
};
return {
getStateManager: function () {
return state;
}
};
}
App.controller('builderCtrl', builderCtrl);
builderCtrl.$inject = ['formState']
function builderCtrl(formState) {
var stateManager = formState.getStateManager();
activate();
function activate() {
console.log("setting val in builder");
stateManager.set.search("yeah, builder!");
console.log("reading search in builder: " + stateManager.search());
console.log("reading builder in builder: " + stateManager.builder());
}
}
App.controller('searchCtrl', searchCtrl);
searchCtrl.$inject = ['formState']
function searchCtrl(formState) {
var stateManager = formState.getStateManager();
activate();
function activate() {
console.log("setting val in search");
stateManager.set.search("yeah, search!");
console.log("reading search in search: " + stateManager.search());
console.log("reading builder in search: " + stateManager.builder());
}
}