I'm currently working on this AngularJS Tutorial: Learn to Build Modern Web Apps with Angular and Rails https://thinkster.io/angular-rails/ which I think is a great resource showing how to build Rails Web Apps with AngularJS.
So far, I've completed over two thirds of the tutorial successfully, but now I'm facing some issues with factory services. When I hit the post button to create new post, I get the following error:
Error message
angular.js?body=1:11608 TypeError: undefined is not a function
at Scope.$scope.addPost (http://0.0.0.0:3000/assets/home/mainCtrl.js?body=1:23:24)
It points to the code below in mainCtrl.js file:
posts.create({
title: $scope.title,
link: $scope.link,
});
the entire mainCtrl.js file:
angular.module('flapperNews')
.controller('MainCtrl', ['$scope', 'posts', function ($scope, posts) {
$scope.test = 'Hello world!';
$scope.posts = posts.posts;
$scope.posts.push({
title: $scope.title,
link: $scope.link,
upvotes: 0,
comments: [
{ author: 'Joe', body: 'Cool post!', upvotes: 0 },
{ author: 'Bob', body: 'Great idea but everything is wrong!', upvotes: 0 }
]
});
$scope.addPost = function () {
if (!$scope.title || $scope.title === '') { return; }
posts.create({
title: $scope.title,
link: $scope.link,
});
$scope.title = '';
$scope.link = '';
};
$scope.incrementUpvotes = function(post) {
posts.upvote(post);
};
}]);
In the above controller, if the addPost function is replaced with the one that was used in the tutorial before the factory create method was introduced, then it works fine.
Working code:
$scope.addPost = function(){
if(!$scope.title || $scope.title === '') { return; }
$scope.posts.push({
title: $scope.title,
link: $scope.link,
upvotes: 0
});
$scope.title = '';
$scope.link = '';
};
So somehow the factory's posts.create method is causing the issue (although posts.posts is accesible).
Below is post.js file which o.create method is causing the current issue
angular.module('flapperNews').factory('posts', ['$http',
function($http){
var o = {
posts: []
};
return o;
o.getAll = function() {
return $http.get('/posts.json').success(function(data){
angular.copy(data, o.posts);
});
};
o.create = function(post) {
console.log("o.create");
return $http.post('/posts.json', post).success(function(data){
o.posts.push(data);
});
};
o.upvote = function(post) {
return $http.put('/posts/' + post.id + '/upvote.json')
.success(function(data){
post.upvotes += 1;
});
};
resolve: {
postPromise: ['posts', function(posts){
return posts.getAll();
}]
}
}
]);
app.js file
angular.module('flapperNews', ['ui.router', 'templates'])
.config([
'$stateProvider',
'$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'home/_home.html',
controller: 'MainCtrl'
})
.state('posts', {
url: '/posts/{id}',
templateUrl: 'posts/_posts.html',
controller: 'PostsCtrl'
})
$urlRouterProvider.otherwise('home')
}]);
If someone knows what the underlying issue here, then please give advice. Many Thanks :-)
My rails version 4.0.2 and I'm using Linux Ubuntu 12.04
Some thoughts, if I can't get the factory methods working, then I might put those methods directly in controllers to resolve the issue, hopefully :-)
You returned the object too soon, the other functions didn't register (since they are function expressions, and not function declarations, they didn't get hoisted). Move your return o; to the end and it will work:
app.factory('posts', ['$http',
function($http){
var o = {
posts: []
};
o.getAll = function() {
return $http.get('/posts.json').success(function(data){
angular.copy(data, o.posts);
});
};
o.create = function(post) {
alert('In create!');
console.log("o.create");
return $http.post('/posts.json', post).success(function(data){
o.posts.push(data);
});
};
o.upvote = function(post) {
return $http.put('/posts/' + post.id + '/upvote.json')
.success(function(data){
post.upvotes += 1;
});
};
resolve: {
postPromise: ['posts', function(posts){
return posts.getAll();
}]
}
return o;
}
])
See this working Fiddle.
Your resolve statement should go in the home state in your app.js. As the tutorial says, "By using the resolve property in this way, we are ensuring that anytime our home state is entered, we will automatically query all posts from our backend before the state actually finishes loading."
App.js:
angular.module('flapperNews', ['ui.router', 'templates'])
.config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'home/_home.html',
controller: 'MainCtrl',
resolve: {
postPromise: ['posts', function(posts){
return posts.getAll();
}]
}
})
.state('posts', {
url: '/posts/{id}',
templateUrl: 'posts/_posts.html',
controller: 'PostsCtrl'
})
$urlRouterProvider.otherwise('home');
}]);
Related
I'm trying to learn angular with this tutorial https://thinkster.io/tutorials/mean-stack/wiring-everything-up and I've hit a snag where opening the application runs into an infinite loop of calling the /posts url. I've checked my routes with postman and they are working as intended so I'm not sure why this isn't resolving correctly. Below is the 'posts' service, 'mainCtrl' controller, and the config. Could someone take a look and tell me if they see my error or if I need provide more of the code to help. Thank you.
Service
app.factory('posts', ['$http', function($http){
var o = {
posts:[]
}
o.getAll = function() {
return $http.get('/posts').success(function(data){
angular.copy(data, o.posts);
});
};
return o;
}])
Controller
app.controller('MainCtrl', ['$scope', 'posts', function($scope, posts){
$scope.test = 'Hello world!';
$scope.posts = posts.posts;
$scope.addPost = function(){
if(!$scope.title || $scope.title === '') { return; }
$scope.posts.push({
title: $scope.title,
link: $scope.link,
upvotes: 0,
comments: [
{author: 'Joe', body: 'Cool post!', upvotes: 0},
{author: 'Bob', body: 'Great idea but everything is wrong!', upvotes: 0}
]
});
$scope.title='';
$scope.link='';
}
$scope.incrementUpvotes = function(post) {
post.upvotes += 1;
};
}]);
Config
app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider){
$stateProvider
.state('home', {
url:'/home',
templateUrl:'/home.html',
controller:'MainCtrl',
resolve: {
postPromise: ['posts', function(posts){
return posts.getAll();
}]
}
})
.state('posts', {
url:'/posts/{id}',
templateUrl:'/posts.html',
controller:'PostsCtrl'
})
$urlRouterProvider.otherwise('home');
}])
I'm going to close this question with a solution that partially has worked for me. I say partially because I'm no longer experience an infinite loop but if someone comes across this question doing the tutorial they will eventually run into a new problem where clicking submit does create a new post but the data is not there. Upon a refresh you will get the result you are looking for, but I have not solved this new issue.
The solution to the infinite loop is to use this in the service function
return $http.get('/posts').then(function(data){
angular.copy(data.data, o.posts);
});
The success syntax the the tutorial calls for does not work.
Right now i am making an AngularJS+UI router install application. But i have a problem, the problem is, that i want to disable access to the views, associated with the install application. I want to do it in resolve in the state config.
But the problem is i need to get the data from a RESTful API, whether the application is installed or not. I tried making the function, but it loaded the state before the $http.get request was finished.
Here was my code for the resolve function:
(function() {
var app = angular.module('states', []);
app.run(['$rootScope', '$http', function($rootScope, $http) {
$rootScope.$on('$stateChangeStart', function() {
$http.get('/api/v1/getSetupStatus').success(function(res) {
$rootScope.setupdb = res.db_setup;
$rootScope.setupuser = res.user_setup;
});
});
}]);
app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/404");
$stateProvider.state('db-install', {
url: "/install/db",
templateUrl: 'admin/js/partials/db-install.html',
controller: 'DBController',
resolve: {
data: function($q, $state, $timeout, $rootScope) {
var setupStatus = $rootScope.setupdb;
var deferred = $q.defer();
$timeout(function() {
if (setupStatus === true) {
$state.go('setup-done');
deferred.reject();
} else {
deferred.resolve();
}
});
return deferred.promise;
}
}
})
.state('user-registration', {
url: "/install/user-registration",
templateUrl: "admin/js/partials/user-registration.html",
controller: "RegisterController"
})
.state('setup-done', {
url: "/install/setup-done",
templateUrl: "admin/js/partials/setup-done.html"
})
.state('404', {
url: "/404",
templateUrl: "admin/js/partials/404.html"
});
}]);
})();
EDIT:
Here is what my ajax call returns:
Try this way:
$stateProvider.state('db-install', {
url: "/install/db",
templateUrl: 'admin/js/partials/db-install.html',
controller: 'DBController',
resolve: {
setupStatus: function($q, $state, $http) {
return $http.get('/api/v1/getSetupStatus').then(function(res) {
if (res.db_setup === true) {
$state.go('setup-done');
return $q.reject();
}
return res;
});
}
}
})
Then inject setupStatus in controller:
.state('setup-done', {
url: "/install/setup-done",
templateUrl: "admin/js/partials/setup-done.html",
controller: ['$scope', 'setupStatus', function ($scope, setupStatus) {
$scope.setupdb = setupStatus.db_setup;
$scope.setupuser = setupStatus.user_setup;
}]
})
I can't seem to get my Wordpress JSON to work. I'm brand new to the AngularJS and Ionic worlds, so I've been reading and watching tutorials.
This is the relevant part of my app.js file:
(function() {
var app = angular.module('mybodyapp', ['ionic', 'angularMoment','LocalStorageModule']);
app.config(function($stateProvider, $urlRouterProvider) {
$stateProvider.state('index', {
url : '/',
templateUrl : 'index.html',
controller : 'MainController'
});
$stateProvider.state('list', {
url: '/list',
templateUrl: 'templates/list.html'
});
$stateProvider.state('edit', {
url: '/edit/:noteId',
templateUrl: 'templates/edit.html',
controller: 'EditCtrl'
});
$stateProvider.state('add', {
url: '/add',
templateUrl: 'templates/edit.html',
controller: 'AddCtrl'
});
$stateProvider.state('notes', {
url: '/notes',
templateUrl: 'templates/notes.html'
});
$stateProvider.state('posts', {
url: '/posts',
templateUrl: 'templates/posts.html',
controller: 'PostsCtrl'
});
$urlRouterProvider.otherwise("/");
});
// ...
I am able to grab data from Reddit through a controller, but not data from Wordpress. I found a good demo template but cannot figure out how to 'rewrite' the beginning of the JS controller. I removed the top of the original angular.module('starter.controllers', []) and put angular.module('mybodyapp') followed by:
.controller('AppCtrl', function($scope, $timeout, $rootScope) {
$rootScope.url = 'http://scottbolinger.com/wp-json/wp/v2/';
})
.controller('PostsCtrl', function( $scope, $http, DataLoader, $timeout, $ionicSlideBoxDelegate, $rootScope ) {
console.log('PostsCtrl');
$scope.loadPosts = function() {
DataLoader.get( $rootScope.url + 'posts' ).then(function(response) {
$scope.posts = response.data;
console.log( response.data );
}, function(response) {
console.log('error', response);
});
}
// Load posts on page load
$scope.loadPosts();
paged = 2;
$scope.moreItems = true;
// Load more (infinite scroll)
$scope.loadMore = function() {
if( !$scope.moreItems ) {
return;
}
var pg = paged++;
$timeout(function() {
DataLoader.get( $rootScope.url + 'posts' + '?page=' + pg ).then(function(response) {
angular.forEach( response.data, function( value, key ) {
$scope.posts.push(value);
});
if( response.data.length <= 0 ) {
$scope.moreItems = false;
}
}, function(response) {
$scope.moreItems = false;
console.log('error');
});
$scope.$broadcast('scroll.infiniteScrollComplete');
$scope.$broadcast('scroll.resize');
}, 1000);
}
$scope.moreDataExists = function() {
return $scope.moreItems;
}
// Pull to refresh
$scope.doRefresh = function() {
console.log('Refreshing!');
$timeout( function() {
$scope.loadPosts();
//Stop the ion-refresher from spinning
$scope.$broadcast('scroll.refreshComplete');
}, 1000);
};
})
.controller('PostCtrl', function($scope, $stateParams, DataLoader, $ionicLoading, $rootScope, $sce ) {
$ionicLoading.show({
noBackdrop: true
});
var singlePostApi = $rootScope.url + 'posts/' + $stateParams.postId;
DataLoader.get( singlePostApi ).then(function(response) {
$scope.post = response.data;
// Don't strip post html
$scope.content = $sce.trustAsHtml(response.data.content.rendered);
$ionicLoading.hide();
}, function(response) {
console.log('error', response);
});
});
The name in your ng-app directive must match your module name. The name in your ng-controller directive must match your controller name.
For more information on the ng-app directive see the AngularJS ng-app API Reference.
For the ng-controller directive, AngularJS ng-controller API Reference
UPDATE
The demo you grabbed has three controllers. You need to make them methods of angular.module
angular.module('mybodyapp').controller('AppCtrl',...
angular.module('mybodyapp').controller('PostsCtrl', function( $sc
angular.module("mybodyapp").controller('PostCtrl', function($scope
I am working on an application and I'd like to use the controllerAs syntax to not rely only on $scope. I am using $resource to get data from the API and the problem I encounter is that in the success/error callbacks I can use only $scope, since this is not defined.
Here is some code to better explain the problem.
This is my main module where among other things I configure the router:
angular
.module('app', ['ngRoute', 'ngResource', 'LocalStorageModule', 'app.users', 'app.auth'])
.config(configure)
.controller('MainController', ['$scope', '$location', MainController]);
function configure($routeProvider, localStorageServiceProvider, $resourceProvider) {
// configure the router
$routeProvider
.when('/', {
templateUrl: 'app/homepage.html',
controller: 'MainController',
controllerAs: 'vm',
data: { authRequired: true }
})
.when('/users', {
templateUrl: 'app/users/main.html',
controller: 'UserController',
controllerAs: 'vmu',
data: { authRequired: true }
})
.otherwise({redirectTo: '/'});
}
// the MainController is not relevant here
In the user module I get some info about the users from the API. Here is a simplified example:
angular
.module('app.users', ['ngResource'])
.controller('UserController', ['UserService', UserController])
.factory('UserService', ['$resource', UserService]);
function UserController(UserService) {
this.users = UserService.users.list();
this.getUserInfo = function(userId) {
this.user = UserService.users.single({ id: userId },
function(success) {
// here I'd like to use 'this' but the following line will trigger an error
this.groupRules = UserService.users.rules({ id: success.userGroupId });
// I have to use $scope instead but it is not what I want
// $scope.groupRules = UserService.users.rules({ id: success.userGroupId });
} );
}
}
function UserService($resource) {
var userResource = {};
userResource.users = $resource('https://my.api.com/users/:action',
{},
{
list: { method: 'GET', isArray: true, params: { action: 'list' } }
single: { method: 'GET', params: { action: 'single', id: '#id' } }
rules: { method: 'GET', params: { action: 'getRules', id: '#id' } }
});
return userResource;
}
I'd like to be able to use 'this' in the callback of the $resource, but of course I'll get an error since 'this' is 'undefined' inside the callback.
Using $scope solves the problem, but I need to refactor some code and I'd like to avoid using $scope all the time.
Any workaround? Maybe I should use a different approach?
Thanks in advance for your help and explanations!
You should look into how to use this in javascript and into javascript scopes and closures.
This should work better:
function UserController(UserService) {
var _this = this;
this.users = UserService.users.list();
this.getUserInfo = function(userId) {
_this.user = UserService.users.single({ id: userId },
function(success) {
// here I'd like to use 'this' but the following line will trigger an error
_this.groupRules = UserService.users.rules({ id: success.userGroupId });
} );
}
}
This works, but Json is not how I want to retrieve the data:
var states = [
{ name: 'main', url: '/', templateUrl: '/views/main.html', controller: 'MainCtrl' },
{ name: 'login', url: '', templateUrl: '/views/login-form.html', controller: 'LoginCtrl' },
{ name: 'logout', url: '', templateUrl: '', controller: 'LogoutCtrl' },
{
name: 'sales',
url: '',
templateUrl: '/views/sales-data.html',
controller: 'SalesDataCtrl',
resolve: {
user: 'User',
authenticationRequired:
['user', function(user) { user.isAuthenticated(); }]
}
}
];
angular.forEach(states, function (state) {
$stateProvider.state(state.name, state);
});
This does not work in retrieving the data:
app.config(['$locationProvider', '$resourceProvider', '$stateProvider', function ($locationProvider, $resourceProvider, $stateProvider) {
$locationProvider.html5Mode(true);
not working ---> var states = $resource('http://localhost:9669/api/breeze/states').query();
angular.forEach(states, function(state) {
$stateProvider.state(state.name, state);
});
}]);
My question is two-fold:
How does one retrieve remote data inside app.config so as to populate $StateProvider.
I know even when that is acheived, I will have a problem with the return value of
"['user', function(user) { user.isAuthenticated(); }]"
as it will come back as a string and angular will not recognize it as a function.
How can I overcome that issue?
[Side note: the api call does work, the issue is not the api controller.]
Thank you.
[Solution]
Documentation is piece-meal on this matter, but after sewing various posts together I found the answer.
First one must understand that data cannot be populated in the $stateProvider as it is part of app.config because app.config only initializes providers, the actual http service has to be performed in app.run.
Step 1: Declare 2 Globals, ie. $stateProviderRef and $urlRouterProviderRef references before the app.module and pass the actual $stateProvider & $urlRouterProvider references to them in app.config:
(function() {
var id = 'app';
var $stateProviderRef = null;
var $urlRouterProviderRef = null;
......
// Create the module and define its dependencies.
app.config(function($stateProvider, $urlRouterProvider) {
$stateProviderRef = $stateProvider;
$urlRouterProviderRef = $urlRouterProvider;
});
.......
Step 2: Create a Custom Factory Provider:
app.factory('menuItems', function($http) {
return {
all: function() {
return $http({
url: 'http://localhost:XXXX/api/menuitems',
method: 'GET'
});
}
};
});
Step 3: Call the custom provider in app.run:
app.run(['$q', '$rootScope', '$state','$urlRouter', 'menuItems',
function($q, $rootScope, $state, $urlRouter, menuItems) {
breeze.core.extendQ($rootScope, $q);
menuItems.all().success(function (data) {
angular.forEach(data, function (value, key) {
var stateName = 'state_' + value.OrderNum; //<-- custom to me as I have over 300 menu items and did not want to create a stateName field
$stateProviderRef.state(stateName, {url:value.url, templateUrl:value.templateUrl, controller:value.controller });
});
// because $stateProvider does not have a default otherwise
$urlRouterProviderRef.otherwise('/');
});
.....
}
]);