I have a dynamic template include defined as follows:
HTML
<div data-ng-include="display()"></div>
Angular
$scope.display = function() {
var partialTemplate = null;
$http.get('angular/partials').
then(function (response) {
partialTemplate = response.data.path;
}
);
// the 'partialTemplate' variable will be a path for Angular to request the partial file from.
return partialTemplate;
};
This setup freezes the browser unfortunately. Removing the $http part and hardcoding a valid path solves this.
Is there any workaround for this while still fetching the partial path from the server (the server decides which partial to return)?
The core problem is that the $http.get is an async call - so therefore your return is being hit before the call completes (that, and you can't return from an async call). I'd suggest setting a $scope variable and assigning that when the call completes:
<div data-ng-include="myTemplate"></div>
And the JS:
function getTemplate() {
$http.get('angular/partials').then(function (response) {
$scope.myTemplate = response.data.path;
});
}
All that's left is to trigger the getTemplate function.
Related
I'm rather new to angular and I'm trying to integrate np-autocomplete in my application (https://github.com/ng-pros/np-autocomplete). However I can only get it to work when I'm passing a html string as a template inside the $scope.options and it doesn't work when I want to load it from a separate html.
the Code for my app looks as follows:
var eventsApp = angular.module('eventsApp',['ng-pros.directive.autocomplete'])
eventsApp.run(function($templateCache, $http) {
$http.get('test.html', {
cache: $templateCache
});
console.log($templateCache.get('test.html')) // --> returns undefined
setTimeout(function() {
console.log($templateCache.get('test.html')) // --> works fine
}, 1000);
//$templateCache.put('test.html', 'html string') //Would solve my issue in the controller,
//but I would rather prefer to load it from a separate html as I'm trying above
Inside my controller I am setting the options for autocomplete as follows:
controllers.createNewEventController = function ($scope) {
$scope.options = {
url: 'https://api.github.com/search/repositories',
delay: 300,
dataHolder: 'items',
searchParam: 'q',
itemTemplateUrl: 'test.html', // <-- Does not work
};
//other stuff...
}
however, it seems that test.html is undefined by the time np-autocomplete wants to use it (as it is also in first console.log above).
So my intuition tells me that the test.html is probably accessed in the controller before it is loaded in eventsApp.run(...). However I am not sure how to solve that?
Any help would be highly appreciated.
You are most likely correct in your assumption.
The call by $http is asynchronous, but the run block will not wait for it to finish. It will continue to execute and the execution will hit the controller etc before the template has been retrieved and cached.
One solution is to first retrieve all templates that you need then manually bootstrap your application.
Another way that should work is to defer the execution of the np-autocomplete directive until the template has been retrieved.
To prevent np-autocomplete from running too early you can use ng-if:
<div np-autocomplete="options" ng-if="viewModel.isReady"></div>
When the template has been retrieved you can fire an event:
$http.get('test.html', {
cache: $templateCache
}).success(function() {
$rootScope.$broadcast('templateIsReady');
});
In your controller listen for the event and react:
$scope.$on('templateIsReady', function () {
$scope.viewModel.isReady = true;
});
If you want you can stop listening immediately since the event should only fire once anyway:
var stopListening = $scope.$on('templateIsReady', function() {
$scope.viewModel.isReady = true;
stopListening();
});
I am not sure what the issue is, but it seems the datatables directive is being initialized before my $scope is defined. What's more, it seems the variables getting set twice.
I have a userService that retrieves the column definitions and data from my server. The getUserList() method returns a promise. I use the then() method to set the $scope variables that will be used by the datatables directive. It seems the directive is requesting the variables before the request completes. Also, it seems the variables are getting set twice, because in Chrome's dev console I see the "test" log twice.
If I use static data (not coming from the server) and place the $scope variables outside the getUserList() promise it works fine.
$scope.indexList = function () {
userService.getUserList().then(function (data) {
$scope.dtOptions = DTOptionsBuilder.fromFnPromise(function () {
console.log("test");
var deferred = $q.defer();
deferred.resolve(data.Data);
return deferred.promise;
});
$scope.dtColumns = [];
angular.forEach(data.DataTablesColumns, function (i, v) {
$scope.dtColumns.push(DTColumnBuilder.newColumn(i.Id)
.withTitle(i.DisplayName)
.renderWith(actionsHtml));
});
});
}
This is how I am setting the datatables directive:
<div ng-init="indexList()">
<table datatable="" dt-options="dtOptions" dt-columns="dtColumns"class="row-border hover"></table>
</div>
The directive code is executed as soon as the page loads. And since your $scope variables are defined in the promise, they are not available at the time of page load.
The directive does not wait for the request to complete because requests are async in nature. If you want the directive variable to be updated when the request completes you have to set a $watch(or $watchGroup if you want to watch multiple variables) on the $scope.variables in your link function as follows:
link: function(scope, element, attrs) {
scope.$watch('dtOptions', function(newval, oldval) {
//your directive code involving scope variables here.
})
}
It seems I have to make 2 calls to my server to do what I am after. This works, but I feel there should be a way to make a single call to the server and get back then entire result. After some reading the options and column builders can take a promise.
$scope.indexList = function () {
$scope.dtOptions = DTOptionsBuilder.fromFnPromise(function () {
return userService.getUserList(ngRequestGlobabls.context.organization).then(function(data) {
return data.Data;
});
});
$scope.dtColumns = userService.getUserList(ngRequestGlobabls.context.organization).then(function (data) {
columns = [];
angular.forEach(data.DataTablesColumns, function(i, v) {
columns.push(DTColumnBuilder.newColumn(i.Id).withTitle(i.DisplayName).renderWith(actionsHtml));
});
return columns;
});
I am quite beginner level with both JS and Angular, and I am trying to return data from an API and store it in $scope.
Once I've stored it, I want to loop over each item and output it into the page, pretty basic stuff.
Problem I am having is the API and data is there, but it seems to be returning after the loop is running, is there any way of making the loop wait?
Heres the code;
Service (Hit the endpoint and retrieve the data)
'use strict';
function RecruiterDashJobs($resource, API_URL) {
var dashJobs = {};
dashJobs.getJobs = function(uuid) {
return $resource(API_URL + 'recruiters/' + uuid + '/jobs').get();
}
return dashJobs;
}
angular
.module('app')
.service('RecruiterDashJobs', RecruiterDashJobs);
Controller (Call the service and store the data)
$scope.currentRecruiter = User.getUser();
$scope.getJobs = function(uuid) {
var data = RecruiterDashJobs.getJobs(uuid);
data.$promise.then(
function(res) {
return res.jobs
},
function(err) {
return err;
}
)
};
$scope.recruiterJobs = $scope.getJobs($scope.currentRecruiter.uuid);
View (the Ng-repeat)
<div class="panel border-bottom pad-s-2x pad-e-1x" ng-repeat="job in recruiterJobs">
<div class="panel__body">
<aside class="valign">
<a class="icon--edit color--echo mar-r-2x" ui-sref="jobs/edit/{{job.uuid}"></a>
</aside>
<div class="valign">
<p>{{job.title}}</p>
<p class="color--charlie">Closing Date: {{job.closing_date}}</p>
</div>
</div>
</div>
EDIT: the "magical" approach below no longer works as of Angular 1.2
In your getJobs method, the return statements are inside child functions. You aren't returning the data from getJobs, you're returning it from the function you passed to then.
Two ways to fix it:
The magical Angular way for Angular less than 1.2
Angular views will work with promises for you, so you can just change your getJobs method to this:
$scope.getJobs = function(uuid) {
var data = RecruiterDashJobs.getJobs(uuid);
return data.$promise.then(
function(res) {
return res.jobs
},
function(err) {
return err;
}
)
};
Added return data.$promise...
If you want this to still work in Angular 1.2, you need to call $parseProvider.unwrapPromises(true) somewhere in your code, typically on your main modules config block.
Less magical way or for Angular 1.2 and above
If you want to better understand what is going on, then you can do it this way
$scope.getJobs = function(uuid) {
var data = RecruiterDashJobs.getJobs(uuid);
data.$promise.then(
function(res) {
$scope.recruiterJobs = res.jobs
},
function(err) {
console.log(err);
}
)
};
A $resource call is asynchronous, but $resource immediately returns an empty object that you can embed in your page and will be later populated by response contents. If all goes well, angular should spot the change (because it comes from a $resource process that angular monitors) and update your view accordingly.
So, the behaviour you observe is normal : the very premise of a $promise is that it will be done at a later stage and the process should proceed anyway.
Solutions :
Simply try :
$scope.getJobs = function(uuid) {
var data = RecruiterDashJobs.getJobs(uuid);
return data;
};
If you don't need to post-process data, this should be all you need (except that you might need to call recruiterJobs.jobs in your view, if your response does indeed return an object containing a jobs array, and not the array itself). The page will display, with an initial empty div, then update when data are retrieved and ng-repeat discovers new data to add to the page.
If you do need some post-processing, you can still use your callback :
$scope.getJobs = function(uuid) {
var data = RecruiterDashJobs.getJobs(uuid);
data.$promise.then(
function(res) {
//do something
},
function(err) {
return err;
}
);
return data;
};
If you really need to wait for your data (e.g. because there are some downstream processes that you need them for that can't be postponed), you can use the promise to do so :
$scope.getJobs = function(uuid) {
$scope.preparing = true;
var data = RecruiterDashJobs.getJobs(uuid);
data.$promise.then(function(res) {
$scope.preparing = false;
return data;
});
};
This way, the function will not return until the promise is resolved. I added an optional $scope.preparing flag that you can use in your page to inform the user that something is loading.
I am trying to call a service in angular.js through a controller on load and return a promise. I then expect the promise to be fulfilled and for the DOM to be updated. This is not what happens. To be clear, I am not getting an error. The code is as follows.
app.controller('TutorialController', function ($scope, tutorialService) {
init();
function init() {
$scope.tutorials = tutorialService.getTutorials();
}
});
<div data-ng-repeat="tutorial in tutorials | orderBy:'title'">
<div>{{tutorial.tutorialId}}+' - '+{{tutorial.title + ' - ' + tutorial.description}}</div>
</div>
var url = "http://localhost:8080/tutorial-service/tutorials";
app.service('tutorialService', function ($http, $q) {
this.getTutorials = function () {
var list;
var deffered = $q.defer();
$http({
url:url,
method:'GET'
})
.then(function(data){
list = data.data;
deffered.resolve(list);
console.log(list[0]);
console.log(list[1]);
console.log(list[2]);
});
return deffered.promise;
};
});
Inside of the ".then()" function in the service, I log the results and I am getting what I expected there, it just never updates the DOM. Any and all help would be appreciated.
getTutorials returns promise by itself. So you have to do then() again.
tutorialService.getTutorials().then(function(data){
$scope.tutorials = data;
});
Before that, $http returns a promise with success() and error().
Although you can also use then as well
Since the returned value of calling the $http function is a promise,
you can also use the then method to register callbacks, and these
callbacks will receive a single argument – an object representing the
response.
So you are correct with that.
What is your data coming from the http call look like? Your code works - I created a version of it here http://jsfiddle.net/Cq5sm/ using $timeout.
So if your list looks like:
[{ tutorialId: '1',
title : 'the title',
description: 'the description'
}]
it should work
In newer Angular versions (I think v 1.2 RC3+) you have to configure angular to get the unwrap feature working (DEMO):
var app = angular.module('myapp', []).config(function ($parseProvider) {
$parseProvider.unwrapPromises(true);
});
This allows you to directly assign the promise to the ng-repeat collection.
$scope.tutorials = tutorialService.getTutorials();
Beside that I personally prefer to do the wrapping manually:
tutorialService.getTutorials().then(function(tutorials){
$scope.tutorials = tutorials;
});
I don't know the exact reason why they removed that feature from the default config but it looks like the angular developers prefer the second option too.
I am working on a multiple file upload module and am stuck when it comes to communicating from my service to my controller without using $rootScope.
A directive watches the file input and hands the files onchange to a service, where they get uploaded and the upload progress monitored. Based on the response (from success, error and progress changes), the parent controller should show thumbs and progress.
I do not want to use $emit and $on on my $rootScope, since I need the pattern quiet often and only that one parent controller needs to know about the uploads.
I created a simplified(!) Plunkr with some additional information to better understand the problem.
Is there another way for my controller to react to changes (happening inside the service factory)?
Or maybe a completely different way of achieving such?
A proper way to handle async operations with Angular is using promises.
You could return a promise from the service call and resolve it to the src of the thumb. You could then not use a directive at all.
Your controller would use the service this way:
function ParentController($scope, imageService) {
$scope.change = function() {
$scope.src = imageService.change();
$scope.then(function(result) {
// the loading ended, but there is no need to set the src
// as it will already bind to src when ends.
}, function(err) {
// something went wrong
});
};
}
Your service:
app.factory('imageService', function($q, $rootScope) {
function doTheUploading(defer) {
if (everythingOk) {
defer.resolve(thumbSrcString);
} else {
defer.reject({something: 'an error ocurred'});
}
}
return {
change: function() {
// creates the promise
var defer = $q.defer();
doSomethingAsync(defer);
return defer.promise;
}
};
});
At least, your HTML should look like:
<div ng-controller="ParentController">
<img src="{{ src }}"><br><br>
<span class="btn" ng-click="change()">Change!</span>
</div>
Regarding the progress, you will need to go with callbacks or returning an object that would bring the promise and a progress indicator (i.e: return {promise: defer.promise, percent: 0}). The progress indicator should be then updated from inside the service.
You could also chain your promise if you need any transformation in the URL returned from the server before sets it to the src property, i.e.:
$scope.src = imageService.change().then(function(result) {
return 'http://dummyimage.com/' + result;
});
Updated your Plunker.