How to properly resolve multiple factories using Angularjs' UI.Router - javascript

Using ui.router this successfully directs me to the proper view and properly retrieves the data from my factory:
.state('calendar', {
url: '/calendar',
templateUrl: 'templates/calendar.html',
controller: 'calendarCtrl',
resolve: {
workoutData: ['WorkoutData', function(WorkoutData){ // USING A FACTORY (workoutDataFct.js)
return WorkoutData.get();
}]
}
})
but I now want to pull in data from another factory but I can't seem to just add a new factory like this:
resolve: {
workoutData: ['WorkoutData', function(WorkoutData){
return WorkoutData.get();
}],
exercises: ['Exercises', function(Exercises){ // USING FACTORY (exercisesFct.js)
return Exercises.get();
}]
}
My factory, exerciseFct.js, is included in my index.html just like workoutDataFct.js and I included the exercises the dependency in my controller calendarCtrl just like I added the workoutData dependency. (am I forgetting to do something?)
I don't get and console errors and but I am not routed to the calendar view. This makes me think that the resolve: is failing. How do I fix this?

To make things clear here are the available options in your case.
Option 1:
Wait for all promises to finish with $q.all.
resolve: {
delayedData: function($q, WorkoutData, Exercises) {
var WorkoutData = WorkoutData.get();
var Exercises = Exercises.get();
WorkoutData.$promise.then(function(response) {console.log('Resource 1 data loaded!')});
Exercises.$promise.then(function(response) {console.log('Resource 2 data loaded!')});
return $q.all([WorkoutData.$promise, Exercises.$promise]);
}
}
Working example can be found here.
Option 2:
As #ExplosionPills stated use object with resolve.
resolve: {
WorkoutData: ["WorkoutData", function (Gists) {
var WorkoutData = WorkoutData.get();
WorkoutData.$promise.then(function(response) {console.log('Resource 1 data loaded!')});
}],
Exercises: ["Exercises", function (Meta) {
var Exercises = Exercises.get();
Exercises.$promise.then(function(response) {console.log('Resource 2 data loaded!')});
}]
}
Working example can be found here.
Thanks #Explosion Pills for your inputs.

Related

Value not set to global variable in JS/AngularJs

