Angular $http fails to return coherent cache - angularjs

I have a problem where a page has two components but only one of them is fully rendered.
The problem seem to be related to $http. I have a angular project where I need to construct a page based on RESTful API. The pages are such that I can expect multiple requests for the same data. At the moment, the set of requests are not behaving correctly.
For the sake of the argument (and also because it is a use case), the following page makes the same request twice.
game.html:
<html ng-app="prvdApp">
<head>
<meta charset="utf-8">
<base href="/">
<title>Providence</title>
<script src="/js/angular-1.6.2.js"></script>
<script src="/data-access/data-access.service.js"></script>
<script src="/score-info/score-info.component.js"></script>
<script src="/js/game.js"></script>
</head>
<body>
<div ng-controller="gameController">
<score-info game-id="8000"></score-info>
<score-info game-id="8000"></score-info>
</div>
</body>
game.js:
angular.module('prvdApp', [
'scoreInfo',
'drivesInfo' ]);
angular.
module('prvdApp').
controller('gameController', function() {
});
score-info.component.js:
angular.module('scoreInfo', [
'dataAccess'
]);
angular.
module('scoreInfo').
component('scoreInfo', {
templateUrl : '/score-info/score-info.template.html',
controller : function ScoreInfoController(dataAccess) {
self = this;
self.$onInit = function() {
dataAccess.game(self.gameId).then(function(game) {
self.game = game;
});
}
},
bindings : {
gameId : '<'
}
});
score-info.template.html:
<div>
Data available: {{ $ctrl.game != undefined }}
</div>
data-access.component.js:
angular.module('dataAccess', []);
angular.
module('dataAccess').
service('dataAccess',
function DataAccessService($http, $q) {
self = this;
self.game = function(game_id) {
var url = '/api/game/' + game_id;
return $http.get(url, { cache: true}).then(function(response) {
return response.data;
});
}
});
The behaviour is as follows:
The page renders with the content:
Data available: false
Data available: false
After some hundreds of milliseconds the $http -request finishes, the page is updated to the following state where only the latter component is updated.
Data available: false
Data available: true
It should be noted that the behaviour is the same even if the two components are of different types with different controllers, etc.

Related

AngularJS Help - User / Data driven UI Load data before finishing controller

So I have searched for multiple results online through StackOverflow and haven't really found the answer I am looking for. I am trying put together my main UI page with my AngularJS controller. What I want to accomplish is sort of a Synchronous call that will load all the user data from multiple queries as well as data driven nodes before the main UI page is actually completely loaded. Some of these queries take 2-3 seconds and I need them to finish before the user starts playing/seeing the UI.
Basically what I want to do is the following:
1) Person navigates to home page
2) User Data is Loaded
3) User Data drives data driven UI design
4) Fill out UI with ng-bind values
5) UI now finishing loading for the user to see
Note I am fine with a loading screen, don't really have
Small Example:
http://plnkr.co/edit/9W8wZX8PhrSzv7KE8IEt
<!DOCTYPE html>
<html ng-app="myApp" ng-controller="myCtrl">
<head>
</head>
<body>
<p><u>Username:</u></p>
<p ng-bind="username"></p>
<p><u>Completed in Order:</u></p>
<p ng-repeat="order in list">{{order}}</p>
<p><u>Projects:</u></p>
<p ng-repeat="data in datas">{{data}}</p>
<script data-require="angular.js#*" data-semver="1.2.9" src="http://code.angularjs.org/1.2.9/angular.js"></script>
<script src="script.js"></script>
</body>
</html>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, myService) {
$scope.list = [];
console.log('1 - Started Loading myCtrl');
$scope.list.push('1 - Started Loading myCtrl')
myService.getProjects().then(function(response){
//console.log(response);
$scope.datas = response.Result;
$scope.list.push('2 - getProjects Complete');
});
myService.getUsername().then(function(response){
//console.log(response);
$scope.username = response.Result;
$scope.list.push('3 - getUsername Complete');
});
//I don't want this to run until all the data is loaded
$scope.list.push('4 - Finished Loading myCtrl');
});
app.factory('myService', function($http) {
return{
getProjects : getProjects,
getUsername : getUsername
}
function getProjects(){
return $http.get('project.txt')
.then(getProjectsComplete)
.catch(getProjectsFailed);
function getProjectsComplete(response) {
return response.data;
}
function getProjectsFailed(error) {
console.log('XHR Failed for getProjectsFailed.' + error.data);
}
}
function getUsername(){
return $http.get('username.txt')
.then(getUsernameComplete)
.catch(getUsernameFailed);
function getUsernameComplete(response) {
return response.data;
}
function getUsernameFailed(error) {
console.log('XHR Failed for getUsernameFailed.' + error.data);
}
}
});
My Sample Files: project.txt
{
"Completed": true,
"Status": true,
"ExceptionType": "",
"ExceptionMessage": "",
"StartTime": "2016-12-16T14:19:17.7066714-05:00",
"EndTime": "2016-12-16T14:19:20.7486909-05:00",
"Result": [
"Project1",
"Project2",
"Project3",
"Project4"
]
}
Sample File: username.txt
{
"Completed": true,
"Status": true,
"ExceptionType": "",
"ExceptionMessage": "",
"StartTime": "2016-12-16T14:30:53.354957-05:00",
"EndTime": "2016-12-16T14:30:53.354957-05:00",
"Result": "IamBillyBob"
}
The UI Shows like this:
Username:
IamBillyBob
Completed in Order:
1 - Started Loading myCtrl
4 - Finished Loading myCtrl
3 - getUsername Complete
2 - getProjects Complete
Projects:
Project1
Project2
Project3
Project4
I want it to change to this and always load in this order 100% of the time:
Completed in Order:
1 - Started Loading myCtrl
2 - getProjects Complete
3 - getUsername Complete
4 - Finished Loading myCtrl

