I'm trying to get better understanding of how to code in Angular properly and am writing my own custom module to better understand how things are supposed to work.
I have an HTML markup consisting of images and a directive i.e.
<img ng-if="!post.tooBig" mydirective resize="0" ng-src="/img/#{{post.Content}}">
My directive is here:
.directive('mydirective', [
'$animate','$mediaStack',
function($animate,$mediaStack) {
return {
restrict: 'A',
compile: function(tElement, tAttrs) {
return loader;
}
};
function loader(scope, element, attrs) {
var source = attrs.ngSrc;
var tooLoadBig = parseInt(attrs.resize);
if(tooLoadBig){
var bigImage = new Image();
bigImage.src = source.replace("small.",".");
}
}
}])
The idea is this: if the image has small appended to its filename, I know it is a thumbnail. I want to load it's big version (same file without the appended small) in the background so it is ready for a lightbox.
This works fine as is, but the problem is because I'm doing all the work in the compile, when I set bigImage.src = source.replace("small.","."); it fires off right away, and if I have many small images on the page, it causes the page to slow down because of all the loading that is going on.
I want to therefore use $q to make it so that it will load one image at a time.
So move
var bigImage = new Image();
bigImage.src = source.replace("small.",".");
Into a promise. Is it best practice to do this in the directive? My understanding is it wouldn't make sense to do so and that I should use a service, but I'm not sure how to do that. I could play around with it more but I was hoping someone with more experience could instruct me as to best-practices for something like this and a code sample of a similar workflow, thank you.
Edits:
Directive:
.directive('mydirective', [
'$animate','$mediaStack',
function($animate,$mediaStack) {
return {
restrict: 'A',
compile: function(tElement, tAttrs) {
return loader;
}
};
function loader(scope, element, attrs) {
var source = attrs.ngSrc;
var tooLoadBig = parseInt(attrs.resize);
if(tooLoadBig){
/*var bigImage = new Image();
bigImage.src = source.replace("small.",".");*/
$mediaStack.load(source);
}
}
}])
My service:
.factory('$mediaStack', [
'$animate', '$timeout', '$document', '$compile', '$rootScope',
'$q',
'$injector',
function($animate , $timeout , $document , $compile , $rootScope ,
$q,
$injector) {
var OPENED_MEDIA_CLASS = 'media-open';
var theImages = [];
$mediaStack = {};
$mediaStack.load = function(source){
theImages.push(source);
};
$mediaStack.loadRequest = function(theImages) {
deferred.resolve(function(){
var bigImage = new Image();
bigImage.src = theImages[0].replace("small.",".");
});
return deferred.promise;
}
/*var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
alert('Success: ' + greeting);
}, function(reason) {
alert('Failed: ' + reason);
}, function(update) {
alert('Got notification: ' + update);
});*/
//above from docs
}
return $mediaStack;
}])
This works in that it gets the image urls into an array in the service, so I have all the images in the array, how do I use $q properly based off that array. I got started but most of the $mediaStack.loadRequest is based off the $q documentation, I'm not sure how to use it effectively in this case.
EDIT 2:
My service as is:
.factory('$mediaStack', [
'$animate', '$timeout', '$document', '$compile', '$rootScope',
'$q',
'$injector',
function($animate , $timeout , $document , $compile , $rootScope ,
$q,
$injector) {
var OPENED_MEDIA_CLASS = 'media-open';
var theImages = [];
var theLoadedImages = [];
var thePromises = [];
var loading = false;
$mediaStack = {};
$mediaStack.load = function(source){
theImages.push(source);
var mainDeferred = $q.defer();
if(loading)
{
thePromises.push(mainDeferred.promise);
console.log(thePromises);
$mediaStack.myRaceFn(thePromises).then(function() {
console.log("Fire!");
loading=true;
$mediaStack.loadRequest(theImages[0]).then(function(bigImage){
console.log(bigImage);
theImages.shift();
theLoadedImages.push(bigImage);
loading = false;
mainDeferred.resolve();
});
});
}
if(!loading)
{
loading = true;
$mediaStack.loadRequest(theImages[0]).then(function(bigImage){
console.log(bigImage);
theImages.shift();
theLoadedImages.push(bigImage);
loading = false;
mainDeferred.resolve();
});
}
}
$mediaStack.loadRequest = function(source){
var deferred = $q.defer();
var bigImage = new Image();
bigImage.src = source.replace("small.",".");
bigImage.onload = function() {
deferred.resolve(bigImage);
}
return deferred.promise;
}
//.race implementation in old angular
$mediaStack.myRaceFn = function (promises){
return $q(function(resolve, reject) {
promises.forEach(function(promise) {
console.log(promise);
promise.then(resolve, reject);
});
});
}
//.race implementation in old angular
return $mediaStack;
}])
I'm very close, I see an array of promises but I never get to the point of being able to fire them again, so I have 10 images, and I get an array of 9 promises. How do I fire them?
I expected it would happen around here:
$mediaStack.myRaceFn(thePromises).then(function() {
console.log("Fire!");
loading=true;
$mediaStack.loadRequest(theImages[0]).then(function(bigImage){
But I never get that console.log().
Instead I get console messages in $mediaStack.myRaceFn() showing me each promise (9 at the end) but they just sit there? I'm missing something.
I think I may be resolving mainDeferred too early...
I think, that your hunch is good. You need a service to handle requests for your images. Remember that browsers are limited to send multiple concurrent requests to single server/proxy (Max parallel http connections in a browser?)
In theory you could create a promise and expose it thru two way bindings or even drive loading with an attribute. Something like:
HTML:
<img mydirective="booleanValue" />
In mydirective:
$scope.$watch('mydirective', function (newVal, oldVal) {
if (newVal === true) {
//perform the load
}
});
But I think, that a service would be better. You could decide what and when to load, from that single point. It wouldn't be limited to UI component, you could load bigger images not only from directives context.
So in the service the essential code would be:
module.factory('$mediaStack', ['$q', function($q) { //first of all you don't need all of these dependencies, keep the list short
var imgDict = {}; //Image dictionary where we're going to keep promises and loaded images
var loadingNow = 0;
function loadImage(img) {
//Send request and return promise
var deferred = $q.defer();
var bigImage = new Image();
bigImage.onload = function() {
deferred.resolve(bigImage);
}
bigImage.src = source.replace("small.", ".");
return deferred.promise;
}
var $mediaStack = function(img) {
var deferred = $q.defer(); //the main deferred task
if (loadingNow > 4) { //if we're loading 5 images or more, defer the task untill one of the Images is loaded
var promises = []; //an array of promises, list of images which are loading now
Object.keys(imgDict).forEach(function(key) {
if (!(imgDict[key] instanceof Element)) { //Check if the item from dictionary is a promise or loaded image
promises.push(imgDict[key]); //if it's a promise, add it to the list
}
});
$q.race(promises).then(function() { //use the list, to create a race: when one of the promises resolves (image is loaded) we can fire our function again (we probably have less than 5 Images loading at the same time)
$mediaStack(img).then(function(data) { //call the function again
deferred.resolve(data); //as we return a promise form that function we have to resolve main deferred object
});
});
}
if (!(img in imgDict)) { //when the image is not loaded yet
loadingNow++; //increase the number of images being loaded
imgDict[img] = loadImage(img).then(function(imgDOM) { //and load the image
imgDict[img] = imgDOM;
deferred.resolve(imgDOM); //once it's loaded resolve the main deferred task
loadingNow--;
});
} else {
deferred.resolve(imgDict[img]);
}
return deferred.promise; //promise to the main deferred task
}
return $mediaStack;
}]);
The general idea behind promises and deferred tasks:
Deferred object is a deferred task, which has a Promise parameter - a entry point of a callback, which is going to be invoked once the deferred task is completed.
Check $q docs for more: angular $q
And how to write a service: https://docs.angularjs.org/guide/services
What is Promise? The general idea and native API
Hope it helps.
Marking Oskar as accepted because it basically allowed me to figure out a solution.
Here is what I actually implemented, however:
.factory('$mediaStack', [
'$animate', '$timeout', '$document', '$compile', '$rootScope',
'$q',
'$injector',
function($animate , $timeout , $document , $compile , $rootScope ,
$q,
$injector) {
var OPENED_MEDIA_CLASS = 'media-open';
var theImages = [];
var theLoadedImages = [];
var thePromises = [];
var loading = false;
$mediaStack = {};
$mediaStack.load = function(source){
if(source)
{
theImages.push(source);
}
var mainDeferred = $q.defer();
if(loading)
{
thePromises.push(mainDeferred.promise);
console.log(thePromises);
$mediaStack.myRaceFn(thePromises).then(function() {
console.log("Fire!");
loading=true;
$mediaStack.loadRequest(theImages[0]).then(function(bigImage){
console.log(bigImage);
theImages.shift();
theLoadedImages.push(bigImage);
loading = false;
//mainDeferred.resolve();
});
});
}
if(!loading)
{
loading = true;
$mediaStack.loadRequest(theImages[0]).then(function(bigImage){
console.log(bigImage);
theImages.shift();
theLoadedImages.push(bigImage);
loading = false;
if(theImages.length>0)
{
$mediaStack.load();
};
});
}
}
$mediaStack.loadRequest = function(source){
var deferred = $q.defer();
var bigImage = new Image();
bigImage.src = source.replace("small.",".");
bigImage.onload = function() {
deferred.resolve(bigImage);
}
return deferred.promise;
}
//.race implementation in old angular
$mediaStack.myRaceFn = function (promises){
return $q(function(resolve, reject) {
promises.forEach(function(promise) {
console.log(promise);
promise.then(resolve, reject);
});
});
}
//.race implementation in old angular
return $mediaStack;
}])
Related
I have created different drupal views to create multiple json endpoints for multiple http request using angular. In my angular script I have created multiple controller for each http request as shown below. But I want to do multiple http request in single controller. I have got inclination that $q and promises or factory are way forward but not completely sure. Any help will be much appreciated. Thanks
var module = angular.module('main',[]);
module.directive("masonry", function () {
var NGREPEAT_SOURCE_RE = '<!-- ngRepeat: ((.*) in ((.*?)( track by (.*))?)) -->';
return {
compile: function(element, attrs) {
// auto add animation to brick element
var animation = attrs.ngAnimate || "'masonry'";
var $brick = element.children();
$brick.attr("ng-animate", animation);
// generate item selector (exclude leaving items)
var type = $brick.prop('tagName');
var itemSelector = type+":not([class$='-leave-active'])";
return function (scope, element, attrs) {
var options = angular.extend({
itemSelector: itemSelector
}, scope.$eval(attrs.masonry));
// try to infer model from ngRepeat
if (!options.model) {
var ngRepeatMatch = element.html().match(NGREPEAT_SOURCE_RE);
if (ngRepeatMatch) {
options.model = ngRepeatMatch[4];
}
}
// initial animation
element.addClass('masonry');
// Wait inside directives to render
setTimeout(function () {
element.masonry(options);
element.on("$destroy", function () {
element.masonry('destroy')
});
if (options.model) {
scope.$apply(function() {
scope.$watchCollection(options.model, function (_new, _old) {
if(_new == _old) return;
// Wait inside directives to render
setTimeout(function () {
element.masonry("reload");
});
});
});
}
});
};
}
};
});
angular.module('main',[]).controller('blogcontroller', function ($scope,$http) {
$http.get('/blog-filter').success(function(result){
$scope.blog = ( function () {
return result.nodes;
})();
});
});
angular.module('cs',[]).controller('cscontroller', function ($scope,$http) {
$http.get('/case-study-view').success(function(results){
$scope.cs = ( function () {
return results.nodes;
})();
});
});
Create a factory with all $http services and use $q like this:
(function(){
app.factory('CountriesServices', countriesServices);
countriesServices.$inject = ['$http', '$q', 'settings'];
function countriesServices($http, $q, settings) {
var self = {};
self.getCountries= function(){
var deferred = $q.defer();
var url = settings.baseService + 'api/country';
$http.get(url)
.success(deferred.resolve)
.error(deferred.reject);
return deferred.promise;
};
self.getCountry = function(id){
var deferred = $q.defer();
var url = settings.baseService + 'api/country/' + id;
$http.get(url)
.success(deferred.resolve)
.error(deferred.reject);
return deferred.promise;
};
return self;
}
})();
Yes you should have your $http methods in a factory:
angular
.module('sharedServices')
.factory('blogRepository', blogRepository);
blogRepository.$inject = ['$http', '$q'];
function blogRepository($http, $q) {
var service = {
getBlogFilter: getBlogFilter
};
return service;
function getBlogFilter() {
var deferred = $q.defer();
$http.get('/blog-filter').success(function(result){
deferred.resolve(result);
});
return deferred.promise;
}
}
and in your controller:
blogRepository.getBlogFilter().then(function(data) {
$scope.blog = data.nodes;
});
It would be similar for the other $http call.
I have a recursive query that needs to potentially make further queries based on the results. I would ideally like to be able to construct a promise chain so that I know when all of the queries are finally complete.
I've been using the example from this question, and I have the following method:
this.pLoadEdges = function(id,deferred) {
if (!deferred) {
deferred = $q.defer();
}
$http.post('/Create/GetOutboundEdges', { id: id }).then(function(response) {
var data = response.data;
if (data.length > 0) {
for (var i = 0; i < data.length; i++) {
var subID = data[i].EndNode;
edgeArray.push(data[i]);
self.pLoadEdges(subID, deferred);
}
} else {
deferred.resolve();
return deferred.promise;
}
});
deferred.notify();
return deferred.promise;
}
Which I then start elsewhere using:
self.pLoadEdges(nodeID).then(function() {
var edgedata = edgeArray;
});
And of course I intend to do some more stuff with the edgeArray.
The problem is that the then() function is trigged whenever any individual path reaches an end, rather than when all the paths are done. One particular pathway might be quite shallow, another might be quite deep, I need to know when all of the pathways have been explored and they're all done.
How do I construct a promise array based on this recursive query, ideally so that I can use $q.all[] to know when they're all done, when the number of promises in the promise array depends on the results of the query?
I'm not 100% positive what the end result of the function should be, but it looks like it should be a flat array of edges based on the example that you provides. If that's correct, then the following should work
this.pLoadEdges = function(id) {
var edges = [];
// Return the result of an IIFE so that we can re-use the function
// in the function body for recursion
return (function load(id) {
return $http.post('/Create/GetOutboundEdges', { id: id }).then(function(response) {
var data = response.data;
if (data.length > 0) {
// Use `$q.all` here in order to wait for all of the child
// nodes to have been traversed. The mapping function will return
// a promise for each child node.
return $q.all(data.map(function(node) {
edges.push(node);
// Recurse
return load(node.EndNode);
});
}
});
}(id)).then(function() {
// Change the return value of the promise to be the aggregated collection
// of edges that were generated
return edges;
});
};
Usage:
svc.pLoadEdges(someId).then(function(edgeArray) {
// Use edgeArray here
});
You need $q.all function:
Combines multiple promises into a single promise that is resolved when all of the input promises are resolved.
Update 1
Check this demo: JSFiddle
The controller can be like following code (well, you may want to put it in a factory).
It loads a list of users first, then for each user, load the posts of this user. I use JSONPlaceholder to get the fake data.
$q.all accepts an array of promises and combine them into one promise. The message All data is loaded is only displayed after all data is loaded. Please check the console.
angular.module('Joy', [])
.controller('JoyCtrl', ['$scope', '$q', '$http', function ($scope, $q, $http) {
function load() {
return $http.get('http://jsonplaceholder.typicode.com/users')
.then(function (data) {
console.log(data.data);
var users = data.data;
var userPromises = users.map(function (user) {
return loadComment(user.id);
});
return $q.all(userPromises);
});
}
function loadComment(userId) {
var deferred = $q.defer();
$http.get('http://jsonplaceholder.typicode.com/posts?userId=' + userId).then(function (data) {
console.log(data);
deferred.resolve(data);
});
return deferred.promise;
}
load().then(function () {
console.log('All data is loaded');
});
}]);
Update 2
You need a recursive function, so, check: JSFiddle.
The code is below. I use round to jump out of the recursion because of the fake API. The key is here: $q.all(userPromises).then(function () { deferred.resolve(); });. That tells: Please resolve this defer object after all promises are resolved.
angular.module('Joy', [])
.controller('JoyCtrl', ['$scope', '$q', '$http', function ($scope, $q, $http) {
var round = 0;
function load(userId) {
return $http.get('http://jsonplaceholder.typicode.com/posts?userId=' + userId)
.then(function (data) {
var deferred = $q.defer();
console.log(data.data);
var posts = data.data;
if (round++ > 0 || !posts || posts.length === 0) {
deferred.resolve();
} else {
var userPromises = posts.map(function (post) {
return load(post.userId);
});
$q.all(userPromises).then(function () {
deferred.resolve();
});
}
return deferred.promise;
});
}
load(1).then(function () {
console.log('All data is loaded');
});
}]);
You can try building up an array of returned promises and then use the $.when.apply($, <array>) pattern. I've used it before to accomplish a similar thing to what you're describing.
More info on this SO thread.
UPDATE:
You probably also want to read the docs on the apply function, it's pretty neat.
I am working through my first Angular project (rails gem version 1.2.16.) and can't seem to figure out how to use $q correctly.
I have a directive where on click: 1. calls a directive function that calls a factory fucntion to run a $http request 2. uses the response from the directive function / $http in the template that is appended to the page.
DIRECTIVE:
d2jive.directive('getSpotifyTracks', ['spotifyFactory', '$compile', '$sce', '$q',
function (spotifyFactory, $compile, $sce, $q) {
'use strict';
var getTracks = function(artistName){
var deferred = $q.defer();
var spotifyTracks = spotifyFactory.getArtistTracks(artistName)
deferred.resolve(spotifyTracks);
return deferred.promise;
}
return {
scope: {
artistName: '#'
},
compile: function(tElement, tAttrs, artistName){
return function(scope, iElement) {
iElement.click(function(){
var tracks = getTracks(scope.artistName);
tracks.then(function(tracks){
var t = '<iframe src="https://embed.spotify.com/?uri=spotify:trackset:Playlist:"' + tracks.spotifyTracks+ 'width="300" height="300" frameborder="0" allowtransparency="true"></iframe>';
iElement.after($compile(t)(scope));
});
});
}
}
}
}]);
FACTORY:
d2jive.factory('spotifyFactory', ['$http','$q', function($http, $q){
var factory = {}
factory.getArtistTracks = function(artistName){
var tracks = {}
var spotifyUrl = "http://ws.spotify.com/search/1/track.json?q=";
var deferred = $q.defer();
var getTracks = function(artistName){
$http.get(spotifyUrl + encodeURIComponent(artistName))
.success(function (data) {
deferred.resolve(data);
});
return deferred.promise;
};
// tracks.spotifyTrakcs = getTracks(artistName);
var spotifyTracks = getTracks(artistName);
spotifyTracks.then(function(result){
var trackArray = [];
var tracks = result.tracks.slice(0,9);
for (var track in tracks){
grabbedTrack = tracks[track].href.slice(
14, tracks[track].href.length);
trackArray.push(grabbedTrack);
}
tracks.spotifyTracks = trackArray;
console.log(tracks.spotifyTracks);
});
return tracks;
}
return factory;
}]);
HTML:
<div class="eventContainer row" ng-controller="VenueResultsCtrl">
<div ng-repeat="event in events">
<h4>
{{event.displayName}}
</h4>
<p>
Buy Tickets
</p>
<div ng-repeat="artist in event.performance">
<button get-spotify-tracks artist-name="{{artist.displayName}}">Discover
{{artist.displayName}}<br> -- {{artist.billing}}</button><br><br>
<div class='spotify'></div>
</div>
</div>
</div>
THE PROBLEM: The template loads before the factory function returns the response.
MY SOLUTION: I created a function in my directive to call the factory and tried using $q to wait for the response before appending the template, but it does not seem to be working.
Any tips or leads will be much appreciated, thanks!
I think you might be able to use some thing simular to this to get it to work:
d2jive.factory('spotifyFactory', function($http){
var spotifyFactory = {
var spotifyUrl = "http://ws.spotify.com/search/1/track.json?q=";
getTracks: function(artstName){
var promise = $http.get(spotifyUrl + encodeURIComponent(artistName))
.then(function (result){
//this is where you modifiy the results
var tracks = result.tracks.slice(0,9);
for (var track in tracks){
grabbedTrack = tracks[track]
.href.slice(14,tracks[track].href.length);
trackArray.push(grabbedTrack);
}
tracks.spotifyTracks = trackArray;
console.log(tracks.spotifyTracks);
return tracks
});
return promise;
});
return spotifyFactory
});
and then in your controller/directive you would call this function like this
spotifyFactory.getTracks(someArtist).then(function(d) {
$scope.data = d;
});
A few pointers:
$http is already based on $q. It returns a promise. So you don't need to wrap $http by a promise, it already returns one. See the doc with examples.
your getArtistTracks function will return 'tracks' before the code in 'then' is executed, because that code is asynchronous. As you use async code, you cannot return the result. Instead you could:
call a callback with the track value you calculated
or, fancier nowadays, return a promise and fulfil it later (recommended, as it would give you a better understanding of how to use promises).
I am trying to wrapp my head around $q angular library. In my routeprovider i would like to get all my data from the server and store it in localStorage. But for some reason the resolve dont seem to wait for all http request to finish before routing to selectMedia. From my understanding reading the angular docs this should work but it doesn´t. Have i totally missunderstood the concept or am i my thinking right?
$routeProvider.
when('/', {
redirectTo : '/selectMedia',
resolve: {
data: function ($q, backendApi, localStorage, network, route, loginService){
var prices = function () {
var defer = $q.defer();
backendApi.prices.get(function (data) {
localStorage.setItem("videoPrice", data.VideoPrice);
localStorage.setItem("imagePrice", data.ImagePrice);
localStorage.setItem("prices", data.SliderPrices);
localStorage.setItem("priceSuffix", data.PriceSuffix);
defer.resolve();
}, defer.resolve);
return defer.promise;
};
var validFormats = function () {
var defer = $q.defer();
backendApi.validFormats.get(function (formats) {
localStorage.setItem("validFormats", formats);
defer.resolve();
}, defer.resolve);
return defer.promise;
};
var videoFormats = function () {
var defer = $q.defer();
backendApi.videoFormats.get(function (videoFormats) {
localStorage.setItem("videoFormats", videoFormats);
defer.resolve();
}, defer.resolve);
return defer.promise;
};
var categories = function () {
var defer = $q.defer();
backendApi.categories.get(function (data){
localStorage.setItem("categories", data.Categories);
defer.resolve();
},defer.resolve);
return defer.promise;
};
var renewToken = function () {
var defer = $q.defer();
loginService.renewToken(defer.resolve);
return defer.promise;
};
if(network.isOnline()){
var promises = [renewToken(), categories(), videoFormats(), validFormats(), prices()];
return $q.all(promises);
}
else if(!network.isOnline() && localStorage.length === 0){
route('/error');
}
}
}
});
}]);
I don't see the controller for the route being specified in the route definition. I guess you have set it via ngController at the view level. The resolve block is skipped when you don't have a controller specified in the definition.
4/23 UPDATE
The definition of the resolve property extracted from the official docs:
An optional map of dependencies which should be injected into the
controller. If any of these dependencies are promises, they will be
resolved and converted to a value before the controller is
instantiated and the $routeChangeSuccess event is fired.
According to the definition, the design purpose of the resolve property is to have a way to inject dependencies into the controller associated with a route.
I'm developing an application with Facebook JS and AngularJS. First of all I login and then get the Friends and show them in a list. Everything is working fine with this code:
var friendsController = app.controller(
"FriendsController",
function FriendsController($scope, friendsData, $location, $routeParams)
{
$scope.friendsData = friendsData.data;
$scope.params = $routeParams;
$scope.onClickFriend = function(friendId)
{
$location.path("friends/friend:" + friendId);
}
}
);
friendsController.loadFriends = function($q, $rootScope)
{
var deferred = $q.defer();
FB.api("/me/friends?fields=name,picture.type(square)", function(response)
{
$rootScope.$apply(deferred.resolve(response));
});
return deferred.promise;
}
Now I want to load the details of a friend in a new view, so I create a new routeProvider rule with a new controller and a new promise.
var friendDetailController = app.controller(
"FriendDetailController",
function FriendDetailController($scope, friendData, $routeParams)
{
$scope.friendData = friendData;
$scope.params = $routeParams;
console.log(friendData);
}
);
friendDetailController.loadFriend = function($q, $route, $rootScope)
{
var deferred = $q.defer();
var friendUrl = $route.current.params.friendId.split(":")[1] + "?fields=id,name,picture.type(large)";
FB.api("/" + friendUrl, function(response)
{
$rootScope.$apply(deferred.resolve(response));
});
return deferred.promise;
}
The problem is that I'm entering at loadFriend but it seems that it's not making the call to the FB.api, and it's really weird because it is working on FriendsController, which is basically the same.
In fact, it seems that is the second call the one that is not called, because if I invert the calls (first FriendDetail, afterwards, Friends), FriendDetail is working.