Controller with ngResource method called indefinitely - angularjs

I decided to start learning AngularJS by making a simple app.
The server-side application is built with ExpressJs, but the resource used below (/movie/:id) are not implemented yet, so pointing to this URL will result in a 404 (Not Found) error. So only getting '/' works.
I wanted to see how a $resource behaved so I made this simple test :
var app = angular.module("app", ["ngResource"]);
app.factory("Movie", function ($resource) {
return $resource("/movie/:id");
})
app.controller("MovieCtrl", function($scope, Movie) {
$scope.test = function () {
Movie.query();
return 42;
}
});
And my template file :
<div ng-app="app">
<div ng-controller="MovieCtrl">
{{ test() }}
</div>
</div>
As expected the template is rendered and '42' is properly displayed, but if I watch the console in the Chrome developer tools I keep seeing the following error (also as expected):
GET http://localhost/movie 404 (Not Found)
But this message is printed indefinitely and never stops, as if my Movie resource keeps trying to reach /movie even though after a hundred tries it still keeps failing.
Thank you in advance.

This is because Movie.query() calls $scope.$apply() after it gets response from the server.
Everytime $scope.$apply() is called, angular does dirty checking (which again invokes test and therefore calls Movie.query() again) to find out if anything has changed. This causes an infinite loop.
move Movie.query() out from the test(), and this should work.
Let me make myself clear - take look at this pseudo code:
var watches = ['$scope.test()'];
var previous = {};
var values = {};
$rootScope.$apply = function(){
previous = values;
values = {};
var dirty = false;
for (var i =0;i<watches.length;i++){
var expression = watches[i];
values[expression] = value = eval(expression);
if(value!=previous)dirty=true;
}
if(dirty)$rootScope.$apply();
}
Movie.query = function(){
setTimeout(function(){
$rootScope.$apply();
},300);
}
$scope.test = function(){
Movie.query();
return 42;
}
so the flow is following:
$scope.apply();
$scope.test();
Movie.query(); -> setTimeout($scope.apply,100) (back to beginning );
and so on..

Related

404 error sporadically occurs on umbraco area module with angular

I have an Umbraco project with an Area section configured with Angular.
I use the Plugins to integrate the Area with the use of package.manifest like this:
Into edit.controller.js, I have this script:
'use strict';
angular.module("umbraco")
.controller('Administration.AdministrationTree.EditController', function administrationEditController($scope, $routeParams, $http) {
//set a property on the scope equal to the current route id
$scope.id = $routeParams.id;
$scope.url = "";
$scope.canShow = false;
$scope.showIframe = function () {
if ($scope.url === "") {
return false;
}
return true;
};
$scope.canShow = false;
if (!$scope.id) {
return;
}
$http.get('/umbraco/backoffice/administration/CustomSection/GetUrl/?node=' + $scope.id)
.success(function (data) {
$scope.url = JSON.parse(data);
$scope.canShow = $scope.url;
});
});
When I run the project and click on any node in this area, I receive most of the time a 404 error like if the page was not exist. I say "most of the time" because 1 out of 10, it works and the page is displayed.
However, if I put a breakpoint in the javascript function below and I click on any node and resume the javascript after the breakpoint was hitting, the node related html page is displayed correctly.
Anybody know why when I put a breakpoint, Umbraco or Angular are able to resolve 100% of the time the page but not when I don't have any breakpoint in this function?
Thanks
I really hate to answer my own questions but after 2 weeks without answers and a lot of reflections on my side, I finally found a solution to this problem.
What was causing the problem of out synching between Umbraco and Angular was due to the $http.get query which is asynchronous with Angular (no other choice I think) and after a response from the server to get a valid URL, the $scope object was not able to talk to Umbraco to redirect to the valid URL.
On my asp.net MVC controller, the GetUrl function was trying to get a valid URL doing a query to the database where I keep a structure of nodes which correspond to a tree displayed to the user. This is a slow process and the time required to respond to the HTTP get request was too long the vast majority of the time.
Here is my solution to this problem:
'use strict';
var myCompany = myCompany || {};
myCompany.myProject = myCompany.myProject || {};
myCompany.myProject.controller = (function (parent){
parent.urls = {};
function loadUrls () {
$.get('/umbraco/backoffice/administration/CustomSection/GetUrls')
.success(function (data) {
parent.urls = data;
});
};
loadUrls();
return parent;
})({});
angular.module("umbraco")
.controller('Administration.AdministrationTree.EditController', function administrationEditController($scope, $routeParams, $http) {
//set a property on the scope equal to the current route id
$scope.id = $routeParams.id;
$scope.url = "";
$scope.canShow = false;
$scope.showIframe = function () {
if ($scope.url === "") {
return false;
}
return true;
};
$scope.canShow = false;
if (!$scope.id) {
return;
}
var url = myCompany.myProject.controller.urls.find(function (element) {
return element.Key == $scope.id;
});
if (url) $scope.url = url.Value;
$scope.canShow = $scope.url;
});
Note in this case that I have an iffe function which query the server to build an array of all my URLs from the backoffice and then when Angular need a redirection, I search directly from the array.
The iffe function is calling only once when the user enters in the backoffice section which I think is nice because the structure behind rarely changes.
I'm not sure if it's a hack or the valid way to do the thing due to my lack of experience with Angular but it works like a charm.

Angucomplete Alt : Getting "No Results Found" before the server response is obtained

