I am trying to create simple webapp on MEAN stack.
I want to create Offer that belongs to Exposition, but don't know how to request Exposition before call createOffer.
Here is my code
$stateProvider
.state('offer', {
url: "/exposition/:expId/offer/",
templateUrl: 'app/exposition/listOffers.tpl.html',
controller: 'OffersController'
})
.state('offercreate', {
url: "/exposition/:expId/offer/create/",
templateUrl: 'app/exposition/createOffer.tpl.html',
controller: 'OffersController'
})
.state('offerview', {
url: "/exposition/:expId/offer/:id/",
templateUrl: 'app/exposition/detailsOffer.tpl.html',
controller: 'OffersController'
});
And controller
offerApp.controller('OffersController', ['$scope', '$resource', '$state', '$location', 'OfferUpdateService', 'Upload',
function ($scope, $resource, $state, $location, OfferUpdateService, Upload) {
var OfferResource = $resource('/offer/:id');
var ExpositionResource = $resource('/exposition/:id');
$scope.offerUpdateService = new OfferUpdateService();
var loadOffers = function () {
return OfferResource.query(function (results) {
$scope.offers = results;
if ($state.params.id) {
$scope.findOffer($state.params.id);
}
if ($state.params.expId) {
ExpositionResource.findExposition($state.params.expId);
}
});
};
}
]);
Is it correct idea? I want to load Exposition before Offer and then just map exposition.id to Offer model.
Thank you.
You need to use a promise chain to do this. I'm not entirely clear as to how your resource objects work (as I use $q & $http instead, but the docs indicate they return objects with RESTful APIs). Here is an example of requesting 2 resources in sequential order:
var OfferResource = $resource('/offer/:id');
var ExpositionResource = $resource('/exposition/:id');
ExpositionResource.get({id:123}).$promise.then( function(rsp, rspHeaders){
//set your model ID how you want
model.id = rsp.id
//now hit your next API in the sequence
OfferResource.query({id:model.id}).$promise.then( function(rsp2, rspHeaders2){
//you can do this again if need be to a 3rd sequential call
})
})
Related
I have a partial in which data is coming from multiple controllers, not the situation is those functions which are called in the controller,they are hitting the server for more than fifty times, and they keep hitting as long as they dont get the response from server. I dont know how to tackle this situation please guide me.
mainControllers.controller('AddProductController', ['$scope', '$http', '$routeParams', '$cookies', '$rootScope', 'Upload', '$timeout', '$uibModal', '$log', '$document', '$window', 'variantsService', 'toaster', '$route', '$rootScope', 'Lightbox', function ($scope, $http, $routeParams, $cookies, $rootScope, Upload, $timeout, $uibModal, $log, $document, $window, variantsService, toaster, $route, $rootScope, Lightbox) {
/*Currency dynamic*/
$scope.currency = function () {
$http.get('currencies',
{headers:
{'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': $rootScope.keyword_auth_token, 'Accept-Language': $cookies.get('type')}
})
.success(function (data) {
$scope.user_curr = data[0].code;
})
.error(function (data) {
console.log(data);
});
};
/*Currency dynamic ends here*/
$scope.currency();
}]);
Is there any way, any way, so that I can limit this thing?
It definitely is a bad idea to have multiple controllers for a single partial. You should consider using angular factories for maintaining data in such cases. But to provide you a short solution, you should remove the line $scope.currency(); from your controller (because it would make an api call as soon as your controller is initialized) and consider using ng-init built-in directive. So, basically in your partial where you are using ng-controller="AddProductController", you can add ng-init="currency()" (If you want to make an api call).
I always put the calls in a service, and then you can take full control. Something like this:
app.service("currencyService", function($q, $http) {
var _currencyPromise = null,
_currencies = null;
this.getCurrencies = function() {
var deferred = $q.defer();
// Check if the currencies are resolved before
// If so, immediately return these
if (_currencies) {
deferred.resolve(_currencies);
}
// Else check if the promise is already running
// If so, use that promise
else if (_currencyPromise) {
_currencyPromise.then(function(response) {
deferred.resolve(response.data);
});
}
// Else make the http call and assign to the promise
// So that the promise can be used instead of a new http call
else {
_currencyPromise = $http.get("..");
_currencyPromise.then(function(response) {
// Assign data to the currencies, so that it can be used
// by next calls immediately
_currencies = response.data;
deferred.resolve(_currencies);
}, function(error) {
// something went wrong
_currencyPromise = null;
deferred.reject();
});
}
return deferred.promise;
}
});
Then in your controllers you can always use this service, while the http call will only be made once:
app.controller("myCtrl", ["$scope", "currencyService", function($scope, currencyService) {
currencyService.getCurrencies().then(function(currencies) {
$scope.user_curr = currencies[0].code;
});
}]);
See this jsfiddle for reference. In the console you can see that the API is only called once.
I came up with a quite simple solution. For example I have a view like this
<div ng-controller="HomeController">
<div class="active tab-pane" ng-controller="AddProductController" ng-init="subcategories_id();currency();">
<p>{{user_curr}}</p>
</div><!--ends here->
<p>first controller {{abc}}</p>
</div>
I am using the nginitwhich works fine.
Based on this tutorial, I have as my parent state a list of people. When I click on one of them, a new view is created in order to show the details for that person. In my URL I use the ID for that person, so it's rather easy to go and fetch the ID to be used in the child state. The problem is that I also want to pass information such as the name, e-mail, age, etc.
The code is as follows:
My routes:
angular
.module('appRoutes', ["ui.router"])
.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
var TalentForceState_seeProfile = {
name: 'students',
url: '/seeProfile',
templateUrl: 'public/templates/talentforce_template.html',
controller: 'People_Controller'
}
var singleStudent = {
name: 'student',
parent: 'students',
url: '/:personId',
templateUrl: 'public/templates/person_template.html',
controller: 'Person_Controller'
}
....
Then, the controller for People:
talentforceApp
.controller('People_Controller', ['$scope', '$state', '$stateParams', 'StudentService', function($scope, $state, $stateParams, StudentService) {
StudentService.query().$promise.then(function(data) {
$scope.students = data;
});
}]);
Then, the controller for Person:
talentforceApp
.controller('Person_Controller', ['$scope', '$state', '$stateParams', 'StudentService', function($scope, $state, $stateParams, StudentService) {
$scope.student_id = $stateParams.personId;
console.log($stateParams)
}]);
Also, here's the HTML for the Person:
<div ng-controller="Person_Controller">
<h3>A person!</h3>
<div>Name: {{student_name}}</div>
<div>Id: {{student_id}}</div>
<button ui-sref="students">Close</button>
</div>
Information such as student_name is what I can't seem to pass from the parent state
I tried using solutions like this, that use $state.go, or this, that use params, but it always gives me errors such as param values not valid for state or the variables are undefined.
Any solution for this problem?
You can use angular's state resolve to achieve your requirement in a better way. Although there are many choices for it.
Procedure:
When your parent state loads, you query all the people in an API call.
In that response, I am assigning the response to an instance of a service using studentService.addStudents($scope.students); where addStudents is a function in the service.
Now, when you navigate to the detail of a person, I have used resolve which fetches the stored data from the service using the function studentService.getStudents() and returns a person object to the controller.
Use that person object directly in the person controller by injecting the resolve variable
I prefer using resolve. I will tell you why.
Here is the resolve you can use:
resolve: {
student: function(studentService, $stateParams) {
return studentService.getStudents().find(function(student) {
return student.id === $stateParams.personId;
});
}
}
You will add a service studentService or you can extend your own service.
Service:
talentforceApp.service('studentService', function(){
vm = this;
vm.students = []
this.addStudents = function(students) {
vm.students = students;
}
this.getStudents = function() {
return vm.students;
}
});
I added addStudents and getStudents methods to it.
One method add students to array and the other get the data of a studenr.
People Controller revised:
talentforceApp
.controller('People_Controller', ['$scope', '$state', '$stateParams', 'StudentService', function($scope, $state, $stateParams, StudentService,studentService) {
StudentService.query().$promise.then(function(data) {
$scope.students = data;
studentService.addStudents($scope.students); // this will create a students array in service instance
});
}]);
I assigned $scope.students to the service instance.
routes revised:
var TalentForceState_seeProfile = {
name: 'students',
url: '/seeProfile',
templateUrl: 'public/templates/talentforce_template.html',
controller: 'People_Controller'
}
var singleStudent = {
name: 'student',
parent: 'students',
url: '/:personId',
templateUrl: 'public/templates/person_template.html',
controller: 'Person_Controller',
resolve: {
student: function(studentService, $stateParams) {
return studentService.getStudents.find(function(student) {
return student.id === $stateParams.personId;
});
}
}
}
Now, you can use student from resolve into your controller, as a dependency.
person controller revised:
talentforceApp
.controller('Person_Controller', ['$scope', '$state', '$stateParams', 'StudentService',student, function($scope, $state, $stateParams, StudentService,student) {
$scope.student_id = $stateParams.personId;
console.log($stateParams)
$scope.student = student // this line add student to scope
console.log($scope.student)
}]);
Check your student object in the view:
View:
<div ng-controller="Person_Controller">
<h3>A person!</h3>
{{student}}
<div>Name: {{student_name}}</div>
<div>Id: {{student_id}}</div>
<button ui-sref="students">Close</button>
</div>
here is why I prefer to use resolve and its advantages
You can't pass a large object with nested properties with $state.go. You can use event: broadcast, emit. Creating a service that hold and share data to your controllers is a better way.
I'm trying to retrieve data from Angularfire using a service, and then setting the returned value to my scope in my controller.
When I run the code below, I get undefined back for scope.sessions.
SERVICE:
app.factory('sessions', function(){
var refToSessions = new Firebase('myFireBaseURL');
var allSessions = [];
return {
getSessions: function () {
refToSessions.on("value", function (sessions) {
allSessions.push(sessions.val());
return allSessions;
});
}
};
});
CONTROLLER:
app.controller('SessionsCtrl', ['$scope', '$state', 'Auth', 'sessions', function($scope, $state, Auth, sessions){
$scope.sessions = sessions.getSessions();
$scope.submitSession = function() {
console.log($scope.sessions);
}
});
You're trying to return asynchronous data.
You are logging allSessions to the console before the data has downloaded from Firebase.
Use $firebaseArray from AngularFire.
app.constant('FirebaseUrl', '<my-firebase-url>');
app.service('rootRef', ['FirebaseUrl', Firebase);
app.factory('Sessions', function(rootRef, $firebaseArray){
var refToSessions = ref.child('sessions');
return $firebaseArray('sessions');
}
Then injection Sessions into your controller:
app.controller('SessionsCtrl', function($scope, $state, Auth, Sessions){
$scope.sessions = Sessions; // starts downloading the data
console.log($scope.sessions); // still empty
$scope.submitSession = function() {
// likely by the time you click here it will be downloaded
console.log($scope.sessions);
$scope.sessions.$add({ title: 'new session' });
};
});
The data starts downloading once it's injected into your controller. When it's downloaded, $firebaseArray knows to trigger $digest, so it appears on the page.
Since you're using ui-router, you can use resolve to make sure the data exists before injecting it into your controller:
app.config(function ($stateProvider) {
$stateProvider
.state("session", {
controller: "SessionsCtrl",
templateUrl: "views/sessions.html",
resolve: {
sessions: function(Sessions) {
// return a promise that will fulfill the data
return Sessions.$loaded();
}
}
})
});
Now you would change your controller code to this:
app.controller('SessionsCtrl', function($scope, $state, Auth, sessions){
$scope.sessions = sessions; // data is available since injected by router
console.log($scope.sessions); // logs the appropriate data
$scope.submitSession = function() {
$scope.sessions.$add({ title: 'new session' });
};
});
I have 2 controllers - ParentCtrl and ChildCtrl.
<div ng-controller="ParentCtrl">
<div ng-controller="ChildCtrl">
</div>
</div>
They using same service method to get data. Data is cached (using cache:true parameter of $http service).
angular.module('myApp')
.factory("myService", [
'$http',
function $http) {
return {
getMyData: function () {
return $http.get(url, {cache: true}).then(function (response) {
return data = response.
});
So the problem is that this 2 controllers begins to work simultaneously - this means myService#getMyData method can be called twice, i.e. 2 round trips to server. But I want only one round trip, so ChildCtrl will get result from cache.
How to solve this problem?
Thanks
Rather than doing some synchronization structure, I would add it to the routing as a prefetched data (viewModel):
$routeProvider
.when('/', {
templateUrl: 'index.html',
controller: homeController,
caseInsensitiveMatch: true,
resolve: {
sharedData: function($http, myService) {
var sharedPromise = myService.getMyData().then(function(results) {
return results;
});
}
}
});
Then in your controllers, at least in the root, you can reference the shared data just as if you had a reference to the service:
function homeController($scope, $http, sharedData) {}
Excuse the pseudo code, but it will allow you at least to be guaranteed that the reference data was fetch once.
I've been doing quite a lot of reading about angular dependency injection and factories vs services etc like in this post here - angular.service vs angular.factory
I'm struggling putting it into practise and wonder if you can give me suggestions on how you would do it.
My current code looks like this
var app = angular.module("martysCoolApp", ['firebase', 'ngRoute'])
function mainController($scope, $firebase) {
var db = new Firebase("https://**.firebaseio.com/");
$scope.messages = $firebase(db);
$scope.addItem = function(error) {
if (error.keyCode != 13) return;
$scope.messages.$add({ name: $scope.name, price: $scope.price });
$scope.name = "";
$scope.price = "";
};
}
I decided I wanted to use angular routes and split this basic function up into two different controllers that I would use for my test app. the MainController would just display everything in the firebase db and the AdminController would be able to add messages to it
var app = angular.module("martysCoolApp", ['firebase', 'ngRoute'])
.factory('fireBaseConnectionService', $firebase)
//code in here to connect to firebase and add messages
.controller('MainController', function(fireBaseConnectionService, $scope, $route, $routeParams, $location) {
$scope.$route = $route;
$scope.$location = $location;
$scope.$routeParams = $routeParams;
//code here to retrieve everything from firebase db
})
.controller('AdminController', function(fireBaseConnectionService, $scope, $routeParams) {
$scope.name = "AdminController";
$scope.params = $routeParams;
//code here to add a row to the db
})
.config(function($routeProvider, $locationProvider) {
$routeProvider.when('/', {
redirectTo: '/menu'
})
.when('/menu', {
path: '/menu',
templateUrl: 'partials/menu.html',
controller: 'MainController'
})
.when('/admin', {
templateUrl: 'partials/admin.html',
controller: 'AdminController'
})
.otherwise({
redirectTo: '/'
});
$locationProvider.html5Mode(false);
});
My problem is I don't want to have to connect to the firebase db in each controller. I would like to have a factory that handles this for me and has maybe functions within that that I can call from my controllers to view everything in db and to add something to the db
factory()
As we’ve seen, the factory() method is a quick way to create and configure a service.
The factory() function takes two arguments:
• name (string)
This argument takes the name of the service we want to register.
• getFn (function)
This function runs when Angular creates the service.
angular.module('myApp')
.factory('myService', function() {
return {
'username': 'auser'
}
});
The getFn will be invoked once for the duration of the app lifecycle, as the service is a singleton
object. As with other Angular services, when we define our service, getFn can take an array or a
function that will take other injectable objects.
The getFn function can return anything from a primitive value to a function to an object (similar to
the value() function).
angular.module('myApp')
.factory('githubService', [
'$http', function($http) {
return {
getUserEvents: function(username) {
// ...
}
}
}]);
service()
If we want to register an instance of a service using a constructor function, we can use service(),
which enables us to register a constructor function for our service object.
The service() method takes two arguments:
• name (string)
This argument takes the name of the service instance we want to register.
• constructor (function)
Here is the constructor function that we’ll call to instantiate the instance.
The service() function will instantiate the instance using the new keyword when creating the
instance.
var Person = function($http) {
this.getName = function() {
return $http({
method: 'GET',
url: '/api/user'
});
};
};
angular.service('personService', Person);
provider
These factories are all created through the $provide service, which is responsible for instantiating
these providers at run time.
angular.module('myApp')
.factory('myService', function() {
return {
'username': 'auser'
}
})
// This is equivalent to the
// above use of factory
.provider('myService', {
$get: function() {
return {
'username': 'auser'
}
}
});
Why would we ever need to use the .provider() method when we can just use the .factory()
method?
The answer lies in whether we need the ability to externally configure a service returned by the
.provider() method using the Angular .config() function. Unlike the other methods of service
creation, we can inject a special attribute into the config() method.
from ng-book
All you have to do is just move the firebase connection into the service, and inject that service wherever you want . The connection line will execute the first time your app runs, given that you front load the service when your app runs, as you seem to be doing now:
.factory('fireBaseConnectionService', function($firebase){
var db = $firebase(new Firebase("https://**.firebaseio.com/"));//creating
//the firebase connection this line executes only once when the service is loaded
return{
getMessage:function(){
return db.whatever;
}
}
})
If you load the service script dynamically, on route where you need it, it will only connect to the database when it reaches that route. The code above will create one connection to the database, as the connection line is executed only once.
Just for anyone interested with the help of the answers above and this link - Firebase _ AngularJS this is what I ended up doing
var app = angular.module("martysCoolApp", ['firebase', 'ngRoute'])
.factory('fireBaseConnectionService', ["$firebase", function($firebase) {
var db = new Firebase("https://***.firebaseio.com/");
return {
getMessages: function() {
return $firebase(db);
},
addMessage: function(message) {
var messages = $firebase(db);
messages.$add(message);
}
}
}])
.controller('MainController', ["fireBaseConnectionService", "$scope", function (fireBaseConnectionService, $scope, $route, $routeParams, $location) {
$scope.$route = $route;
$scope.$location = $location;
$scope.$routeParams = $routeParams;
$scope.messages = fireBaseConnectionService.getMessages();
}])
.controller('AdminController', ["fireBaseConnectionService", "$scope", function(fireBaseConnectionService, $scope, $routeParams) {
$scope.name = "AdminController";
$scope.params = $routeParams;
$scope.addItem = function(error) {
if (error.keyCode != 13) return;
fireBaseConnectionService.addMessage({ name: $scope.name, price: $scope.price });
$scope.name = "";
$scope.price = "";
}
}])
.config(function($routeProvider, $locationProvider) {
$routeProvider.when('/', {
redirectTo: '/menu'
})
.when('/menu', {
path: '/menu',
templateUrl: 'partials/menu.html',
controller: 'MainController'
})
.when('/admin', {
templateUrl: 'partials/admin.html',
controller: 'AdminController'
})
.otherwise({
redirectTo: '/'
});
$locationProvider.html5Mode(false);
});