multi data from different schema, angular display

I have a really long json that each comes from different schema.
I did push in order to get them all in one json - that works.
know I want to use a controller for all of them and display it to the screen.
my index
<!DOCTYPE html>
<html ng-app="showFrozen">
<head>
<title>frozen</title>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/bootstrap-theme.min.css">
</head>
<body ng-controller="showFrozenCtrl">
<tbody>
<div ng-repeat="themes in showFrozenController.themes" ng-show="$first">
<h2>{{themes.theme}}</h2>
<span>for age: </span>
<p>{{themes.age}}</p>
<span>description: </span>
<p>{{themes.description}}</p>
<p>{{themes.description_more}}</p>
<img ng-src="{{themes.image}}" width="170" height="170">
</div>
</table>
<script src="js/lib/angular/angular.min.js"></script>
<script src="js/showFrozenController.js"></script>
</body>
</html>
my controller
var showFrozen = angular.module('showFrozen',[]);
showFrozen.filter("allItems", function() {
return function(frozen) {
var resultArr = [];
angular.forEach(frozen,function(item) {
resultArr.push(item);
});
return resultArr;
};
});
var model = {};
showFrozen.run(function($http) {
$http.get("http://localhost:3000/frozen").success(function(data){
console.log(data);
model.frozen = data;
});
});
showFrozen.controller('showFrozenCtrl',function($scope) {
$scope.showFrozenController = model;
});
so I don't get any output - but I see the json in the console, I'm attaching an image.
In your controller model is undefined.
Move the HTTP call to your controller and in the success assign the scope.showFrozenController to data
You need to make your $http request inside of your controller.
showFrozen.controller('showFrozenCtrl',function($scope, $http) {
$http.get("http://localhost:3000/frozen").success(function(data){
console.log(data);
$scope.model = data;
});
});
This is because when you try and print items out in your template (html) what is actually being accessed inside of any {{ }} blocks is your $scope object. So to make data available to your template you must store it on your $scope.
Have a read of this blog post
showFrozen.factory('frozenDataSrv',function($http) {
return {
getFrozenData: getFrozenData
};
function getFrozenData() {
return $http.get("http://localhost:3000/frozen")
.then(getFrozenDataComplete)
.catch(getFrozenDataFailed);
function getFrozenDataComplete(response) {
return response.data.results;
}
function getFrozenDataFailed(error) {
logger.error('XHR Failed for getFrozenData.' + error.data);
}
}
});
showFrozen.controller('showFrozenCtrl',function($scope, frozenDataSrv) {
frozenDataSrv.getFrozenData()
.then(function(response){
console.log(response)
})
});

