Displaying data with AngularJS loaded by NodeJS - javascript

I'm building my first MEAN twitter like application and currently try to display a list of posts to the gui. what I currently do is:
The angular.js part:
in my main.js:
angular.module('MyApp')
.controller('MainCtrl', ['$scope', 'Feed', function($scope, Feed) {
$scope.feeds = Feed.showFeeds();
// ...
}
in my feed.js:
angular.module('MyApp')
.factory('Feed', ['$http', '$location', '$rootScope', '$cookieStore', '$alert', '$resource',
function ($http, $location, $rootScope, $cookieStore, $alert, $resource) {
return {
// other functions like addFeed: function(f) {...},
showFeeds: function() {
return $http.get('/api/feeds');
}
The node.js part:
app.get('/api/feeds', function (req, res, next) {
var query = Feed.find();
query.limit(8);
query.exec(function (err, feeds) {
if (err) return next(err);
res.send(feeds)
// feeds is a corret JSON with all my data at this point
});
});
and my home.html:
<p>{{ feeds }}</p> <!-- for testing - this just returns {} -->
<div ng-repeat="feed in feeds">
<div>
{{feed.feedMessage}}
</div>
So my problem is: Everything loads fine, but nothing renders out on the page, I don't get any errors, just my $scope.feeds object is empty. I'm pretty new to this, so maybe it's an obvious bug, but if someone could point me in the right direction, that would be great!

Right now you are returning a promise, and you need to be accessing the data.:
Feed.showFeeds().success( function(data) {
$scope.feeds = data.feeds;
});

The '$http' service provided by angular return always an instance of '$q', another angular service which allows us to use the Promise syntax for all async commands.
When you assign the return of Feed.showFeeds() to your scope variable, you bind a promise to your view and Angular can't display it.
You should use the success method provide by $http in order to get the server data and bind them to your scope variable like bencripps said.
Note: The success method (and error) are specific methods of $http and call the $digest method of angular which triggers a refresh of the view automatically.
angular.module('MyApp')
.controller('MainCtrl', ['$scope', 'Feed', function($scope, Feed) {
$scope.feeds = [];
Feed.showFeeds().success(function(data) {
//depends of the json return
//try a console.log(data)
$scope.feeds = data.feeds
});
// ...
}

Related

Angular-DataTables initial promise method does not fire

I can't find any similar problems on SO, so I suspect my issue is due to my setup, so I hope someone is able to spot a flaw in my code
I've used DataTables successfully in the past, but not with Angular and I'm having some trouble getting the table to do the initial load with its data.
I have the following code and the code with a fromFnPromise() that never gets hit. As you'll see, I'm forced to use an existing controller, so its definition does not match that of the example, but I'm pretty sure what I have should work:
var controllerName = "gaugeDashboardController";
angular
.module('bps.gauge', ['daterangepicker', 'amChartsDirective', 'angularjs-dropdown-multiselect', 'datatables', 'datatables.bootstrap', 'datatables.buttons'])
.controller(controllerName, Controller);
Controller.$inject = ['$scope', '$location', 'hubservice', '$http', '$state', '$stateParams', '$localStorage', 'coreAppService', '$q', 'DTOptionsBuilder', 'DTColumnDefBuilder'];
function Controller($scope, $location, hubservice, $http, $state, $stateParams, $localStorage, coreAppService, $q, DTOptionsBuilder, DTDefColumnBuilder) {
var vm = this;
vm.dtOptions = DTOptionsBuilder.fromFnPromise(function () {
var defer = $q.defer();
$http.get('/master/js/custom/bps/empty.json').then(function (result) {
defer.resolve(result.data);
});
return defer.promise;
})
.withPaginationType('full_numbers')
.withButtons([
'copy',
'excel'
])
.withOption('responsive', true);
vm.dtInstance = {};
vm.dataTableColumns = [
DTDefColumnBuilder.newColumnDef('Id').withTitle('Id').notVisible(),
DTDefColumnBuilder.newColumnDef('LocationId').withTitle('LocationId').notVisible(),
DTDefColumnBuilder.newColumnDef('ClientId').withTitle('ClientId').notVisible(),
DTDefColumnBuilder.newColumnDef('RecordDate').withTitle('RecordDate'),
DTDefColumnBuilder.newColumnDef('StoreCode').withTitle('StoreCode'),
DTDefColumnBuilder.newColumnDef('LocationName').withTitle('LocationName'),
DTDefColumnBuilder.newColumnDef('SubLocality').withTitle('SubLocality'),
DTDefColumnBuilder.newColumnDef('Locality').withTitle('Locality'),
DTDefColumnBuilder.newColumnDef('Country').withTitle('Country')
];
I've also tried the following and the GET request is never fired:
DTOptionsBuilder.fromSource('/js/data/test.json')
The json file just contains a simple array of objects
HTML:
<table datatable="" dt-options="dataTableOptions" dt-columns="dataTableColumns" dt-instance="dtInstance" dt-column-defs="dtColumnDefs" class="row-border hover"></table>
The controller name is not specified in the HTML, but rather in a routes.config.js file as follows:
.state('app.gauge_dashboard', {
url: '/gauge/dashboard',
title: 'Gauge Dashboard',
controller: 'gaugeDashboardController',
controllerAs: 'gdc',
templateUrl: helper.basepath('Bps/GaugeDashboard'),
params: {
}
})
So, in my understanting of this syntax, the controller name is set programmatically to 'gaugeDashboardController' when my HTML (at url: "/guage/dashboard"). So I think the scope is correct. Initially I got the injection of the dependency wrong and I got exceptions about the validity of DTDefColumnBuilder and DTOptionsBuilder
I've made sure that my JS files are included in the correct order as specified here
I do get an exception thrown in jquery.dataTables.js "Cannot read property 'aDataSort' of undefined", but this is after the above code gets executed.
It seems to me as if I'm missing some instantiation method call, but there's nothing here that I'm leaving out.
I really want to achieve two things:
Get the table to instantiate with data
Push new data into the table at a later stage. I can't seem to do this because there is no instance of the table

Sending data to factory from controller?

my factory is:
myAppServices.factory('ProfileData',['$http', function($http){
return{
newly_joined:function(callback){
$http.get(
//myUrl will be an url from controller.
myUrl
).success(callback);
}
};
}
]);
and I have three controller which has different URL:
controller1:
AppControllers.controller('ProfileListCtrl',['$scope','$state', '$rootScope', 'ProfileData', '$timeout', function($scope, $state, $rootScope, ProfileData, $timeout ) {
ProfileData.newly_joined(function(response) {
var myUrl= "www.abc...."
//something goes there
});
}]);
controller2:
AppControllers.controller('ProfileListCtrl1',['$scope','$state', '$rootScope', 'ProfileData', '$timeout', function($scope, $state, $rootScope, ProfileData, $timeout ) {
ProfileData.newly_joined(function(response) {
var myUrl= "www.abc...."
//something goes there
});
}]);
and controller 3 is:
AppControllers.controller('ProfileListCtrl2',['$scope','$state', '$rootScope', 'ProfileData', '$timeout', function($scope, $state, $rootScope, ProfileData, $timeout ) {
ProfileData.newly_joined(function(response) {
var myUrl= "www.abc...."
//something goes there
});
}]);
I want different data in different controller because of different URL and I am showing all three details on single web page.
So if there were any method to send 'myUrl' in factory that I can use that for pulling data.
Note: please don't suggest me for using $resource or $routeparams because $resource was not successfull in pulling data from json and I don't want to use big variable Url for my page.
Thanks in advance
All you need to do is add an additional parameter to the newly_joined function:
newly_joined:function(callback, myUrl){
Also, you should be using .then instead of .success
Your factory should be returning promises instead of using callbacks.
myAppServices.factory('ProfileData',['$http', function($http){
return function(myUrl) {
return $http.get(myUrl);
};
}]);
The controller
AppControllers.controller('ProfileListCtrl',['$scope', 'ProfileData', function($scope,ProfileData) {
var myUrl= "www.abc....";
var httpPromise = ProfileData(myUrl);
httpPromise.then(function onFulfilled(response) {
$scope.data = response.data;
}).catch(function onRejected(response) {
console.log("ERROR ", response.status);
});
}]);
The DEMO on JSFiddle
The advantage of using promises is that they retain error information.
Also notice that myUrl is sent to the factory as an argument.
For more information on the advantages of using promises, see Why are Callbacks from Promise Then Methods an Anti-Pattern?

Angularjs ng-controller with resolve

I've ran into problem with ng-controller and 'resolve' functionality:
I have a controller that requires some dependency to be resolved before running, it works fine when I define it via ng-route:
Controller code looks like this:
angular.module('myApp')
.controller('MyController', ['$scope', 'data', function ($scope, data) {
$scope.data = data;
}
]
);
Routing:
...
.when('/someUrl', {
templateUrl : 'some.html',
controller : 'MyController',
resolve : {
data: ['Service', function (Service) {
return Service.getData();
}]
}
})
...
when I go to /someUrl, everything works.
But I need to use this controller in other way(I need both ways in different places):
<div ng-controller="MyController">*some html here*</div>
And, of course, it fails, because 'data' dependency wasn't resolved. Is there any way to inject dependency into controller when I use 'ng-controller' or I should give up and load data inside controller?
In the below, for the route resolve, we're resolving the promise and wrapping the return data in an object with a property. We then duplicate this structure in the wrapper service ('dataService') that we use for the ng-controller form.
The wrapper service also resolves the promise but does so internally, and updates a property on the object we've already returned to be consumed by the controller.
In the controller, you could probably put a watcher on this property if you wanted to delay some additional behaviours until after everything was resolved and the data was available.
Alternatively, I've demonstrated using a controller that 'wraps' another controller; once the promise from Service is resolved, it then passes its own $scope on to the wrapped controller as well as the now-resolved data from Service.
Note that I've used $timeout to provide a 1000ms delay on the promise return, to try and make it a little more clear what's happening and when.
angular.module('myApp', ['ngRoute'])
.config(function($routeProvider) {
$routeProvider
.when('/', {
template: '<h1>{{title}}</h1><p>{{blurb}}</p><div ng-controller="ResolveController">Using ng-controller: <strong>{{data.data}}</strong></div>',
controller: 'HomeController'
})
.when('/byResolve', {
template: '<h1>{{title}}</h1><p>{{blurb}}</p><p>Resolved: <strong>{{data.data}}</strong></p>',
controller: "ResolveController",
resolve: {
dataService: ['Service',
function(Service) {
// Here getData() returns a promise, so we can use .then.
// I'm wrapping the result in an object with property 'data', so we're returning an object
// which can be referenced, rather than a string which would only be by value.
// This mirrors what we return from dataService (which wraps Service), making it interchangeable.
return Service.getData().then(function(result) {
return {
data: result
};
});
}
]
}
})
.when('/byWrapperController', {
template: '<h1>Wrapped: {{title}}</h1><p>{{blurb}}</p><div ng-controller="WrapperController">Resolving and passing to a wrapper controller: <strong>{{data.data ? data.data : "Loading..."}}</strong></div>',
controller: 'WrapperController'
});
})
.controller('HomeController', function($scope) {
$scope.title = "ng-controller";
$scope.blurb = "Click 'By Resolve' above to trigger the next route and resolve.";
})
.controller('ResolveController', ['$scope', 'dataService',
function($scope, dataService) {
$scope.title = "Router and resolve";
$scope.blurb = "Click 'By ng-controller' above to trigger the original route and test ng-controller and the wrapper service, 'dataService'.";
$scope.data = dataService;
}
])
.controller('WrapperController', ['$scope', '$controller', 'Service',
function($scope, $controller, Service) {
$scope.title = "Resolving..."; //this controller could of course not show anything until after the resolve, but demo purposes...
Service.getData().then(function(result) {
$controller('ResolveController', {
$scope: $scope, //passing the same scope on through
dataService: {
data: result
}
});
});
}
])
.service('Service', ['$timeout',
function($timeout) {
return {
getData: function() {
//return a test promise
return $timeout(function() {
return "Data from Service!";
}, 1000);
}
};
}
])
// our wrapper service, that will resolve the promise internally and update a property on an object we can return (by reference)
.service('dataService', function(Service) {
// creating a return object with a data property, matching the structure we return from the router resolve
var _result = {
data: null
};
Service.getData().then(function(result) {
_result.data = result;
return result;
});
return _result;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular-route.min.js"></script>
<div ng-app="myApp">
By ng-controller |
By Resolve |
By Wrapper Controller
<div ng-view />
</div>
Create a new module inside which you have the service to inject like seen below.
var module = angular.module('myservice', []);
module.service('userService', function(Service){
return Service.getData();
});
Inject newly created service module inside your app module
angular.module('myApp')
.controller('MyController', ['$scope', 'myservice', function ($scope, myservice) {
$scope.data = data;
// now you can use new dependent service anywhere here.
}
]
);
You can use the mechanism of the prototype.
.when('/someUrl', {
template : '<div ng-controller="MyController" ng-template="some.html"></div>',
controller: function (data) {
var pr = this;
pr.data = data;
},
controllerAs: 'pr',
resolve : {
data: ['Service', function (Service) {
return Service.getData();
}]
}
})
angular.module('myApp')
.controller('MyController', ['$scope', function ($scope) {
$scope.data = $scope.pr.data; //magic
}
]
);
Now wherever you want to use
'<div ng-controller="MyController"></div>'
you need to ensure that there pr.data in the Scope of the calling controller. As an example uib-modal
var modalInstance = $modal.open({
animation: true,
templateUrl: 'modal.html',
resolve: {
data: ['Service', function (Service) {
return Service.getData();
}]
},
controller: function ($scope, $modalInstance, data) {
var pr = this;
pr.data = data;
pr.ok = function () {
$modalInstance.close();
};
},
controllerAs:'pr',
size:'sm'
});
modal.html
<script type="text/ng-template" id="modal.html">
<div class="modal-body">
<div ng-include="some.html" ng-controller="MyController"></div>
</div>
<div class="modal-footer">
<button class="btn btn-primary pull-right" type="button" ng-click="pr.ok()">{{ 'ok' | capitalize:'first'}}</button>
</div>
</script>
And now you can use $scope.data = $scope.pr.data; in MyController
pr.data is my style. You can rewrite the code without PR.
the basic principle of working with ng-controller described in this video https://egghead.io/lessons/angularjs-the-dot
Presuming that Service.getData() returns a promise, MyController can inject that Service as well. The issue is that you want to delay running the controller until the promise resolves. While the router does this for you, using the controller directly means that you have to build that logic.
angular.module('myApp')
.controller('MyController', ['$scope', 'Service', function ($scope, Service) {
$scope.data = {}; // default values for data
Service.getData().then(function(data){
// data is now resolved... do stuff with it
$scope.data = data;
});
}]
);
Now this works great when using the controller directly, but in your routing example, where you want to delay rendering a page until data is resolved, you are going to end up making two calls to Service.getData(). There are a few ways to work around this issue, like having Service.getData() return the same promise for all caller, or something like this might work to avoid the second call entirely:
angular.module('myApp')
.controller('MyController', ['$scope', '$q', 'Service', function ($scope, $q, Service) {
var dataPromise,
// data might be provided from router as an optional, forth param
maybeData = arguments[3]; // have not tried this before
$scope.data = {}; //default values
// if maybeData is available, convert it to a promise, if not,
// get a promise for fetching the data
dataPromise = !!maybeData?$q.when(maybeData):Service.getData();
dataPromise.then(function(data){
// data is now resolved... do stuff with it
$scope.data = data;
});
}]
);
I was trying to solve the problem using ng-init but came across the following warnings on angularjs.org
The only appropriate use of ngInit is for aliasing special properties
of ngRepeat, as seen in the demo below. Besides this case, you should
use controllers rather than ngInit to initialize values on a scope.
So I started searching for something like ng-resolve and came across the following thread:
https://github.com/angular/angular.js/issues/2092
The above link consists of a demo fiddle that have ng-resolve like functionality. I think ng-resolve can become a feature in the future versions of angular 1.x. For now we can work around with the directive mentioned in the above link.
'data' from route resolve will not be available for injection to a controller activated other than route provider. it will be available only to the view configured in the route provider.
if you want the data to the controller activated directly other than routeprovider activation, you need to put a hack for it.
see if this link helps for it:
http://www.johnpapa.net/route-resolve-and-controller-activate-in-angularjs/
Getting data in "resolve" attribute is the functionality of route (routeProvider) , not the functionality of controller.
Key( is your case : 'data') in resolve attribute is injected as service.
That's why we are able fetch data from that service.
But to use same controller in different place , you have fetch data in controller.
Try this
Service:
(function() {
var myService = function($http) {
var getData = function() {
//return your result
};
return {
getData:getData
};
};
var myApp = angular.module("myApp");
myApp.factory("myService", myService);
}());
Controller:
(function () {
var myApp = angular.module("myApp");
myApp.controller('MyController', [
'$scope', 'myService', function($scope, myService) {
$scope.data = myService.getData();
}
]);
//Routing
.when('/someUrl', {
templateUrl : 'some.html',
controller : 'MyController',
resolve : {
data: $scope.data,
}
})
}());

AngularJS UI-Router won't recognize a defined controller on the module

I'm in the process of learning Angular, and I'm trying to set up a state with a controller using ui-router. I have a main module named app.js, and then sub-modules based on different content.
The first sub-module is diary.js. Everything is working with this controller other than the controllers. The state works in the UI, etc. When I make the controller directly in diary.js (such as controller : function () { //stuff }, it works fine). But when I try to include an already defined controller for the diary state (like it's currently written), I get the following error (I couldn't post the whole error because stackoverflow won't let me have that many links):
"Error: [ng:areq]
errors.angularjs.org/1.2.23/ng/areq?p0=DiaryCtrl&p1=not%20aNaNunction%2C%20got%20undefined
at Error (native) ...
/** diary.js */
'use strict';
(function () {
angular.module('diary', ['ui.router'])
.config(['$stateProvider', function ($stateProvider) {
// States
$stateProvider.state('diary', {
url : '/diary',
templateUrl : 'diary/html/diary.html',
controller : 'DiaryCtrl'
});
}]);
}).call();
Here is the code for the DiaryCtrl.js (the defined controller).
/** DiaryCtrl.js */
'use strict';
angular.module('diary')
.controller('DiaryCtrl', [$scope, $http, function ($scope, $http) {
$http.get('api/json/diaries.html').success(function (data) {
$scope.diaries = data;
}).error(function (status) {
console.log(status);
});
}]);
I'd appreciate any help. Let me know if you need more information.
I am fairly certain it is because your injections ($scope & $http) in the DiaryCtrl are not strings:
.controller('DiaryCtrl', [$scope, $http, function ($scope, $http)
should be:
// Notice the added quotations around the scope and http injections in the array
.controller('DiaryCtrl', ['$scope', '$http', function ($scope, $http) {

Sharing dynamic data between two controllers with service AngularJS

I have two angular services that need to share models (a list of messages and an individual message), which they get from a call to our API. The service is as follows:
angular.module('CmServices', ['ngResource'])
.factory('Messages', function ($resource, $routeParams, $rootScope) {
var data = {};
data.rest = $resource(url, {}, {
query: {method:'GET', params: params},
post: {method:'POST', params: params}
});
// Trying to set this through a call to the API (needs to get param from route)
$rootScope.$on('$routeChangeSuccess', function(event, current, previous) {
var messages = data.rest.query({m_gid: $routeParams.gid}, function () {
data.messages = messages;
});
});
return data;
});
and the controllers are:
function MessagesCtrl ($scope, $http, $location, $routeParams, Messages) {
$scope.messages = Messages.messages;
}
function MessageCtrl ($scope, $http, $location, $routeParams, Messages) {
$scope.messages = Messages.messages[0];
}
But neither of the controllers update when the data loads from the REST API (I've logged the data coming back, and it definately does).
Instead of assigning a new array to data.messages like this:
data.messages = messages
use angular.copy() instead, which will populate the same array:
angular.copy(messages, data.messages)
That way, the controllers will see the update.
The problem is that you are returning a different version of data to each controller. I would place messages in $rootScope. So
data.rest.query({m_gid: $routeParams.gid}, function () {
$rootScope.messages = messages;
});
Incidentally, what is the purpose of setting the return value of data.rest.query to var messages? That variable gets blown as soon as you leave the function.

Categories

Resources