I am using gulp to run and build to run my application. I am getting file contents using $http service in my index.js file and then setting value of a variable like
window.variablex = "http://localhost:8080/appname".
here is how I am doing it (in index.js)
(function ()
{
'use strict';
angular
.module('main')
.controller('IndexController', IndexController);
function IndexController($http){
$http.get('conf/conf.json').success(function(data){
window.variable = data.urlValue;
}).error(function(error){
console.log(error);
});
}
});
And I've created a factory to call the rest APIs of my backend application like
(function(){
'use strict';
angular
.module('main')
.factory('testService',['$resource',testService]);
function agentService($resource){
var agents = $resource('../controller/',{id:'#id'},
{
getList:{
method:'GET',
url:window.variable+"/controller/index/",
isArray:false
}
});
Now, I except a rest call to made like
http://localhost:8080/appname/controller
But it always sends a call like http://undefined/appname/controller which is not correct.
I can get the new set value anywhere else, but this value is not being set in resource service objects somehow.
I am definitely missing something.
Any help would be much appreciated
As you are using Gulp, I advise you to use gulp-ng-config
For example, you have your config.json:
{
"local": {
"EnvironmentConfig": {
"api": "http://localhost/"
}
},
"production": {
"EnvironmentConfig": {
"api": "https://api.production.com/"
}
}
}
Then, the usage in gulpfile is:
gulp.task('config', function () {
gulp.src('config.json')
.pipe(gulpNgConfig('main.config', {
environment: 'production'
}))
.pipe(gulp.dest('.'))
});
You will have this output:
angular.module('myApp.config', [])
.constant('EnvironmentConfig', {"api": "https://api.production.com/"});
And then, you have to add that module in your app.js
angular.module('main', [ 'main.config' ]);
To use that variable you have to inject in your provider:
angular
.module('main')
.factory('testService', ['$resource', 'EnvironmentConfig', testService]);
function agentService($resource, EnvironmentConfig) {
var agents = $resource('../controller/', {id: '#id'},
{
getList: {
method: 'GET',
url: EnvironmentConfig + "/controller/index/",
isArray: false
}
});
}
#Kenji Mukai's answer did work but I may have to change configuration at run time and there it fails. This is how I achieved it (in case anyone having an issue setting variables before application gets boostrap)
These are the sets that I followed
Remove ng-app="appName" from your html file as this is what causing problem. Angular hits this tag and bootstraps your application before anything else. hence application is bootstratped before loading data from server-side (in my case)
Added the following in my main module
var injector = angular.injector(["ng"]);
var http = injector.get("$http");
return http.get("conf/conf.json").then(function(response){
window.appBaseUrl = response.data.gatewayUrl
}).then(function bootstrapApplication() {
angular.element(document).ready(function() {
angular.bootstrap(document, ["yourModuleName"]);
});
});
This will load/set new values everytime you refresh your page. You can change conf.json file even at runtime and refreshing the page will take care of updating the values.

AngularJS communication between components through a template (with a promise)

I'm working on an app that has actor listings. There are tags and actors with a many to many relationships on the backend.
I have 2 components and I want to pass and unresolved promise between them through a template.
This is the actor-tag-detail component:
angular.module('actorTagDetail').component('actorTagDetail', {
templateUrl: 'static/partials/actor-tag-detail/actor-tag-detail.template.html',
controller: ['$routeParams', 'ActorTag',
function ActorTagDetailController($routeParams, ActorTag) {
var self = this;
self.actorTag = ActorTag.get({actorTagId: $routeParams.actorTagId})
}
]
});
The actorTag returns a JSON for the ActorTag object from the backend that looks like this:
{
"id": 37,
"name": "Oscar Winner",
"actors": [
664,
668,
670,
673,
678,
696
] }
I also have an actor-list component that is responsible for retrieving an actor list from the backend:
angular.module('actorList').component('actorList', {
templateUrl: 'static/partials/actor-list/actor-list.template.html',
bindings: {
pk: '='
},
controller: ['Actor',
function ActorListController(Actor) {
var self = this;
alert(self.pk)
self.actors = Actor.query();
self.orderProp = 'name';
}
]
});
In the template for actor-tag-details I'm trying to pass an attribute to the actor-list component.
actor-tag-detail.template.html:
<actor-list pk="$ctrl.actorTag.actors"></actor-list>
What I'm trying to achieve is to have access to the retrieved JSON data in the actor-list component. However I'm getting an 'unresolved object error' instead of the desired result when I try to alert(self.pk) in actor-list.component.This is happening because when alert(self.pk) runs, the data is not yet retrieved from the backend.
I do not know how to remedy the situation.
Is there a way to specify in the receiving component to await execution until the promise is resolved and the data received from the backend?
Or, am I'm doing it completely wrong and there is a better way to pass this information between components?
Use a watcher to listen the object changes:
function ActorListController($scope, Actor) {
var self = this;
$scope.$watch(function() {
return self.pk;
}, function(newValue, oldValue) {
if(angular.isObject(newValue) {
//alert(self.pk)
self.actors = Actor.query();
}
}
}

Correct syntax to inject angular service into angular value definition?

The following code works:
angular.module("appname").value("RavenConfig", {
dsn: "key",
config: {
maxMessageLength: 1000,
},
});
The following code does not work:
RavenConfig = function($window) {
return {
dsn: "key",
config: {
maxMessageLength: 1000,
},
}
};
RavenConfig.$inject = ["$window"];
angular.module("appname").value("RavenConfig", RavenConfig);
I get Error: Raven has not been configured.
Can you try this:
angular.module('appname').value('RavenConfig', RavenConfig);
function RavenConfig($window) {
return {
dsn: "key",
config: {
maxMessageLength: 1000
}
}
};
RavenConfig.$inject = ['$window'];
You can't. Values are set up during the configuration/bootstrapping process, to be made available to services later. You will need to go through the provider. See this related question.
value is intended for constants. It is a wrapper around factory service with dummy factory function.
Use factory for services that have dependencies:
angular.module("appname").factory("RavenConfig", RavenConfig);|

Resolve 2 arrays before anything on the page with a promise

I have 2 arrays, sports and leagues and I want those arrays to become resolve before anything on the page, I will paste all of my code regarding those arrays I just mentioned. I need to do this due to an issue I am having with the Angular filters.
this is my html
<div ng-repeat="sport in sportsFilter = (sports | filter:query)">
<div ng-if="sport.leagues.length">
<!--first array-->
{{sport.name}}
</div>
<div ng-repeat="league in sport.leagues">
<!--second array-->
{{league.name}}
</div>
</div>
controller
.controller('SportsController', function($scope, $state, AuthFactory,
SportsFactory, Sports) {
$scope.sports = [];
$scope.sportPromise = Sports;
AuthFactory.getCustomer().then(function(customer) {
$scope.customer = customer;
SportsFactory.getSportsWithLeagues(customer).then(function(sports) {
$ionicLoading.hide();
if (sports.length) {
$scope.sportPromise = Sports;
$scope.sports = sports;
}else {
AuthFactory.logout();
}
}, function(err) {
console.log(err);
});
}
});
$scope.isSportShown = function(sport) {
return $scope.shownSport === sport;
};
});
and here de app.js so far, I thought with this I was resolving the arrays, actually the must important is the array named leagues, but still is giving me troubles
.state('app.sports', {
url:'/sports',
views:{
menuContent:{
templateUrl:'templates/sportsList.html',
controller:'SportsController',
resolve: {
Sports: function(SportsFactory, AuthFactory, $q) {
var defer = $q.defer();
AuthFactory.getCustomer().then(function(customer) {
SportsFactory.getSportsWithLeagues(customer).then(function(sports) {
var sportLeagues = _.pluck(sports, 'leagues'),
leaguesProperties = _.chain(sportLeagues).flatten().pluck('name').value();
console.log(leaguesProperties);
defer.resolve(leaguesProperties);
});
});
return defer.promise;
}
}
}
}
})
UPDATE:
the page is loading and I my filter is getting the array leagues empty, so is not searching thru to it, so I need that array to load first than the filters.
Here's how this could work at a high-level, including avoiding some mistakes you are making.
Mistakes:
You don't need to use $q.defer when the API you are using is already returning a promise. Just return that promise. What you are doing is called an deferred anti-pattern.
resolve is used when you need to do something (like authentication) before you are hitting a particular state. You are under-using the resolve by not resolving the customer, and instead doing this in the controller.
resolve property of $stateProvider can accept other resolves as parameters.
With these out of the way, here's how it could work:
.state('app.sports', {
resolve: {
customer: function(AuthFactory){
return AuthFactory.getCustomer();
},
sports: function(SportsFactory, customer){
return SportsFactory.getSportsWithLeagues(customer);
},
leagues: function(sports){
var leagueProperties;
// obtain leagueProperties from sports - whatever you do there.
return leagueProperties;
}
}
});
Then, in the controller you no longer need AuthFactory - you already have customer:
.controller('SportsController', function($scope, customer, sports, leagues){
$scope.sports = sports;
})
As per request in the comments of New Dev's answer the same only using array notation so that the code remains minifiable:
.state('app.sports', {
resolve: {
customer: ['AuthFactory', function(AuthFactory){
return AuthFactory.getCustomer();
}],
sports: ['SportsFactory', 'customer', function(SportsFactory, customer){
return SportsFactory.getSportsWithLeagues(customer);
}],
leagues: ['sports', function(sports){
var leagueProperties;
// obtain leagueProperties from sports - whatever you do there.
return leagueProperties;
}]
}
});
A little explanation to go with that, when you minify this:
function (AuthFactory) {
return AuthFactory.getCustomer();
}
You get something like this:
function (_1) {
return _1.getCustomer();
}
Now it will try to inject _1 which is not defined so the code will fail. Now when you minify this:
['AuthFactory', function(AuthFactory){
return AuthFactory.getCustomer();
}]
You'll get this:
['AuthFactory', function(_1){
return _1.getCustomer();
}]
And that will keep working because now _1 is assigned to AuthFactory because angular injects the first parameter in the function with the first string in the array.
Reference: https://docs.angularjs.org/guide/di (see: inline array notation)

How do you search and return a specific index of a JSON resource in AngularJS?

I have a json file of events setup like this:
{
2: {
sched_conf_id: "38",
title: "Coffee Break",
},
3: {
sched_conf_id: "39",
title: "registration",
},
}
I setup and angular factory like this:
.factory('eventFactory', ['$resource',
function($resource) {
return {
query: function(event_id) {
return $resource('/assets/events.json', {}, {
query: { method: 'GET', params: {id:event_id}, isArray: false }
}).query();
}
}
}
])
and lastly I have my angular controller which calls the query method from the factory with the id being the id from the url:
.controller('eventCtrl', function($scope, $routeParams, eventFactory) {
var eventId = $routeParams.event_id;
$scope.eventData = eventFactory.query(eventId);
})
The return data seems to be just the entire events.json file rather than just the specific id I want to query for in the parameters. In the params I am trying id but that is obviously not correct. Using this setup how can I return just the data from event_id: 2?
Assuming your production scheme is going to be fetching just a static file that doesn't do anything with arguments passed in, you need to extract the record you need after it's returned from the server.
Basically, you're requesting something like http://yourhost.com/assets/events.json?event_id=3 and if it's just a static file, the server can't do anything with that parameter. In practice, I would think you'd actually be requesting a response from a web service that can handle that, in which case your client-side code would probably work as-is.
In this specific case, however, I would think that you could handle this with an interceptor. Something like this:
.factory('eventFactory', ['$resource',
function($resource) {
return {
query: function(event_id) {
return $resource('/assets/events.json', {}, {
query: { method: 'GET', isArray: false,
interceptor: {response:
function(data){ var d = JSON.parse(data);
return d[event_id];
}
}
}
});
}
}
}
])
I don't have an environment set up to test this at the moment, but I think it should work. I have a couple places in my own code where I do something similar, for other reasons.

Categories

Resources