AngularJS: Function in controller is called three times

I have a controller which calls a save service, and it is for some reason triggered three times. It is not that great if it saves three instances every time it is triggered. Is my approach of solving it wrong?
I found this following article which says it is a normal behavior in AngularJS
Below is an example which triggers that behavior. I'm using webpack to bundle AngularJS, and other dependencies.
FooCtrl
import {IFooService} from "./FooService";
export class FooCtrl {
static $inject = ["FooService"];
public fooService: IFooService;
constructor(fooService: IFooService) {
console.log("creating foo controller");
this.fooService = fooService;
}
public callService(): boolean {
console.log("call service");
this.fooService.save();
console.log(this.fooService.getSaves());
return true;
}
}
FooService
export interface IFooService {
save(): void;
getSaves(): number;
}
export class FooService implements IFooService{
private saves: number = 0;
public save(): void {
console.log("saved");
this.saves++;
}
public getSaves(): number {
return this.saves;
}
}
Main
namespace Main {
let fooModule = new FooModule.Module();
let main = angular.module("myApp", [
"ngRoute",
fooModule.getFooModule(),
]);
main.controller("BarCtrl", function($scope) {
$scope.message = "Bar";
});
main.config(function($routeProvider: ng.route.IRouteProvider) {
$routeProvider.when("/", {
"templateUrl": "foo/index.html",
});
});
}
index.html
<!doctype html>
<html ng-app="myApp">
<head>
<meta charset="UTF-8">
<title></title>
<script src="http://localhost:8080/webpack-dev-server.js"></script>
<script src="vendors.bundle.js"></script>
<script src="app.bundle.js"></script>
</head>
<body>
<div ng-view></div>
</body>
</html>
index.part.html
<div ng-controller="FooCtrl as ctrl">
<p ng-bind="ctrl.callService()"></p>
</div>
Because you are binding your method to the <p> element, it will be trigger on every digest cycle so that angular can check if the value changed.
I am not sure what you are trying to do exactly, but it looks like this this method should be trigger by a user action or maybe periodically and controlled using $timeout service.
Read more about scope digest cycle in the official documentation.

why factory don't work properly between the directives?

I make two directives .To communicate between two directives I used a factory .
but it not work properly ..I want to delete my text when I press delete button ..I take factory to do my task but it not working .I also try to take service .it also don't help
here is my code
http://plnkr.co/edit/Yenmira9J9XpjscQzRoX?p=preview
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
</head>
<body ng-app="app">
<a></a>
<b></b>
<script>
angular.module('app',[]).directive('a',function(){
return {
restrict :'E',
scope:{},
templateUrl:'a.html',
controller:'ts',
controllerAs:'vm'
}
}).controller('ts',function(sharedService){
var vm=this;
vm.delete=function(){
alert('--');
sharedService.deletepro();
}
}).directive('b',function(){
return {
restrict :'E',
scope:{},
templateUrl:'b.html',
controller:'bb',
controllerAs:'vm'
}
}).controller('bb',function(sharedService){
var pm=this;
pm.message= sharedService.sendData();
}).factory('sharedService', function() {
var data = {};
function deletepro(){
data = {};
}
function sendData(){
var obj = {name:"pQr"};
data = obj;
return data;
}
return {
sendData: sendData,
deletepro: deletepro
};
});
</script>
</body>
</html>
After your controller is first initialized, data and vm.message reference the same object, but when you run deletepro then data references a new object, but vm.message still references the old one.
If you want to pass data in this way, you must never replace data with a new object (otherwise, controllers will have to get the new object again).
Instead of data = {};, try data.name = '';
It looks like you're expecting that it will update because data is a shared reference. But you are resetting it to {}, which breaks the reference. You instead need to modify it:
function deletepro(){
for(var prop in data){
delete data[prop];
}
}
Also, keep in mind a and b are both real html tags, not sure if there are any issues ovewriting the standard ,

Backbone multi routers trap - sub router handler doesent invoke

