Lets say i list all users in a list, when i click a user i want to route to a new view and get the data for the selected person.
What is the preferred way? Should i move the data i already got when i listed the users or should i create a new server call?
My first thought is to pass the data, but the problem with this is that the data the gets lost if the user refreshes the page.
What is the best practice to solve this?
Small example:
(function() {
var app = angular.module('app');
var controllerId = 'app.controllers.views.userList';
app.controller(controllerId, [
'$scope', 'UserService',function ($scope, userService) {
var vm = this;
vm.users = [];
userService.getAllUsers().success(function (data) {
vm.users= data.users;
});
var gotoUser = function(user) {
// Pass the user to UserDetail view.
}
}
]);
})();
<div data-ng-repeat="user in vm.users" ng-click="vm.gotoUser(user)">
<span>{{customer.firstname}} {{customer.lastname}}</span>
</div>
i now list the user details in UserDetail view, this view is now vulnerable against a browser refresh.
Typically most people just create a new server call, but I'll assume you're worried about performance. In this case you could create a service that provides the data and caches it in local storage.
On controller load, the controller can fetch the data from the service given the route params and then load the content. This will achieve both the effect of working on page refresh, and not needing an extra network request
Here's a simple example from one of my apps, error handling left out for simplicity, so use with caution
angular.
module('alienstreamApp')
.service('api', ['$http', '$q','$window', function($http, $q, $window) {
//meta data request functions
this.trending = function() {
}
this.request = function(url,params) {
var differed = $q.defer();
var storage = $window.localStorage;
var value = JSON.parse(storage.getItem(url+params))
if(value) {
differed.resolve(value);
} else {
$http.get("http://api.alienstream.com/"+url+"/?"+params)
.success(function(result){
differed.resolve(result);
storage.setItem(url+params,JSON.stringify(result))
})
}
return differed.promise;
}
}]);
I would say that you should start off simple and do a new server call when you hit the new route. My experience is that this simplifies development and you can put your effort on optimizing performance (or user experience...) where you will need it the most.
Something like this:
angular.module('app', ['ngRoute', 'ngResource'])
.factory('Users', function ($resource) {
return $resource('/api/Users/:userid', { userid: '#id' }, {
query: { method: 'GET', params: { userid: '' }, isArray: true }
});
});
.controller("UsersController",
['$scope', 'Users',
function ($scope, Users) {
$scope.loading = true;
$scope.users = Users.query(function () {
$scope.loading = false;
});
}]);
.controller("UserController",
['$scope', '$routeParams', 'Users',
function ($scope, $routeParams, Users) {
$scope.loading = true;
$scope.user = Users.get({ userid: $routeParams.userid }, function () {
$scope.loading = false;
});
$scope.submit = function () {
$scope.user.$update(function () {
alert("Saved ok!");
});
}
}]);
.config(
['$routeProvider', '$locationProvider',
function ($routeProvider, $locationProvider) {
$routeProvider
.when('/users', {
templateUrl: '/users.html',
controller: 'UsersController'
})
.when('/users/:userid', {
templateUrl: '/user.html',
controller: 'UserController'
})
.otherwise({ redirectTo: '/users' });
}
]
);
Related
im using angularJS v 1.5.6 and want to know how to pass my form data correctly with $location.path.
Here is my code Page A:
<form>
...
<button type="submit" ng-click="submit(formData)">submit</button>
</form>
JS:
app.config(['$routeProvider', function ($routeProvider) {$routeProvider
// Home
.when("/", {
templateUrl: "A.html",
controller: "ACtrl"
})
.when("/B/", {
templateUrl: "B.html",
controller: "BCtrl"
})
//fallback url if nothing matches
.otherwise({
redirectTo: '/'
});
}]);
app.controller('ACtrl', function ( $scope, $location, $http) {
$scope.formData = {};
$scope.submit = function() {
$location.path("/B/" + $scope.formData );
};
});
//controller for B page
app.controller('BCtrl', ['$scope', '$routeParams',
function($scope,$routeParams) {
$scope.formData = $routeParams.formData;
}]);
it is a pretty simple example, but i cant figure out how to solve it :(
By clicking the submit nothing happens. If i remove the $scope from $scope.formData i get a error like: Error: formData is not defined.
The terms in formdata are available, i tested it with console.log($scope.formData) and everything is ok.
here is the link plunker: https://plnkr.co/edit/K5zwcmRRyom5HR4a5Q9o
EDIT
the only issue is now, how to handle the select object correctly in the foreach loop. Need help please
You can do it by creating a service and using setter/getter in order to transfer a variable.
For example like this: https://plnkr.co/edit/IuTXsVLU7dq3TylfnSYP?p=preview
app.service('TransferService', [function(){
var savedData,
service = {
getData: getData,
setData: setData
}
function getData(){
return savedData
}
function setData(data){
savedData = data
}
return service
}])
Don't use location.path...
You could either use a service or use localstorage (or some other browser storage mechanism [sessionStorage, indexdb].
Service Method Below
app.service("SomeService", function () {
var value = null;
this.set = function (val) {
value = val;
return this;
}
this.get = function () {
return value;
}
})
app.controller("ACtrl", function ($scope, SomeService) {
$scope.formData = {};
$scope.submit = function() {
//Assuming you've populated it with some data...
SomeService.set($scope.formData);
$location.path("/B/");
};
})
app.controller("BCtrl", function ($scope, SomeService) {
$scope.formData;
(function () {
//Check that the data is present in the SomeService service.
var dataFromACtrl = SomeService.get();
if (dataFromACtrl) {
$scope.formData = dataFromACtrl;
}
})();
})
Using localStrorage below, could be sessionStorage.
app.controller("ACtrl", function ($scope, SomeService) {
$scope.formData = {};
$scope.submit = function() {
//Assuming you've populated it with some data...
window.localStorage.setItem("form_data", JSON.stringify($scope.form_data));
$location.path("/B/");
};
})
app.controller("BCtrl", function ($scope, SomeService) {
$scope.formData;
(function () {
var dataFromACtrl = window.localStorage.getItem("form_data");
if (dataFromACtrl) {
$scope.formData = JSON.parse(dataFromACtrl);
}
})();
})
Note
Using the localStorage example you would need to do some clean-up, after doing whatever you want to do with that data in Bctrl you'd want to clear the entry in localstorage using either of the below lines of code:
window.localStorage.removeItem("form_data");
delete window.localStorage["form_data"];
I can't figure out how to destroy my cache to get a new list from my server.
When I get the first list, it's work perfect, but after inserting informations to my database and sending another get to my server, the browser only show the cached version of my list, without the new data.
I tried to use cacheFactory like this:
$cacheFactory.get('$http').removeAll();
but it doesn't worked.
Here is my angular Module, Service and Controller.
Module myApp
var app = angular.module('myApp', ['ngRoute', 'LocalStorageModule', 'angular-loading-bar', 'smart-table']);
app.config(function ($routeProvider) {
$routeProvider.when("/home", {
controller: "homeController",
templateUrl: "/web/views/home.html"
});
$routeProvider.when("/cidades", {
controller: "cidadesController",
templateUrl: "/web/views/basico/cidades/cidades.html"
});
$routeProvider.otherwise({ redirectTo: "/home" });
});
app.config(function ($httpProvider) {
$httpProvider.interceptors.push('authInterceptorService');
});
app.run(['authService', function (authService) {
authService.fillAuthData();
}]);
cidadesService
'use strict';
app.factory('cidadesService', ['$http', '$cacheFactory', function ($http, $cacheFactory) {
var serviceBase = 'http://localhost:22207/';
var serviceFactory = {};
var _getCidades = function () {
$cacheFactory.get('$http').removeAll(); //This doesn't worked
return $http.get(serviceBase + 'api/cidades/getall').then(function (results) {
return results;
});
};
serviceFactory.getCidades = _getCidades;
return serviceFactory;
}]);
cidadesController
'use strict';
app.controller('cidadesController', ['$scope', 'cidadesService', function ($scope, service) {
$scope.cidade = {
id: "",
nome:"",
};
$scope.message = "";
$scope.getCidades = function () {
service.getCidades().then(function (results) {
$scope.cidades = [];
$scope.collection = [];
$scope.cidades = results.data;
$scope.collection = [].concat($scope.cidades);
}, function (err) {
$scope.message = err.error_description;
});
};
//Initializing the list
$scope.getCidades();
}]);
I really don't see anything wrong, but in any case you can add unique param for your request to prevent caching
like
$http.get(serviceBase + 'api/cidades/getall?unique=' + new Date().getTime())
I have an angular app with three views. When it loads it runs some code to populate the $scope variables. When I change views and then go back to the controller I want the initial code to run again but it doesn't. It seems it is cached and the $scope variables are not updated based on what happened.
How can I force the controller to run the initialisation code every time the view is loaded?
My routes:
app.config(function ($routeProvider) {
$routeProvider
.when('/', {
controller: 'HomeController',
templateUrl: 'home.html'
})
.when('/teach', {
controller: 'TeachController',
templateUrl: 'teach.html'
})
.otherwise({
redirectTo: '/'
});
});
The code I want to run every time the '/' route is clicked:
getSubPools.success(function(data) {
$scope.userPools = data;
});
Controller in full:
app.controller('HomeController', ['$scope', '$filter', 'stream', 'removeDroplet', 'qrecords', 'helps', 'get_user', 'updateRecords', 'getSubPools', function($scope, $filter, stream, removeDroplet, qrecords, helps, get_user, updateRecords, getSubPools) {
get_user.success(function(data) { //get current user
$scope.user = data;
});
getSubPools.success(function(data) {
$scope.userPools = data;
});
stream.success(function(data) {
$scope.stream = data;
if ($scope.stream.length === 0) { //determine if user has stream
$scope.noStream = true;
} else {
$scope.noStream = false;
}
$scope.getNumberReady(); //determine if any droplets are ready
if ($scope.numberReady === 0){
$scope.noneReady = true;
} else {
$scope.noneReady = false;
$scope.stream = $filter('orderBy')($scope.stream, 'next_ready'); //orders droplets by next ready
}
});
$scope.showEditStream = true;
$scope.showStream = false;
$scope.rightAnswer = false;
$scope.wrongAnswer = true;
$scope.noneReady = false;
$scope.subbedDroplets = [];
$scope.focusInput = false;
}]);
You can use the $routeChangeStart and $routeChangeSuccess events to reload the data into the controller:
https://docs.angularjs.org/api/ngRoute/service/$route
Edit:
As mohan said as this will work for every route change, you can make a service to catch these events and for each route broadcast a special event.
And in the relevant controller/service listen to this event and reload data
If you want to force reload, then add an click function like follows,
Note: This will work only if you use $stateProvider
Home
and in controller ,
$scope.goToHome = function(){
$state.transitionTo('home', {}, {reload:true});
}
The issue here was that on clicking the link to '/' not all of the initialisation code was rerunning. Rather than making calls to the database to get fresh data, angular was just returning old data. The way I fixed this was to rewrite my factories. The factories that were failing were written:
app.factory('stream', ['$http', function($http) {
return $http.get('/stream/')
.success(function(data) {
return data;
})
.error(function(err) {
return err;
});
}]);
The factory that worked every time was written:
app.factory('stream', ['$http', function($http) {
return {
fetch: function () {
return $http.get('/stream/');
}
}
}]);
Now it runs every time. I am not sure why though.
I am receiving an external object from WordPress, in one view I have the post.title, and if you click in that title you can go to another view and see te entire post.content.
So far, I can not see the entire post because I am getting a couple errors, posts is undefined.
I made a Plunkr, CodePen and one JSBin for you to understand easier. If you use JSBin is better because you can use the console which is integrated there. All you have to do is click on the title of the post, and you are going to realize that can not go to the other view.
Here is the code regarding my issue, which is the same you will see in the online editors I post above
.state('tabs', {
url: "/tabs",
abstract: true,
templateUrl: "tabs.html"
})
.state('tabs.news', {
url: "/news",
views: {
'tab-news': {
templateUrl: "tab-news.html",
controller: 'NewsCtrl'
}
}
})
.state('tabs.post-detail', {
url: '/news/:postId',
views: {
'tab-news': {
templateUrl: 'tab-post-detail.html',
controller: 'PostDetailCtrl'
}
}
})
the html for the main view, news
<a ng-href="#/tabs/news/{{post.ID}}">
<h2 ng-bind-html="post.title"></h2>
<p>{{post.date | date}}</p>
</a>
and here is the view where you can not enter yet, the view where are redirected after clicking in the title on the main view
<div>
<h3>{{:: post.title}}</h3>
<p>{{:: post.content}}</p>
</div>
now the controller for the main view
.controller('NewsCtrl', function($scope, $ionicLoading, FreshlyPressed) {
$scope.posts = [];
$scope.doRefresh = function() {
$scope.posts = FreshlyPressed.getBlogs($scope);
}
});
here the service
angular.module('urbanet.app.service', [])
.service('FreshlyPressed', function($http) {
return {
getBlogs: function($scope) {
$scope.posts = [];
$http.jsonp('https://public-api.wordpress.com/rest/v1.1/freshly-pressed?callback=JSON_CALLBACK')
.success(function(result) {
$scope.posts = result.posts;
});
},
get: function(postId, $scope) {
console.log(postId);
console.log($scope.posts);
for (var i = 0; i < $scope.posts.length; i++) {
if ($scope.posts[i].id === parseInt(postId)) {
return $scope.posts[i];
}
}
return null;
}
}
})
and controller for the second view, the view of the entire post
.controller('PostDetailCtrl', function($scope, $stateParams, FreshlyPressed) {
$scope.post = FreshlyPressed.get($stateParams.postId, $scope);
});
You are going to have to make a new request for individual posts
The freshly-pressed api returns to you a siteID and postId.
You then combine those to use the posts API to get the single post.
Since freshly-pressed is a constantly changing list, this is the only way you would ever be able to bookmark posts. Tomorrow you may not get the same set of main posts, so wouldn't be able to replicate links that were used today the way you are attempting to
Service method:
getPostById: function(siteId,postId ) {
var url ='https://public-api.wordpress.com/rest/v1.1/sites/'+siteId+'/posts/'+postId+'?callback=JSON_CALLBACK'
return $http.jsonp(url)
}
controller
.controller('PostDetailCtrl', function($scope, $stateParams, FreshlyPressed) {
var postId = $stateParams.postId,
siteId = $stateParams.siteId;
FreshlyPressed.getPostById(siteId,postId).success(function(response){
$scope.post = response
})
});
Modify links accordingly
<a ng-href="#/tabs/news/{{post.site_ID}}/{{post.ID}}">
Working demo
.controller('PostDetailCtrl', function($scope, $stateParams, FreshlyPressed) {
$scope.post = FreshlyPressed.get($stateParams.postId, $scope);
});
This is a problem. You are trying to pass the entire $scope object as a parameter?
What exactly do you want to pass to the get call?
You have this:
get: function(postId, $scope) { //<- that shouldn't be $scope, make it 'post' or something
console.log(postId);
console.log($scope.posts); //Notice that this comes back undefined?
for (var i = 0; i < $scope.posts.length; i++) { //<- length throws an error because there isn't anything there.
if ($scope.posts[i].id === parseInt(postId)) {
return $scope.posts[i];
}
}
return null;
}
You shouldn't be trying to pass $scope as a param to your service then access $scope inside the factory.
Have your service store your posts data and return that data to your controllers. You can do something like this:
How to make multiple http requests?
Don't try to pass in $scope. Just send the post id to the service and return the data you need.
As #tpie and #charlietfl already mentioned, the current approach (passing $scope as parameter to the service) doesn't work. Another alternative to what tpie suggested (caching the posts data in the service) might be using resolve and promises:
Service code
.service('FreshlyPressed', function($q, $http) {
return {
getBlogs: function() {
var deferred = $q.defer();
$http.jsonp('https://public-api.wordpress.com/rest/v1.1/freshly-pressed?callback=JSON_CALLBACK')
.success(function(result) {
deferred.resolve(result.posts);
});
return deferred.promise;
},
get: function (postId, posts) {
/* snipped, this wasn't the problematic part */
}
}
})
This won't cache the posts result in your service, which might be a downside if the $http call you are making is heavy, but using promises is a nice way of setting up asynchronous requests.
Then corresponding state config
.state('tabs', {
url: "/tabs",
abstract: true,
templateUrl: "tabs.html"
})
.state('tabs.news', {
url: "/news",
views: {
'tab-news': {
templateUrl: "tab-news.html",
controller: 'NewsCtrl'
}
},
resolve: {
posts: function (FreshlyPressed) {
return FreshlyPressed.getBlogs();
}
}
})
.state('tabs.post-detail', {
url: '/news/:postId',
views: {
'tab-news': {
templateUrl: 'tab-post-detail.html',
controller: 'PostDetailCtrl'
}
},
resolve: {
posts: function (FreshlyPressed) {
return FreshlyPressed.getBlogs();
}
}
})
This will invoke the getBlogs function in your service and wait for the promise to be resolved. After that, you can inject the resolved posts variable into your controllers:
and finally controllers
.controller('NewsCtrl', function($scope, $ionicLoading, FreshlyPressed, posts) {
$scope.posts = posts;
$scope.doRefresh = function() {
FreshlyPressed.getBlogs()
.then(function (posts) {
$scope.posts = posts;
});
}
});
Here we can set up the $scope.posts variable directly using the resolved posts variable. The doRefresh function needs then to be modified so that it will invoke the service function and after the promise is resolved, set the $scope.posts data accordingly.
.controller('PostDetailCtrl', function($scope, $stateParams, FreshlyPressed, posts) {
$scope.post = FreshlyPressed.get($stateParams.postId, posts);
});
And here we give the service get function the resolved posts variable as parameter, as in this case the service isn't caching that data.
This isn't problem-free approach, for example consider following scenario: you get one set of posts data into your main controller. Then, there are new posts posted into the wordpress you are querying, before your user clicks on a title. This might cause the PostDetailCtrl to receive a new set of posts data, that doesn't any longer contain the certain post that your user clicked.
I think this is anyway a viable alternative, and at least a bit of food for thought.
I have an Angular service that looks like:
var lunchrServices = angular.module('lunchrServices', []);
lunchrServices.service('authService', function () {
var user = null;
this.login = function (userEmail) {
user = userEmail;
};
this.logout = function () {
user = null;
};
this.currentUser = function(){
return user;
}
});
I use this service on a controller on the main page of my application like so:
var lunchrControllers = angular.module('lunchrControllers', []);
lunchrControllers.controller('MainPageController', ['$scope', '$http', '$state', 'authService',
function ($scope, $http, $state, authService) {
$scope.logIn = function () {
$http.post('/api/users/authenticate', {email: $scope.email, password: $scope.password}).
success(function (data, status, headers, config) {
// lines of interest
authService.login($scope.email);
$state.go('users');
}).
error(function (data, status, headers, config) {
$scope.errorMessages = data;
$scope.password = "";
})
}
}]);
With the users state displaying the following (I'm using ui-router to plug this in a ui-view):
div(class='container')
h1 Welcome {{user}}
// other stuff
The controller for this page looks like:
lunchrControllers.controller('UserController', ['$scope', '$http', '$state', 'authService',
function ($scope, $http, $state, authService) {
$scope.user = authService.currentUser();
//other stuff
}]);
When the user taken to this page through the $state.go('users') call, {{user}} is correctly populated.
The problem, however, is that refreshing the page now results in {{user}} being empty. How can I have the data stored in the service persist through page refreshes?
You can set a cookie or use localStorage. There is a $cookies service in angular.
For a localstorage solution you will have to google around.
shove the user object into a cookie that never expires and then try to read it from there before making your request next time they reload the page.