I have an angular app with a list of users. When I click on edit, I need to pass a specific user id into the url and am having difficulty in doing do. I am getting an undefined id when I click on the edit button- www.example.com.users/undefined- and an error message in the console-
Error in resource configuration. Expected response to contain an object but got an array
The are lots of SO questions about this error, but seem to be the other way around whereby the response was expected to contain an array but got an object. I set isArray to false on the show method of my service anyway but to no effect.
Can anyone see where I'm going wrong?
UserCtrl:
angular.module('myApp.controllers')
.controller('UserCtrl', ['$scope', 'UsersFactory', 'UserFactory', '$location',
function ($scope, UsersFactory, UserFactory, $location) {
//This is the function behind my edit button
$scope.editUser = function (userId) {
$location.path('/users/' + userId);
};
// callback for ng-click 'createUser':
$scope.createNewUser = function () {
$location.path('/users/new');
};
$scope.users = UsersFactory.query();
}]);
UserEditCtrl:
angular.module('myApp.controllers')
.controller('UserEditCtrl', ['$scope', '$routeParams', 'UserFactory', '$location',
function ($scope, $routeParams, UserFactory, $location) {
// callback for ng-click 'updateUser':
$scope.updateUser = function () {
UserFactory.update($scope.user);
$location.path('/users');
};
//EDIT: This shows a value of "undefined"
console.log($routeParams.userId);
$scope.user = UserFactory.show({id: $routeParams.userId});
}]);
Users Service:
var app = angular.module('myApp.services');
app.factory('UsersFactory', function ($resource) {
return $resource('https://example.com/users', {}, {
query: { method: 'GET', isArray: true },
create: { method: 'POST' }
})
});
app.factory('UserFactory', function ($resource) {
return $resource('https://example.com/users/:id', {}, {
show: { method: 'GET', isArray: false },
update: { method: 'PATCH', params: {id: '#id'} },
delete: { method: 'DELETE', params: {id: '#id'} }
})
});
You could actually use one factory for the resource
app.factory('UserFactory', function ($resource) {
return $resource('https://example.com/users/:id', null,
{
update: { method: 'PATCH' }
})
});
AngularJS $resource
In your controller, you use $scope.user = UserFactory.get({id: $routeParams.userId}); instead and .query() to return array.
You also need to make sure that the responses from the server are in the required format.
Note: This was AngularJS v1.2.5
Related
I'm using ui-router 1.0.0.X with it's transitions new standart.
My code:
Service for login, save data in storage, determine if it exist and clear
app.factory('AuthService', ['$http', '$cookies', '$rootScope',
function ($http, $cookies, $rootScope) {
var service = {};
// Authenticates throug a rest service
service.authenticate = function (email, password, callback) {
$http.post('endPoints/login.php', {
email: email,
password: password
})
.then(function (response) {
callback(response);
});
};
// Creates a cookie and set the Authorization header
service.setCredentials = function (response) {
$rootScope.globals = response;
$http.defaults.headers.common['Authorization'] = 'Bearer ' + response;
$cookies.put('globals', $rootScope.globals);
};
// Checks if it's authenticated
service.isAuthenticated = function () {
console.log("If TRUE callback not worked yet!!",$cookies.get('globals') === undefined);
return !($cookies.get('globals') === undefined);
};
// Clear credentials when logout
service.clearCredentials = function () {
$rootScope.globals = undefined;
$cookies.remove('globals');
console.log("CLEAN coockies globals",$cookies.get('globals'));
$http.defaults.headers.common.Authorization = 'Bearer ';
};
return service;
}
]);
Configuration and run. There we have transitions methods to work:
angular.module('myApp',
['ui.router',
'ngCookies'
])
.config(['$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/resumen');
$stateProvider
.state("dashboard", {
url: "/dashboard",
templateUrl: "partials/dashboard.html",
controller: "dashCtrl",
data: {
authRequired: true
}
})
.state("login", {
url: "/login",
templateUrl: "partials/login.html",
controller: "loginController"
})
}])
.run(['$rootScope', '$transitions', '$state', '$cookies', '$http', 'AuthService',
function ($rootScope, $transitions, $state, $cookies, $http, AuthService) {
// keep user logged in after page refresh
$rootScope.globals = $cookies.get('globals') || {};
$http.defaults.headers.common['Authorization'] = 'Bearer ' + $rootScope.globals;
$transitions.onStart({
to: function (state) {
return state.data != null && state.data.authRequired === true;
}
}, function () {
console.log("I'm transition.onStart and i'm alive!!!");
if (!AuthService.isAuthenticated()) {
return $state.target("autorize");
}
});
}]);
In dashboard state i use data to mark the state only accessible if is authenticated.
Then, on the .run I use the transitions to check the autheticated state.
my controller. $scope.logIn binded to ng-click directive:
$scope.logIn = function () {
AuthService.authenticate($scope.loginInfo.email, $scope.loginInfo.password, function (callback) {
console.log("CALLBACK!",callback); //-> callback from server. Always true
AuthService.setCredentials(callback);
});
}
All this works as expected, but on first ng-click i recieve those:
so, .run method with transitions runs BEFORE ng-click callback from server.
On second ng-click data was already set in previous ng-click server request so everything works FINE!
So, the question is how to avoid those terrible two ng-click calling.
documentation for $transition where i took part of code:
http://angular-ui.github.io/ui-router/1.0.0-alpha.1/interfaces/transition.ihookregistry.html
related post: angular ui-router login authentication
I am trying to send data from my http service to my controller. The service correctly gets the data but it doesn't get sent to the controller.
Now, I am aware that the query is done asynchronously which is why I am trying to use $q.defer.
I tried following the example provided by a similar question : AngularJS $http call in a Service, return resolved data, not promises , however it still doesn't work.
Here is my Service :
.service("builds", ['$http', '$q', function($http, $q) {
var deferred = $q.defer();
$http({
method:'GET',
url: '/builds',
cache : true
}).success(function(data) {
deferred.resolve(data);
}).error(function(msg){
deferred.reject(msg);
});
console.log(deferred.promise);
return deferred.promise;}]);
And here is my routeProvider
$routeProvider.
when('/builds', {
controller: ['$scope', 'buildsData', function ($scope, buildsData) {
console.log("In routeprovider:" + buildsData);
$scope.allBuilds = buildsData;
}],
template: '<build-list></build-list>',
resolve: {
buildsData: ['builds', function(builds){
return builds;
}]
}
})
And finally here is a snippet of my Controller :
var app = angular.
module('buildList').
component('buildList', {
templateUrl: 'build-list/build-list.template.html',
controller: function BuildListController($scope, $window,$location,$cookies, builds) {
console.log($scope.allBuilds);
$scope.league = $scope.allBuilds;
As #vishal says
You should create a method in service because generally a service may have many get and set methods ( I mean best practice).
create a function say getData
function getData()
{
$http({
method:'GET',
url: '/builds',
cache : true
})
}
then you should be calling this method in controller
In the controller you should inject this service and then
builds.getData().then(function(s){
//result
},function(e){
//error
}
);
you shouldntt have
controller: ['$scope', 'buildsData', function ($scope, buildsData) {
console.log("In routeprovider:" + buildsData);
$scope.allBuilds = buildsData;
}],
and a controller in an other file:
You can directly do
when('/builds', {
controller: 'BuildListController'
template: '<build-list></build-list>',
resolve: {
buildsData: ['builds', function(builds){
return builds;
}]
}
})
and then in your controller
$scope.allBuilds = buildsData;
Beside, if you want add some functions to it , your service should look,like this:
.service("builds", ['$http', '$q', function($http, $q) {
var deferred = $q.defer();
getbuilds: function(){
$http({
method:'GET',
url: '/builds',
cache : true
}).success(function(data) {
deferred.resolve(data);
}).error(function(msg){
deferred.reject(msg);
});
console.log(deferred.promise);
return deferred.promise;}]);
}
I want to call a function declared inside the link function on my directive after an ajax request on my controller. So I want to communicate the controller with the link function.
This is my controller code:
.controller('productsCtrl', ['$scope', '$location', '$http', function ($scope, $location, $http) {
this.courseSeriesId = $location.search().courseSeriesId;
//FUNCTION: get data from products to show them
this.getCourseSeriesProducts = function(){
$http({
method: 'post',
url: "xxx",
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: {
functionName:'xxx',
courseSeriesId: this.courseSeriesId}
})
.success(function(response) {
---CODE
CALL welcome() function in LINK?
---CODE
}.bind(this))
.error(function(data){
alert('ERROR: ' + data);
});
}
And here is my directive code:
.directive('nlProducts', ['$location', function($location) {
return {
restrict: 'E',
templateUrl: function(elem, attr){
var type = '';
switch($location.search().courseSeriesId){
case 'en-efw':
type = 'tableview';break;
}
return 'xxx/nl-products-'+ type+'.php';
},
//control DOM elements
link:
function welcome() {
element.text('hi!');
}
};
}]);
I know that link is called after compilation, but in that moment the ajax request hasn't been finished yet. How can I do it?
Thanks.
You can't directly.
You can either use events with $broadcast in your controller and $on in your directive.
see this answer
or you can use $watch in your directive to look for a change in a variable your controller sets
I've had a problem in my previous topic, that I couldn't consume my service.
After doing some research I could finally figure out a way to consume my service after all. Still I was wondering why my other approach with the javascript object as method container didn't work out. I have some guesses but can't find an appropriate solution.
Hopefully you guys can lead me on the right path.
controller.js (Working solution)
angular.module('TodoApp.controllers', []).
controller('listCtrl', function ($scope, $location, todoApiService) {
$scope.todos = todoApiService.query();
});
services.js (Working solution)
angular.module('TodoApp.services', []).
factory('todoApiService', function ($resource) {
return $resource('/api/todo/:id', { id: '#id' }, { update: { method: 'PUT' } });
});
controllers.js (Not working solution)
angular.module('TodoApp.controllers', []).
controller('listCtrl', function ($scope, $location, todoApiService) {
$scope.todos = todoApiService.getMyTodos.query();
});
services.js (Not working solution)
angular.module('TodoApp.services', []).
factory('todoApiService', function () {
var todoApi = {};
todoApi.getMyTodos = function ($resource) {
return $resource('/api/todo/:id', { id: '#id' }, { update: { method: 'PUT' } });
};
return todoApi;
});
You should either:
Inject $resource to your factory function, just like you did in the working version. And then you can remove the $resource as a parameter for getMyTodos.
angular.module('TodoApp.services', []).
factory('todoApiService', function ($resource) {
var todoApi = {};
todoApi.getMyTodos = function () {
return $resource('/api/todo/:id', { id: '#id' }, { update: { method: 'PUT' } });
};
return todoApi;
});
And then from the controller:
angular.module('TodoApp.controllers', []).
controller('listCtrl', function ($scope, $location, todoApiService) {
$scope.todos = todoApiService.getMyTodos().query();
});
Or, you can pass the $resource from the controller to getMyTodos (after injecting it to the controller) - so your controller would look like:
angular.module('TodoApp.controllers', []).
controller('listCtrl', function ($scope, $location, todoApiService, $resource) {
$scope.todos = todoApiService.getMyTodos($resource).query();
});
I didn't check to see that this is working, but it should :)
I have an angular application which has two views:
1) List view
2) Detail View
when you click on the thumbnail from list view you go to detail view, here is the route:
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/list', {
templateUrl: 'partials/list.html',
controller: 'ListCtrl',
}).
when('/list/:id', {
templateUrl: 'partials/detail.html',
controller: 'DetailCtrl',
}).
otherwise({
redirectTo: '/list'
});
}]);
Now there is a function loadmore in 'listCtrl' controller which is used to load
myControllers.controller('ListCtrl', ['$scope', '$location', 'Troll', '$http',
function ($scope, $location, Troll, $http) {
$scope.Trolls = Troll.query();
$scope.orderProp = 'popular';
$scope.fromData = {};
//console.log($scope.Trolls);
//this will be used to fetch the data in json which is defined in services.js
$scope.loadmore = function () {
jQuery.ajax({
url: 'trolls/trolls.php?troll_index=' + $('#main-content #item-list .sub-item').size(),
type: 'GET',
async: false,
data: {},
dataType: 'json',
success: function (response) {
if (response != null) {
$.each(response, function (index, item) {
$scope.Trolls.push({
UID: response[index].UID,
id: response[index].id,
popular: response[index].popular,
imageUrl: response[index].imageUrl,
name: response[index].name,
tags: response[index].tags,
category: response[index].category
});
});
}
},
complete: function () {},
error: function () {
console.log('Failed!');
}
});
$scope.text = 'Hello, Angular fanatic.';
$http.get('trolls/trolls.php?troll_id=' + Troll);
}
}]);
PROBLEM:
now the problem is,
After clicking on loadmore if i go to detail view and i come back on list view my newly loaded divs are gone how do i preserve them??
When you change routes the controller that is in charge of that route is initialized when the route is loaded and destroyed when the route is changed. So the reason you lose your data is the controller is reinitialized and the previous data never exists.
There are two ways to fix this.
Higher level controller that is not destroyed - could potentially live on the body - this passes its scope to the children controllers. But this is not a true modularization of concerns. For this issue... Can be very useful for other issues - Authentication, Profile, etc.
The way I would advocate is to pull this into a service for example - listService - this will fetch and cache the data and pass it back to the listController when its reloaded, thus preventing the data from being lost.
First way to solve could be this...
So if you have a higher level controller in charge of fetching the data or move it into a service which is what I would do, then the data that is loaded from loadMore function will continue to be there, but it needs to be on a higher parent scope that is not destroyed on the route change.
HTML:
<body ng-controller="ApplicationController">
<!-- Code Here -->
</body>
Controller:
myControllers.controller('ApplicationController', function($scope) {
var data = [];
$scope.loadmore = function () {
// Use Angular here!!! $http not jQuery!
// Its possible to write a complete Angular app and not ever true jQuery
// jQuery Lite the Angular implementation will be used though
jQuery.ajax({
url: 'trolls/trolls.php?troll_index=' + $('#main-content #item-list .sub-item').size(),
type: 'GET',
async: false,
data: {},
dataType: 'json',
success: function (response) {
if (response != null) {
$.each(response, function (index, item) {
data.push({
UID: response[index].UID,
id: response[index].id,
popular: response[index].popular,
imageUrl: response[index].imageUrl,
name: response[index].name,
tags: response[index].tags,
category: response[index].category
});
});
}
return data;
}
error: function () {
console.log('Failed!');
}
});
}
});
However I dont really like this approach as its a bit hacky...and jQuery is used...
Second approach using a service to fetch/cache:
So lets pull it into a service instead.
myServices.factory('listService', function($http, $q) {
var//iable declaration
service = {},
list = []
;
/////////////////////
//Private functions//
/////////////////////
function loadMore(url) {
var deferred = $q.defer();
$http({ method: 'GET', url: url }) // Need to pass in the specific URL maybe from the DOM scoped function?
.success(function(data) {
deferred.resolve(data);
})
.error(function() {
deferred.reject();
//Do error things
});
return deferred.promise;
}
////////////////////
//Public Functions//
////////////////////
service.loadMore = function(url) {
// Used for loading more data
loadMore(url).then(function(data) {
list.push(data);
return list
});
}
service.getList = function() {
// Returns the currently loaded data
return list;
}
return service;
});
Then in your controller:
myControllers.controller('ListCtrl', ['$scope', '$location', 'Troll', listService
function ($scope, $location, Troll, listService) {
$scope.Trolls = Troll.query();
$scope.orderProp = 'popular';
$scope.fromData = {};
$scope.loadmore = function(subItemSize) { //add url specific params here
var url = 'trolls/trolls.php?troll_index=' + subItemSize;
return listService.loadMore(url);
};
}]);
use ui-router instead:
https://github.com/angular-ui/ui-router, you can have multiple ui-view and also you can use relative routes, you should define child states, this way the parent state remains unchanged when route changes to a child state, check out this video:https://egghead.io/lessons/angularjs-introduction-ui-router
i created a plunker:http://plnkr.co/edit/FA3DuHgngKD2CIPG5bHW?p=preview
You can store data in a service or cache it into the Angular cache, but your divs will still be gone when you route back to that view.
If you want to preserve the state of the DOM too, there's an extension to ui-router called ui-router-extras. It has a a nice demo with state transitions and full DOM preservation when switching among tabs.