got in trap with Backbone Router. Imagine, i have 2 Backbone Routers:
1) RootRouter - has only one route and the only responsibility - load subRouters with RequireJS and instance it.
var RootRouter = Backbone.Router.extend({
routes: {
'*all': 'invokeSubModule'
},
invokeSubModule: function(route, args) {
require(['SubRouter'], function(subRouter) {
new subRouter()
})
}
});
2) SubRouter - standard BB router with routes hash and handlers.
var SubRouter = Backbone.Router.extend({
routes: {
'some/bar': 'doBar',
'some/foo': 'doFoo'
},
doBar: function() { ... },
doFoo: function() { ... }
});
I start the application from some/bar URL.
On start RootRouter instancing and Backbone.History starts.
As expected RootRouter - match any URL and fire invokeSubModule - async load and SubRouter instancing works as expected, but the problem is associated with some/bar SubRouter handler does not firing as page URL has not changed from last route.
Looking for solution i've found answers only for the case u load sub routers before history start, but it useless in my case.
So after some digging i've found solution - extend Backbone.Route and override route method to make possible to invoke handler if the Backbone.getHash() is equal to the route method operate with.
Backbone.Router.extend({
route: function(route, name, callback) {
...
if (!callback) callback = this[name];
/* run handler immediately if route we add is the current URL fragment */
if(routeRegexp.test(Backbone.history.getHash()) ) {
this.execute(callback, this._extractParameters(routeRegexp, routeStr));
}
Backbone.history.route(route, function(fragment) {
....
});
return this;
}
})
So i'm confused that this just a hack and may cause possible bugs in future.
So looking for best practice how to resolve such issue and critic of my solution.
Also expect as possible answer how to manage routers lazy loading without RootRouter, as in this case first route will not be fired.
i was able to replicate the behavior you need without hacking the internals of backbone routing, but i have to do some stuff in the initialization.
first i will create the main router and start backbone history in with silent option = true
var mainRouter = new RootRouter();
Backbone.history.start({silent: true});
this will start backbone history, but without routing the current url.
then i got the current fragment and saved it for later use, then navigated to the base url, then back to the original fragment
var fragment = Backbone.history.fragment;
mainRouter.navigate('reset',true);
mainRouter.navigate(fragment, true);
the bad side of this approach is that you need to do 2 routing on start up
UPDATE:
below the full sample
<html><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title> sample </title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="http://underscorejs.org/underscore-min.js"></script>
<script type="text/javascript" src="http://backbonejs.org/backbone-min.js"></script>
<style type="text/css">
</style>
<script>
var RootRouter = Backbone.Router.extend({
routes: {
'*all': 'invokeSubModule',
'reset': 'invokeSubModule',
},
invokeSubModule: function(route, args) {
new SubRouter();
},
navigate: function () {
Backbone.Router.prototype.navigate.apply(this, arguments);
},
execute: function(callback, args) {
console.log('execute root');
Backbone.Router.prototype.execute.apply(this, arguments);
console.log ('current fragment ' + Backbone.history.fragment);
}
});
var SubRouter = Backbone.Router.extend({
routes: {
'some/bar': 'doBar',
'some/foo': 'doFoo'
},
navigate: function () {
Backbone.Router.prototype.navigate.apply(this, arguments);
},
execute: function(callback, args) {
console.log('execute sub');
Backbone.Router.prototype.execute.apply(this, arguments);
console.log ('current fragment ' + Backbone.history.fragment);
},
doBar: function() {
$('#content').html('').append('<p>BAR</p>');
},
doFoo: function() {
$('#content').html('').append('<p>FOO</p>');
}
});
$(document).ready(function(){
var mainRouter = new RootRouter();
Backbone.history.start({silent: true});
var fragment = Backbone.history.fragment;
mainRouter.navigate('#',true);
mainRouter.navigate(fragment, true);
$('a').click(function(){
mainRouter.navigate($(this).attr('href'));
});
});
</script>
</head>
<body>
<a id='home' href="#">home</a></br>
<a id='foo' href="#/some/foo">foo</a></br>
<a id='bar' href="#/some/bar">bar</a></br>
<div id='content'>HOME</div>
</body></html>

Resources