I'm storing data and language terms for the website with json, and depending on the language selected i load the adequate file. The problem is when i switch the language, only the adress in the adressbar change without reloading the right json file.
factory.js
app.factory('PostsFactory', function ($http, $q, $timeout) {
var factory = {
posts: false,
find: function (lang) {
var deferred = $q.defer();
if (factory.posts !== false) {
deferred.resolve(factory.posts);
} else {
$http.get(lang+'/json.js').success(function (data, status) {
factory.posts = data;
$timeout(function () {
deferred.resolve(factory.posts);
})
}).error(function (data, status) {
deferred.reject('error')
});
}
return deferred.promise;
},
get: function (id, lang) {
var deferred = $q.defer();
var post = {};
var posts = factory.find(lang).then(function (posts) {
angular.forEach(posts, function (value, key) {
if (value.id == id) {
post = value;
};
});
deferred.resolve(post);
}, function (msg) {
deferred.reject(msg);
})
return deferred.promise;
}
}
return factory;
})
controller
posts.js
app.controller('PostsCtrl', function ($scope, PostsFactory, $rootScope, $routeParams) {
$rootScope.loading = true;
lang = $routeParams.lang;
PostsFactory.find(lang).then(function (posts) {
$rootScope.loading = false;
$scope.posts = posts;
}, function (msg) {
alert(msg);
});
});
post.js
app.controller('PostCtrl', function ($scope, PostsFactory, $routeParams, $rootScope) {
$rootScope.loading = true;
SounanFactory.get($routeParams.id, $routeParams.lang).then(function (post) {
$rootScope.loading = false;
$scope.title = post.title;
$scope.text = post.text;
$scope.image = post.image;
$scope.id = post.id;
}, function (msg) {
alert(msg);
});
});
app.js
var app = angular.module('Posts', ['ngRoute']);
app.config(function ($routeProvider) {
$routeProvider.when('/:lang/', {
templateUrl: 'partials/posts.html', controller: 'PostsCtrl'})
.when('/:lang/post/:id', {
templateUrl: 'partials/post.html', controller: 'PostCtrl'})
.otherwise({
redirectTo: '/en'
});
})
posts.html
<div ng-hide="loading">
<a href="#/en/post/{{post.id}}" ng-repeat="post in posts " class="{{post.class}}">
<div class="title">{{post.title}}</div>
<div class="index">{{post.id}}</div>
</a>
</div>
index.html
<body ng-app="Posts">
<div ng-show="loading">Loading ...</div>
<div class="container" ng-view></div>
<a href="#/fr" >FR</a>
<a href="#/en" >EN</a>
</body>
In your posts.js have you tried adding $scope.$apply() underneath $scope.posts = posts; in posts.js and again under $scope.id = post.id; in post.js?
The problem that you're facing is probably due to Angular's digest cycle and the bizarre way you're trying to pass values around through promises/objects/wizardry.
I mean, it's kind of the least of your worries. I know nothing of the project but there are a few things that could be causing an issue here.
Why do you have a $timeout() without a time?
Why are you setting a lang var and then immediately using it instead of just passing it through?
Why are you assigning a posts member of your factory and then using it immediately instead of just passing it through?
You should be using ng-href for dynamic url's
Let us know how you get on.
Related
I am using AngularJS and I have 2 controllers which are siblings. The first controller gets data from an $http request on click. The second controller has to retrieve this data, but I don't know how this controller will get the data because it has to wait for the function in the first controller before it can get its data.
This is the first controller:
$scope.getMessageData = function(username, full_url, main_item, item_id, sub_id){
$scope.ajax_spinner = true;
$http({
method: "GET",
url: "/getMessageData",
params:{
"adUsername" : username,
"fullUrl" : full_url,
"mainItem" : main_item,
"tagId" : item_id,
"subId" : sub_id
}
}).then(function successCallback(response) {
$scope.adUsername = response.data.adUsername;
$scope.fullUrl = response.data.fullUrl;
$scope.main_item = response.data.main_item;
$scope.message = response.data.docs[0];
$scope.sub_item = response.data.sub_docs[0];
$scope.ajax_spinner = false;
Data.setItem($scope.message)
});
};
The factory I use to share the data with the second controller:
App.factory('Data', function(){
return {
setItem: function(item){
this.item = item;
},
getItem: function(){
return this.item;
}
};
});
The second controller:
App.controller("dataController", function($scope, $http, $sce, Data){
$scope.message = Data.getItem();
console.log($scope.message)
});
Now obviously $scope.message is undefined because the controller already loads on page load, but the getMessageData function is not ready called, so how can I "wait" for the second controller to load before getMessageData is fired?
You could use $watch in your controller to keep up with the changes in your service, e.g.
var unwatch = $scope.$watch(function() {
/* set Data.item value in your service */
return Data.item;
}, function(item) {
$scope.item = item || 'nothing here';
});
$scope.$on('$destroy', unwatch);
Or you could implement simple mechanism to register and unregister callbacks in your service to get notified when ever data is changed, e.g.
Template
<html ng-app="App">
<head>
...
</head>
<body>
<div ng-controller="SomeController">
SomeController
<button ng-click="getData()">Fetch</button>
<div ng-bind="data | json"></div>
</div>
<br>
<div ng-controller="OtherController">
OtherController
<div ng-bind="data | json"></div>
</div>
</body>
</html>
JavaScript
angular.module('App', [])
.controller('SomeController', function($scope, Data) {
$scope.getData = function() {
Data.getData().then(function(data) {
$scope.data = data;
});
};
})
.controller('OtherController', function($scope, Data) {
var callback = function(data) {
$scope.data = data;
};
Data.register(callback);
$scope.$on('$destroy', function() {
Data.unregister(callback);
});
})
.factory('Data', function($http) {
var callbacks = [];
return {
register: function(callback) {
var index = callbacks.indexOf(callback);
if (index === -1) {
callbacks.push(callback);
}
},
unregister: function(callback) {
var index = callbacks.indexOf(callback);
if (index !== -1) {
callbacks.splice(index, 1);
}
},
getData: function() {
/* where data.json is { "message": "My message" } */
return $http.get('data.json').then(function(response) {
var data = response.data;
data.timestamp = (new Date()).getTime();
/* move inside $interval out of this fn maybe... */
angular.forEach(callbacks, function(callback) {
callback(data);
});
return data;
});
}
};
});
This is a basic weather app that grabs info from an open weather API. Upon loading, it gets the weather information for the default city and I'm able to successfully log the returned info to the console, however my view doesn't update until I switch to a different view and then back. I feel like a $scope.$apply needs to go somewhere, but I couldn't get it working anywhere I tried.
App:
var weather = angular.module('weather', ['ngRoute', 'ngResource', 'ui.router']);
weather.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/overview');
$stateProvider
.state('overview', {
url: '/overview',
templateUrl: 'pages/overview.html',
controller: 'overviewController'
})
.state('forecast', {
url: '/forecast',
templateUrl: 'pages/forecast.html'
})
.state('details', {
url: '/details',
templateUrl: 'pages/details.html'
})
});
weather.controller('homeController', ['$scope', '$location', '$resource', 'weatherService', function($scope, $location, $resource, weatherService) {
$scope.txtCity = weatherService.city;
$scope.submit = function() {
weatherService.city = $scope.txtCity;
weatherService.getForecast(weatherService.city, function(x){
weatherService.currentForecast = x;
// TESTING
console.log(x);
});
};
// Run the submit function initially to retrieve weather data for the default city
$scope.submit();
// Function to determine the active tab, sets its class as "active"
$scope.isActive = function (path) {
return ($location.path().substr(0, path.length) === path) ? 'active' : '';
}
}]);
weather.controller('overviewController', ['$scope', '$filter', 'weatherService', function($scope, $filter, weatherService) {
$scope.currentForecast = weatherService.currentForecast;
// Kelvin to Fahrenheit
$scope.convertTemp = function(temp) {
return Math.round((1.8*(temp - 273))+32);
}
$scope.convertToDate = function(dt) {
var date = new Date(dt * 1000);
return ($filter('date')(date, 'EEEE, MMM d, y'));
};
}]);
weather.service('weatherService', function($resource, $http){
this.currentForecast = null;
// default city
this.city = 'Chicago, IL';
this.getForecast = function(location, successcb) {
$http({
method : "GET",
url : "http://api.openweathermap.org/data/2.5/forecast/daily?q="+location+"&mode=json&cnt=7&appid=e92f550a676a12835520519a5a2aef4b"
}).success(function(data){
successcb(data);
}).error(function(){
});
}
});
overview.html (view):
<h4>Daily</h4>
<ul>
<li ng-repeat="w in currentForecast.list">{{ convertToDate(w.dt) }} {{ convertTemp(w.temp.day) }}°</li>
</ul>
Submit form:
<form ng-submit="submit()">
<button type="submit" class="btn btn-primary btn-sm">Get Forecast</button>
<input type="text" ng-model="txtCity">
</form>
Change your service function to:
this.getForecast = = function(location) {
return $http({
method : "GET",
url : "http://api.openweathermap.org/data/2.5/forecast/daily?q="+location+"&mode=json&cnt=7&appid=e92f550a676a12835520519a5a2aef4b"
}).then(
function(response) {
return response.data;
}
)
};
And in your controller, use it like this in the submit function:
weatherService.getForecast(weatherService.city).then(
function(data) {
weatherService.currentForecast = data;
console.log(data);
}
);
This allows you to handle the success function of the $http promise directly from the controller. Currently you're passing the function to the service, and it doesn't know what the weatherServiceparameter is, because it outside of the service scope (It's avalable in the controller).
Now, in your overviewController controller, you can watch for changes inside the service like this:
$scope.$watch(function() { return weatherService.currentForecast; },
function(newVal) {
if(!newVal) return;
$scope.currentForecast = newVal;
}, true);
I have a resolve object in UI-router working correctly; however my current setup makes two separate API calls to the same source. I don't want to cache the data because it is frequently changing, but I would like both functions to pull from one data api call. Here is my current setup.
Basically one function gets all results and one does some looping through the results to create an array.
resolve: {
recipeResource: 'RecipeResource',
getRecipeData: function($stateParams, recipeResource){
var recipeId = $stateParams.id;
var getData = function(){
var data = recipeResource.get({recipeId: recipeId}).$promise;
return data;
}
return {
recipeId: recipeId,
getData: getData
}
},
recipe: function($stateParams, recipeResource, getRecipeData){
return getRecipeData.getData();
},
subCatArray: function($stateParams, recipeResource, getRecipeData){
var data = getRecipeData.getData().then(function(value){
var ingredients = value.data.ingredients;
var log = [];
angular.forEach(ingredients, function(value, key) {
if(value){
if(value.subCategory){
log.push(value.subCategory);
};
};
}, log);
return $.unique(log.concat(['']));
});
return data;
}
}
You could use nested resolves. First get all entries and then use that resolved data in your subCatArray resolve to do additional filtering.
Then you'll only have one api call.
Please have a look at the demo below or in this jsfiddle.
angular.module('demoApp', ['ui.router'])
.factory('dummyData', function($q, $timeout) {
return {
getAll: function() {
var deferred = $q.defer(),
dummyData = ['test1', 'test2', 'test3', 'test4'];
$timeout(function() {
deferred.resolve(dummyData);
}, 1000);
return deferred.promise
}
}
})
.config(function($urlRouterProvider, $stateProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('main', {
url: '/',
template: '{{all}} {{filtered}}',
resolve: {
'allEntries': function(dummyData) {
return dummyData.getAll().then(function(data) {
return data;
});
},
'filteredEntries': function(allEntries, $filter) {
return $filter('limitTo')(allEntries, 2);
}
},
controller: function($scope, allEntries, filteredEntries) {
$scope.all = allEntries;
$scope.filtered = filteredEntries;
}
})
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.15/angular-ui-router.js"></script>
<div ng-app="demoApp">
<div ui-view=""</div>
</div>
I am unable to find easy steps on how to bind a returning json object to a angular template [The template is also remote]
e.g. data in success callback is JSON and I want to bind it to a template before display
app.controller('jobs', ['$scope', 'wpHttp', function ($scope, wpHttp) {
$scope.buildMatches = function (job_id) {
$scope.matchesHtml = 'Loading matches please wait ...';
wpHttp("get_employer_job_matches").success(function (data) {
// bind json to template similar to mustache.js?
$scope.matchesHtml = data;
});
}; etc.......
I am using bootstrap.ui and I am want to load the rendered template as follows
<tab select="buildMatches(job.ID)" heading="Matches">
<div data-ng-bind-html="matchesHtml"></div>
</tab>
# azium
Yes the JSON data is returned as expected everything is working fine, I think the issue can be solved using a custom directive, I want to bind data to templateUrl: views_url + "matches.php" only when tab select="buildMatches(job.ID)" heading="Matches" is selected.
(function (angular, wp_localize) {
var app = angular.module("app", ['ngRoute', 'ngSanitize', 'ui.bootstrap']);
var theme_url = wp_localize.theme_url;
var views_url = theme_url + "/angular-apps/employer-profile/views/";
var language = lang;
app.service('wpHttp', ['$http', function ($http) {
var request = {
method: 'POST',
url: ajaxurl,
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
};
return function (action, data) {
var queryStr = "action=" + action;
if (data) {
queryStr += "&data=" + JSON.stringy(data);
}
request.data = queryStr;
return $http(request);
};
}]);
app.controller('jobs', ['$scope', 'wpHttp', function ($scope, wpHttp) {
$scope.lang = language;
$scope.img = theme_url + "/images/front_page/candidate-mummy.png";
$scope.questionnaireHtml = $scope.matchesHtml = '';
$scope.buildMatches = function (job_id) {
$scope.matchesHtml = 'Loading matches please wait ...';
wpHttp("get_employer_job_matches").success(function (data) {
$scope.matchesHtml = data;
});
};
$scope.buildQuestionnaire = function () {
$scope.matchesHtml = 'Loading please wait';
};
wpHttp("get_employer_jobs").success(function (data) {
$scope.jobs = data;
});
}]);
app.config(['$routeProvider',
function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: views_url + "create-jobs.php",
controller: 'jobs'
}).when('/jobs', {
templateUrl: views_url + "list-jobs.php",
controller: 'jobs'
});
}
]);
app.directive('ngMatchesHtml', function () {
return {
restrict: 'A',
templateUrl: views_url + "matches.php"
}
});
})(angular, wp_localize_employer_profile);
I already write a code to display a loader div, when any resources is in pending, no matter it's getting via $http.get or routing \ ng-view.
I wan't only information if i'm going bad...
flowHandler service:
app.service('flowHandler', function(){
var count = 0;
this.init = function() { count++ };
this.end = function() { count-- };
this.take = function() { return count };
});
The MainCTRL append into <body ng-controller="MainCTRL">
app.controller("MainCTRL", function($scope, flowHandler){
var _this = this;
$scope.pageTitle = "MainCTRL";
$scope.menu = [];
$scope.loader = flowHandler.take();
$scope.$on("$routeChangeStart", function (event, next, current) {
flowHandler.init();
});
$scope.$on("$routeChangeSuccess", function (event, next, current) {
flowHandler.end();
});
updateLoader = function () {
$scope.$apply(function(){
$scope.loader = flowHandler.take();
});
};
setInterval(updateLoader, 100);
});
And some test controller when getting a data via $http.get:
app.controller("BodyCTRL", function($scope, $routeParams, $http, flowHandler){
var _this = this;
$scope.test = "git";
flowHandler.init();
$http.get('api/menu.php').then(function(data) {
flowHandler.end();
$scope.$parent.menu = data.data;
},function(error){flowHandler.end();});
});
now, I already inject flowHandler service to any controller, and init or end a flow.
It's good idea or its so freak bad ?
Any advice ? How you do it ?
You could easily implement something neat using e.g. any of Bootstrap's progressbars.
Let's say all your services returns promises.
// userService ($q)
app.factory('userService', function ($q) {
var user = {};
user.getUser = function () {
return $q.when("meh");
};
return user;
});
// roleService ($resource)
// not really a promise but you can access it using $promise, close-enough :)
app.factory('roleService', function ($resource) {
return $resource('role.json', {}, {
query: { method: 'GET' }
});
});
// ipService ($http)
app.factory('ipService', function ($http) {
return {
get: function () {
return $http.get('http://www.telize.com/jsonip');
}
};
});
Then you could apply $scope variable (let's say "loading") in your controller, that is changed when all your chained promises are resolved.
app.controller('MainCtrl', function ($scope, userService, roleService, ipService) {
_.extend($scope, {
loading: false,
data: { user: null, role: null, ip: null}
});
// Initiliaze scope data
function initialize() {
// signal we are retrieving data
$scope.loading = true;
// get user
userService.getUser().then(function (data) {
$scope.data.user = data;
// then apply role
}).then(roleService.query().$promise.then(function (data) {
$scope.data.role = data.role;
// and get user's ip
}).then(ipService.get).then(function (response) {
$scope.data.ip = response.data.ip;
// signal load complete
}).finally(function () {
$scope.loading = false;
}));
}
initialize();
$scope.refresh = function () {
initialize();
};
});
Then your template could look like.
<body ng-controller="MainCtrl">
<h3>Loading indicator example, using promises</h3>
<div ng-show="loading" class="progress">
<div class="progress-bar progress-bar-striped active" style="width: 100%">
Loading, please wait...
</div>
</div>
<div ng-show="!loading">
<div>User: {{ data.user }}, {{ data.role }}</div>
<div>IP: {{ data.ip }}</div>
<br>
<button class="button" ng-click="refresh();">Refresh</button>
</div>
This gives you two "states", one for loading...
...and other for all-complete.
Of course this is not a "real world example" but maybe something to consider. You could also refactor this "loading bar" into it's own directive, which you could then use easily in templates, e.g.
//Usage: <loading-indicator is-loading="{{ loading }}"></loading-indicator>
/* loading indicator */
app.directive('loadingIndicator', function () {
return {
restrict: 'E',
scope: {
isLoading: '#'
},
link: function (scope) {
scope.$watch('isLoading', function (val) {
scope.isLoading = val;
});
},
template: '<div ng-show="isLoading" class="progress">' +
' <div class="progress-bar progress-bar-striped active" style="width: 100%">' +
' Loading, please wait...' +
' </div>' +
'</div>'
};
});
Related plunker here http://plnkr.co/edit/yMswXU
I suggest you to take a look at $http's pendingRequest propertie
https://docs.angularjs.org/api/ng/service/$http
As the name says, its an array of requests still pending. So you can iterate this array watching for an specific URL and return true if it is still pending.
Then you could have a div showing a loading bar with a ng-show attribute that watches this function
I would also encapsulate this requests in a Factory or Service so my code would look like this:
//Service that handles requests
angular.module('myApp')
.factory('MyService', ['$http', function($http){
var Service = {};
Service.requestingSomeURL = function(){
for (var i = http.pendingRequests.length - 1; i >= 0; i--) {
if($http.pendingRequests[i].url === ('/someURL')) return true;
}
return false;
}
return Service;
}]);
//Controller
angular.module('myApp')
.controller('MainCtrl', ['$scope', 'MyService', function($scope, MyService){
$scope.pendingRequests = function(){
return MyService.requestingSomeURL();
}
}]);
And the HTML would be like
<div ng-show="pendingRequests()">
<div ng-include="'views/includes/loading.html'"></div>
</div>
I'd check out this project:
http://chieffancypants.github.io/angular-loading-bar/
It auto injects itself to watch $http calls and will display whenever they are happening. If you don't want to use it, you can at least look at its code to see how it works.
Its very simple and very useful :)
I used a base controller approach and it seems most simple from what i saw so far. Create a base controller:
angular.module('app')
.controller('BaseGenericCtrl', function ($http, $scope) {
$scope.$watch(function () {
return $http.pendingRequests.length;
}, function () {
var requestLength = $http.pendingRequests.length;
if (requestLength > 0)
$scope.loading = true;
else
$scope.loading = false;
});
});
Inject it into a controller
angular.extend(vm, $controller('BaseGenericCtrl', { $scope: $scope }));
I am actually also using error handling and adding authorization header using intercepting $httpProvider similar to this, and in this case you can use loading on rootScope
I used a simpler approach:
var controllers = angular.module('Controllers', []);
controllers.controller('ProjectListCtrl', [ '$scope', 'Project',
function($scope, Project) {
$scope.projects_loading = true;
$scope.projects = Project.query(function() {
$scope.projects_loading = false;
});
}]);
Where Project is a resource:
var Services = angular.module('Services', [ 'ngResource' ]);
Services.factory('Project', [ '$resource', function($resource) {
return $resource('../service/projects/:projectId.json', {}, {
query : {
method : 'GET',
params : {
projectId : '#id'
},
isArray : true
}
});
} ]);
And on the page I just included:
<a ng-show="projects_loading">Loading...</a>
<a ng-show="!projects_loading" ng-repeat="project in projects">
{{project.name}}
</a>
I guess, this way, there is no need to override the $promise of the resource