I´m trying to code a CRUD app with Angular.JS, and I need your help to move on.
This is the scenario:
View 1 (index) gets JSONP data from a remote API and stores it.
View 2 (master) shows data filtered on a grid
View 3 (detail) shows an specific item selected on View 2
I did it already, but requesting the very same JSON object on each view, , but I think one only api call is enough.
I can´t figure out how to properly share this JSON object for all the controllers. I tried several tutorials on ngResource, $http, factories and services but still have not a clear path to go through.
How can I do this?
Any snippet or code sample you may share will be very useful to keep on tryin this thing...
Thanks in advance,
Ariel
You can implement a base controller to store common functionality that's shared between the controllers. I wrote a blog post about it recently, here's the code snippet showing how it works:
'use strict';
angular.module('Diary')
// base controller containing common functions for add/edit controllers
.controller('Diary.BaseAddEditController',
['$scope', 'DiaryService',
function ($scope, DiaryService) {
$scope.diaryEntry = {};
$scope.saveDiaryEntry = function () {
DiaryService.SaveDiaryEntry($scope.diaryEntry);
};
// add any other shared functionality here.
}])
.controller('Diary.AddDiaryController',
['$scope', '$controller',
function ($scope, $controller) {
// instantiate base controller
$controller('Diary.BaseAddEditController', { $scope: $scope });
}])
.controller('Diary.EditDiaryController',
['$scope', '$routeParams', 'DiaryService', '$controller',
function ($scope, $routeParams, DiaryService, $controller) {
// instantiate base controller
$controller('Diary.BaseAddEditController', { $scope: $scope });
DiaryService.GetDiaryEntry($routeParams.id).success(function (data) {
$scope.diaryEntry = data;
});
}]);
Using services to cache and share the data across controllers would be the way to go. Since services in angular are singleton, the same copy of data can be shared. A service such as
angular.module('myApp').factory('dataService', function($q, $resource) {
var items=[];
var service={};
service.getItems=function() {
var itemsDefer=$q.defer();
if(items.length >0)
itemsDefer.resolve(data);
else
{
$resource(url).query({},function(data) {
items=data;
itemsDefer.resolve(data)
});
}
return itemsDefer.promise;
}
return service;
});
Now in the controller you can inject the dataService and call the getItems method. This method returns a promise, which is either resolved using the cached data or by making remote request.
And the controller code would look something like
angular.module('myApp').controller('MyCtrl', function($scope,dataService) {
dataService.getItems().then(function(items) {
$scope.items=items;
}
});
Related
I have two partial pages. In the first page a rest call is made in the ManageUsersTreeViewController. I want this data to get to the second controller ManageUsersTableController. $scope.userData = data; in the getUserData() function is what i need to get to the second controller.
First Controller
app.controller('ManageUsersTreeViewController',['$rootScope', '$scope', '$http', function($rootScope, $scope, $http) {
$scope.getAllUsers = function() {
$http.get('rest/useraccess/users/').
success(function (data) {
$scope.users = data;
}).
error(function (error) {
console.log(error);
});
};
$scope.getUserData = function(user){
$http.get('rest/useraccess/' + user ).
success(function (data) {
$scope.userData = data;
console.log("user data returned:" + $scope.userData);
}).
error(function (error) {
console.log(error);
});
};
}]);
Second Controller
app.controller('ManageUsersTableController',[ '$scope', '$http', '$rootScope', function($rootScope, $scope, $http) {
$scope.maxSize = 3;
$scope.pageNumber = 1;
$scope.pageSize = 20;
$scope.pageSizesForSelect =[5,10,20,50];
}]);
How can i share the $scope.userData in the ManageUsersTreeViewController to the ManageUsersTableController? Any help is much appreciated.
you can use $emit function. this stackoverflow answer explains $emit, $broadcast, $on.
app.controller('ManageUsersTreeViewController',['$rootScope', '$scope', '$http', function($rootScope, $scope, $http) {
$rootScope.$emit('ManageUserData.Event_or_name_it_yourself', { data: $scope.userData });
}
then, in your other controllers, you can listen to this event. *make sure your view loaded both controllers.
app.controller('ManageUsersTableController',[ '$scope', '$http', '$rootScope', function($rootScope, $scope, $http) {
// destory the rootscope watcher on scope destory event, as garbage collection. otherwise your rootscope will keep listen on the event
$scope.$on('$destory', $rootScope.$on('ManageUserData.Event_or_name_it_yourself', function (event, params) {
$scope.userData = params.data;
// do what you need to do.
});
}
You're probably going to want to build an angular service to share that data across controllers. Another "down n dirty" option is to attach your userData to the $rootScope (shown below). I think you'll find your app will outgrow that solution quickly, however.
$rootScope.getUserData = function(user){
$http.get('rest/useraccess/' + user ).
success(function (data) {
$rootScope.userData = data;
console.log("user data returned:" + $rootScope.userData);
// ... now just access $rootScope.userData in other controllers
}).
error(function (error) {
console.log(error);
});
You cannot use $emit, $broadcast, $on inorder to message data between controllers. the problem with this approch is that the receiving controller may not be loaded yet and there for didnt register it's $on function.
For example if you have 2 page app with seperate controller when the first is loaded it's controller is loaded as well but the seconde page controller isn't there for there will be no controller to catch the first controller $emit or $broadcast.
The above will work only if all involved controllers were allready loaded.
As metion before you can use a service to do the work for you and store the receiving data in the $rootScope. The probllem with this approach is that when your app will grow bigger and will have more and more controller that you will want to communicate between each other , storing all this data in the rootScope is bad approach.
If you wish to use a service, i suggest that you will design a communication system as follow.
Create an array at the size of the number of your conroller in your service.
Each cell in the above array will also be an array , starting with the size of zero.
Inside this service create a const array containing all of your controllers names.
inject ths service into each one of your controllers.
Each Time a controller wants to send data to another controller, it need to iterate the service's names array until it finds the controller name it want to pass data to. one the receiving controller name is found the sending controller know it's index and can push a new message into the receiving controller cell in the array described in 1+2.
The "message" can be an object such as : {from:'controller-name', data:{}}.
when a controller start runing it check it's "mailbox" retrive the data and delete the cell if it no longer needed.
Another way you can try out is the creat a Root controller and place it on you body element in your index, such as : ...
this controller will run at the moment your app start and can use to store data as i described above
I am using a service to get data from the server using $http.get() method. I am calling the function in the service and then accessing its variable from the controller. But it is not working this way.
The code is below
'use strict';
var mission_vision_mod = angular.module('myApp.mission_vision', ['ngRoute']);
mission_vision_mod.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/mission_vision', {
templateUrl: 'partials/mission_vision/mission_vision.html',
controller: 'mission_visionCtrl'
});
}]);
mission_vision_mod.controller('mission_visionCtrl', ['$scope','getMissionData','$http', function($scope, $http, getMissionData) {
$scope.visiontext = "Here is the content of vision";
getMissionData.getmissiondata();
$scope.missions = getMissionData.missiondata;
$scope.len = $scope.missions.length;
}]);
mission_vision_mod.service('getMissionData', ['$rootScope','$http', function($rootScope, $http){
var missiondata;
function getmissiondata(){
$http.get('m_id.json').success(function(data){
missiondata = data;
});
}
}]);
When i write the$http.get() function in the controller itself, it works. I am new to angular JS.
Try writing your service like this
mission_vision_mod.service('getMissionData', ['$rootScope','$http', function($rootScope, $http){
this.getMissionData=function(){
return $http.get('m_id.json');
}
}]);
Use Service in controller like this:
mission_vision_mod.controller('mission_visionCtrl', ['$scope','getMissionData','$http', function($scope,getMissionData,$http) {
$scope.visiontext = "Here is the content of vision";
getMissionData.getMissionData().success(function(response){
$scope.missions=response;
$scope.len = $scope.missions.length;
}).error(function(errorl){
//handle error here
});
}]);
Also I suggest using a better name for service -'MissionDataService' :)
EDIT- Your sequence of injected service should match sequence of injectable names specified in the array..See my last edit
['$scope','getMissionData','$http',
function($scope, $http, getMissionData
The service names don't match with the variable names.
My advice: stop using this array notation. It clutters the code and is a frequent source of bugs. Use ng-annotate.
That said, your service is not correctly defined. It doesn't have any member function that would allow it to be called. Re-read the documentation of services. Defining services using factory() is easier than defining them using service(), BTW. And the service should return the promise returned by $http. Trying to access the value returned by an asynchronous call right after making the asynchronous call will never work. Read http://blog.ninja-squad.com/2015/05/28/angularjs-promises/
Try:
mission_vision_mod.service('getMissionData', ['$rootScope','$http', function($rootScope, $http){
var missiondata;
function getmissiondata(){
$http.get('m_id.json').success(function(data){
missiondata = data;
return missiondata;
});
}
}]);
This might be a beginner question, but I am retrieving data via http calls in AngularJS and setting them as properties in the $scope variable. However, since http calls take a while, my page tries to load AngularJS more than once in order to render different parts of the page as more the data is retrieved. Is there a way around this? (to hold off on loading the page before all data has been retrieved)
What you could do is to use ng-hide or ng-cloak, so that whatever should not be displayed until the http call fully loaded the data would remain hidden.
take a look at the resolve property in the route settings. If you set something to be resolved the router will resolve this before going to the controller.
app.config(function ($routeProvider) {
$routeProvider
.when('/',
{
templateUrl: "app.html",
controller: "AppCtrl"
resolve: {
app: function ($q, $timeout) {
YourFactory.getData({});
}
}
}
)
});
then create a Factory that will get the data you need
app.factory('YourFactory', ['$http', '$q',
function($http, $q) {
var url = '/api2.php/api';
var YourFactory = {};
var factory_data = [];
var messages = [];
YourFactory.getData = function(params) {
console.log("into GET data");
var deferred = $q.defer();
$http.get(url).success(function(response) {
angular.copy(factory_data, response.data);
deferred.resolve();
}).error(function(response) {
//couldn't resolve therefore it's rejected
deferred.reject();
});
//returns a promise that indicates that something is being resolved and will be returned for the app to continue
return deferred.promise;
};
YourFactory.data = function() {
return factory_data;
};
return YourFactory;
}
]);
then in your controller you need to input the factory and set the scope data from the Factory. Remember that Angular used the Factory to get data before the controller using the resolve property.
app.controller("AppCtrl", ['$scope','YourFactory',
function($scope, YourFactory) {
$scope.data = YourFactory.data();
});
(I haven't tested the code, I simply wrote an example based on an app that I'am doing and in which I passed through the same things as you)
Look at this links if you have any doubt.
https://egghead.io/lessons/angularjs-resolve
http://www.javierlerones.com/2013/07/preloading-data-using-deferred-promises-in-angular-js.html
I am relatively new to Angular but I am quite an experienced developer. So far I have made quite some progress in building my application to work with a CMS. I am a bit lost however on what the 'correct' approach would be to handle data in my model.
This is best described with an example:
Because I am hooking up my angular frontend with a CMS, the routing (pages) exist only in the CMS context. This means that the routing should be dynamic as well. I have managed to get the dynamic routes thing to work, but when I try to do things the right way (actually getting data from a server) I run into some issues...
app.config(function($provide, $routeProvider) {
$provide.factory("$routeProvider", function() {
return $routeProvider;
});
});
// Load the dynamic routes from the API...
app.run(function($routeProvider, $http, $scope, logger, siteRoutes) {
$routeProvider.when('/', { templateUrl: '__views/', controller: 'ContentPageController' });
$routeProvider.otherwise({redirectTo: '/'});
});
In other words, I inject a service into my app.run method (siteRoutes) and this one should connect to the API.
So my siteRoutes is a service:
cmsModule.service('siteRoutes', function siteRouteFactory(apiConnection, logger)
// SNIP
And in this service I inject my generic apiConnection service:
cmsModule.factory('apiConnection', ['$q', '$http', '$timeout', 'logger', function apiConnectionService($q, $http, $timeout, logger)
What I want is this:
I would like the siteRoutes service to load the data once and not execute the connection every time. I did this in the following way:
bla.service('example', function() {
var service = {
get: function(apiStuff) { // DO API CONNECT WITH .THEN HERE },
data: {}
}
service.get();
return service;
}
I would like one entry point towards the Api that handles all the $q stuff (my factory) I assumed I need to handle all the .then() stuff in my siteRoutes object, which is what I did.
Now, what happens in my app.run method is that I don't get the siteRoutes object with any data. So I recon I need to do a .then there as well?
But that made me question the entire design of putting all logic in a separate factory for the connection, because I basically like my app to just use the data and have my library deal with the async stuff (if you get what I am saying)...
Hope this is clear.
TL;DR -> How to make your services / factories handle async stuff without making your 'app' deal with it?
The templateUrl property can also be a function that takes the url parametes as input.
In the example below all routes will load a template with same name.
Eg. domain.com/#/blabla.html will load the view blabla.html from the server.
myApp.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/:templateName',
{
templateUrl: function (params) {
return params.templateName + ".html";
}
}
)
.otherwise({ redirectTo: '/main' });
}]);
I have a customer management interface that I'm trying to write using ui-router. I have some states set up as
"csp"
"csp.search"
"csp.customer"
"csp.customer.details"
"csp.customer.status"
How can I use ui-router's $state data to take the csp.search result and provide it to the rest of csp and/or csp.customer? As I understand it, the data would need to be on the closest common ancestor, csp, but there's no easy/clean way to do that that I can find.
I know I can make everything a child state of csp.search, so that they would inherit $state.current.data. I could also parse $state.current.name for the first name before the ., but how universal is that? Further still, I think I could write something that climbs up the ancestry ($state.$current.parent) until finding some "top-most" signal, but I don't know what that should be.
Is there a more elegant, Angular solution?
Edit: The same question might be asked, given a known state, e.g. csp, how can I add data to it from any controller?
Your csp.search results would be on a $scope. If $scopes in additional controllers need to share the model/state/data referenced by that $scope, use a singleton object instance by registering a angular service. That one factory can be injected into as many controllers as you like, and then everything can work off that one source of truth.
Heres a simple demo of a factory sharing an Object between controllers with ui-router http://plnkr.co/edit/P2UudS?p=preview (left tab only)
Factory & Controllers:
app.factory('uiFieldState', function () {
return {uiObject: {data: null}}
});
app.controller('NavbarCtrl', ['$scope', 'uiFieldState', '$stateParams', '$state',
function($scope, uiFieldState, $stateParams, $state) {
$scope.selected = uiFieldState.uiObject;
}
]);
app.controller('LeftTabACtrl', ['$scope', 'uiFieldState', '$stateParams', '$state',
function($scope, uiFieldState, $stateParams, $state) {
$scope.selected2 = uiFieldState.uiObject;
}
]);
The factory object {uiObject: {data: null}} is injected into the controller with uiFieldState & then its simply $scope.selected = uiFieldState.uiObject; for connecting the factory to the scope ng-model="selected.data" .
This is a pretty good tutorial on angularJS services: http://ng-newsletter.com/posts/beginner2expert-services.html