I am trying to initiate a request after 3 characters but once 3 characters are typed I am getting the No records found error and after that I can find the service call get completed but nothing is shown in auto complete(only the error message under the autocomplete dropdown).What am I missing here?
HTML code:
<angucomplete-alt id="angu_{{$index}}"
placeholder="Search people"
remote-api-handler="search"
remote-url-data-field="results"
title-field="fullName"
minlength="3"
input-class="form-control form-control-small"
selected-object="selected"
selected-object-data="$index"
clear-selected="true"/>
Api remote handler
$scope.search = function(str, timeoutPromise) {
return $timeout(function () {
$scope.input = {};
$scope.input.userId = "";
$scope.input.name = str;
$scope.input.system = "";
$scope.input.officeNumber = "";
$scope.input.department= "";
// server call and get the response to $scope.organisationNames
var promise = genericAPIService.doApiCall(url, appConstant.POST,
JSON.stringify($scope.input));
promise.then(
function(payload) {
console.log(payload);
$scope.organisationNames = payload.data.result.data.List;
$scope.results = [];
$scope.organisationNames.forEach(function(organisation) {
if ((organisation.fullName.toLowerCase().indexOf(str.toString().toLowerCase()) >= 0)) {
$scope.results.push(organisation);
}
});
return scope.results;
},
function(errorPayload) {
$log.error('failure loading role json', errorPayload);
}).finally(function(){
$scope.loading = false;
});
}
},5000);
};
Tried another version of the same:
<angucomplete-alt id="angu_{{$index}}"
placeholder="Search people"
selected-object="selectedBusiness"
selected-object-data="$index"
clear-selected="true"
input-class="form-control
form-control-small"
remote-api-handler="searchAPI"
remote-url-data-field="responseData.data"
title-field="fullName" minlength="3" />
$scope.searchAPI = function (userInputString, timeoutPromise) {
$scope.approversAndEndorsersInput = {};
return $timeout(function () {
$scope.approversAndEndorsersInput.userId = "";
$scope.approversAndEndorsersInput.name = userInputString;
$scope.approversAndEndorsersInput.system = "";
$scope.approversAndEndorsersInput.officeNumber = "";
$scope.approversAndEndorsersInput.department= "";
return $http.post(appConstant.selfDomainName+appConstant.getApproversAndEndorsersList, JSON.stringify($scope.approversAndEndorsersInput), {timeout: timeoutPromise});
}, 1000);
}
First Example
In your first example you never return the promise
var promise = genericAPIService.doApiCall(url, appConstant.POST, ...
So the $timeout is never resolved with the response from the API
Return the promise at the end of the $timeout callback
Second Example
It looks like that you are not settings correctly the remote-url-data-field
Here's a plunker that I came with using your second example http://plnkr.co/edit/QsJFWh?p=preview and it works correctly, I have slightly altered the searchAPI method to show the last http response
When I misconfigured the remote-url-data-field, the response was made but the angucomplete read "No results found"
See the notes on the plunk, and try to configure it the way it works for you
If you can't bind the correct property, provide a response sample from your api, and we can come up with a solution
Using $timeout
The reason you are using $timeout to add a delay is not obvious, you do it in both examples. The only thing this does is to add an artificial delay of the given amount of time. If the reason is to add some debounce/delay to the input field triggering an API request, that won't cut it.
There is a pause property that can be configured with a delay in milliseconds that will ensure that the api will be called after this amount of time when the user has stopped typing.
You can see it on the plunker demo, the default value is 500
Since your custom service and $http (from the 2nd example) both return promises, you can directly return their result
I was able to solve the issue.PFB the link which helped in solving it.
Angucomplete-alt: Remote-API-handler not working as expected
Based on what I see in your code, and in the documentation, I think the problem is that you're immediately returning a Promise from $timeout in your searchAPI function, which resolves itself as soon as the timeout is reached. This is why you'll see the "No Records Found" returned, and THEN your API is called. Instead, return your $http promise object directly. i.e.:
$scope.searchAPI = function(userInputString, timeoutPromise)
{
// do some other stuff, stringify data, build url_endpoint
return $http.post(url_endpoint, json_data, {timeout: timeoutPromise});
}
The $http promise will only resolve after your API has been consumed.

Angular freezes upon ng-include $http.get combination

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.

How do I maintain scope when delegating to service?

I've built a small service to handle errorMessages in my application. Its publicly available on the rootScope and is able to add new messages to my page as needed. Now the need to have clickable links in the messages have arisen.
Questions:
How do I dynamically add JavaScript that is handled by angular to the messages that are created?
I've added onclicks that work, but ng-click seem to not be handled.
The Js that I would like to run is in the controller that created the message in the first place. How do I make sure that I end up in the correct scope when clicking a link in an error message?
If the function adding a message is another service, how do I solve that?
And the service I'm playing around with:
var myApp = angular.module('myApp', []);
function errorHandlingFactory() {
this.messages = [];
this.addMessage = function (messageText, type) {
var message = this.messages.push({messageText: messageText, type: type, closeable: false});
};
this.getHtmlContent = function(messageId) {
return this.messages[messageId].messageText;
}
this.removeMessage = function (messageId) {
this.messages.splice(messageId, 1);
};
this.clearMessages = function() {
this.messages = [];
};
}
myApp.service('errorHandling', function () {
return new errorHandlingFactory();
});
myApp.run(function($rootScope, errorHandling) {
// Attach global error handling object to our rootScope
$rootScope.errorFactory = errorHandling;
});
// Usage from controller
$rootScope.errorFactory.addMessage('The message to be added', 'warning');
To make it a bit easier to understand, I've created a jsfiddle to look at.
http://jsfiddle.net/kxsmL25h/7/
What I would like to do is when the link in message is clicked, the function desiredCallback is run on the GenericTestController.

AngularJS : Service data binding

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.

Resources