AngularJS : Can't init with ng-init via Ajax - angularjs

HTML:
<div class="span10" ng-controller="GroupFieldsCntl" ng-init="init()">
<div ng-repeat="field in fields"></div>
</div>
GroupFieldCntl:
function GroupFieldsCntl($scope) {
$scope.fields = [];
$scope.init = function() {
// Get fields.
$.get(ctx + 'admin/fields/fieldsJSON', function(data) {
for(var i in data) {
$scope.fields.push(data[i]);
}
});
}
}
I'm sure the ajax call get correct response, but the html page doesn't display those data.

Like the commentors here say:
1. Use $http.get instead of $.get. This is Angular's Ajax and you should be using it. [it needs to be injectecd into the controller]
2. If you loop in var i in data you might loop through methods of non-data properties, so as was suggested, use
for (var i in data) {
if data.hasOwnProperty(i)
//do something
}
}
And if you don't think there will be issues with bad data, you can always use the following syntax to have the resolved promise (get request) resolve itself to the $scope variable:
$scope.fields = $http.get(tx + 'admin/fields/fieldsJSON');
When the data arrives fields will automatically contain the JSON response after it was resolved. This is a shortcut which doesn't handle error responses though.

The changes done to data will trigger view changes only if you do it with angular functions or use $apply on other functions. So as the comments suggest you can (should) either use angular's $http service, or call your function inside $apply.

Related

angular to fetch the json service from http

I have my js for angular to fetch the json service from http and am using the {{post.title}} on my html to get the data and post to my html.
The data is not showing up on html page - using code pen.
var app = angular.module("blogApp", []);
app.controller("mainCtrl", function($scope) {
$scope.posts = [];
let postsUrl ="https://jsonplaceholder.typicode.com/posts"
getPosts().then(posts =>{
$scope.posts = posts.slice(3);
$scope.$apply();
});
function getPosts(){
return fetch(postsUrl).then(res=>res.json());
}
});
I have seen your shared codepen. So Ricky as you are new to angularJS, I would suggest you to read the documentation related to angular 1 from here: Angular JS - Documentation
Now coming to your requirement, you required to call an external API and use the data from the result. For that you have to learn about the $http in angularJS : $http documentation
Coming to the code, angular supports the dependency injection. The code you have shared is a mystery for me like what fetch(postsUrl) function is doing? Where is the declaration?
Cut and short, the implementation should be clear and readable. Here is my refactored one:
var app = angular.module("blogApp", []); //here you defined the ng-app module
//you are initializing a controller, you need to inject $http for calling the API
app.controller("mainCtrl", function($scope, $http) {
//Declaration of the posts object
$scope.posts = [];
//Onetime initialization of the API Endpoint URL
let postsUrl ="https://jsonplaceholder.typicode.com/posts";
//A method for getting the posts
function getPosts(){
//We are calling API endpoint via GET request and waiting for the result which is a promise
//todo: Read about the Promises
//A promise return 2 things either a success or a failure callback, you need to handle both.
//The first one is success and the second one is a failure callback
//So in general the structure is as $http.get(...).then(successCallback, failureCallback)
$http.get(postsUrl).then(function(response){
//In promises you get data in the property data, for a test you can log response like console.log(response)
var data = response.data;
$scope.posts = data; //Storing the data in the posts variable
//Note: you don't need to call the $scope.$apply() because your request is with in the angular digest process.
//All the request which are outside the angular scope required a $apply()
}, function(err){
//log the err response here or show the notification you want to do
});
}
//The final step is to call that function and it is simple
getPosts();
});
Coming to the second part to show the data. You have to use the ng-repeat documentation, it is as ng-repeat="var item in collection track by $index". It's documentation is here ng-repeat
So you html should be in this structure:
<div ng-repeat="var post in posts track by $index">
{{post.userid}}
{{post.id}}
{{post.title}}
{{post.body}}
</div>
Now it is onto you to learn and implement.

how to return a value from a ajax promise

I have a method in my angular 1.5 controller, as shown below but I wanted to refactor the ajax call into the factory itself but I'm having problems with promises.. I'm trying to get to a point where in my controller I can just call the method like I've shown below. Is this possible? I'm trying to avoid having the ...success(function(...) in the controller code.
Any help much appreciated.
Trying to move to
vm.member = someFactory.getMember(vm.id);
Existing working controller code
vm.myMethod = myMethod;
...
function myMethod() {
someFactory.getMember(vm.id).success(function(response) {
vm.member = response;
});
}
When I move the getMethod line into the factory the response is populated obviously but as soon as I come back to the controller, even with the return value from the factory being the response the result is undefined. I know this is because of promises but is there a design pattern I'm missing or a clean way of doing this. Using my currently approach my controller is littered with .success(function()...)
Many thanks!
The procedure is called promise unwrapping.
Besides the fact that success is deprecated and should be replaced with then,
someFactory.getMember(vm.id).then(function(response) {
var data = res.data;
...
});
it is totally ok to have this in controller.
The alternative to this pattern is to return self-filling object (something that ngResource $resource does):
function getMember(...) {
var data = {};
$http(...).then(function (response) {
// considering that response data is JSON object,
// it can replace existing data object
angular.copy(data, response.data);
});
return data;
}
In this case controller can get a reference to the object instantly, and the bindings of object properties {{ vm.member.someProperty }} will be updated in view on response.
The pattern is limited to objects (and arrays), scalar values should be wrapped with objects.

AngularJs: Does not update the model after the modifying it through $http transform

I need to transform objects coming from $http call to an api. My code adds some fields (functions) to the object coming from the api, here the constructor of this object :
(function () {
window.TransformedObject = function (obj) {
var self = this;
self = {};
if (obj) {
self = angular.copy(obj);
}
self.hasChanged = function () {
// return true or false if the object has changed
}
return self;
}
}());
The $http transform code looks like this :
$http({
url: 'api/...',
method: 'GET',
transformResponse: function(value) {
return new TransformedObject(JSON.parse(value));
})
}).success(function(data){
vm.obj = angular.copy(data);
});
Note that the value in the transformResponse callback is stringified, and need to be parsed to get the object
All this is working fine, suppose the object coming from the api contains a key called title, doing obj.title = 'some title' will update the object.
The problem :
Binding the title field with an input tag will not update the object if the change is coming from the view.
I use a regular ng-model to do it:
<input type="text" placeholder="Title" ng-model="vm.obj.title"/>
even using $rootScope.$watch will never be triggered if the change is coming from the view aka the input tag.
$rootScope.$watch(function () {
return vm.obj;
}, function () {
console.log('watch');
// this log will never appear in the console
});
Am I doing something wrong, why transforming the object coming from the api is breaking angulars binding ???
Thanks.
http://www.bennadel.com/blog/2605-scope-evalasync-vs-timeout-in-angularjs.htm
Sometimes, in an AngularJS application, you have to explicitly tell
AngularJS when to initiate it's $digest() lifecycle (for dirty-data
checking). This requirement is typically contained within a Directive;
but, it may also be in an asynchronous Service. Most of the time, this
can be easily accomplished with the $scope.$apply() method. However,
some of the time, you have to defer the $apply() invocation because it
may or may not conflict with an already-running $digest phase. In
those cases, you can use the $timeout() service; but, I'm starting to
think that the $scope.$evalAsync() method is a better option.
...
Up until now, my approach to deferred-$digest-invocation was to
replace the $scope.$apply() call with the $timeout() service (which
implicitly calls $apply() after a delay). But, yesterday, I discovered
the $scope.$evalAsync() method. Both of these accomplish the same
thing - they defer expression-evaluation until a later point in time.
But, the $scope.$evalAsync() is likely to execute in the same tick of
the JavaScript event loop.

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.

Reload a list with angularjs

I'm just starting to play with angularJS, so maybe I'm asking something easy to do, but I can't find the way to do it.
The situation is the following: I have a list that's populated by an ng-repeat taking the values from a scoped controller variable. This variable is loaded on page load by an jsonp call, and this works fine.
The problem comes when I need to reload this list based on another select. For example, if a select 'day' value in the select I need to show some values and when I select 'week' I need to show others (also loaded via ajax).
What I've tried is to have a service that loads the data and returns it, and in the controller have two methods, one for the first load and another for the second one that does $scope.apply with the variable. I then call this second method on select value change (I've done it with jquery to simplify it until I can fix this).
This is part of my HTML
<div x-ng-controller='LeaderboardCtrl'>
<select id='leaderboard-select'>
<option value='day'>day</option>
<option value='week'>week</option>
<option value='month'>month</option>
</select>
<div x-ng-repeat='p in leaderboard'>
<p>{{p}}</p>
</div>
</div>
And this is part of the code that affects this functionality
var lead = angular.module("lead",[]);
function LeaderboardCtrl($scope,$attrs,$http,jtlanService) {
$scope.leaderboard = [];
$scope.period = 'day';
var data = {
period:$scope.period
};
$scope.loadLeaderboard = function(){
myService.loadLeaderboard(data).then(function(leaderboard) {
$scope.leaderboard = [];
$scope.leaderboard.push.apply($scope.leaderboard,leaderboard);
});
}
$scope.reloadLeaderboard = function() {
myService.loadLeaderboard(data).then(function(leaderboard) {
$scope.$apply(function() {
$scope.leaderboard = [];
$scope.leaderboard.push.apply($scope.leaderboard,leaderboard);
});
});
}
$scope.loadLeaderboard()
}
lead.service("myService",["$http", function($http) {
var myService = {
loadLeaderboard : function(data) {
var promise = $http.jsonp("/widget/leaderboardJSONP?callback=JSON_CALLBACK&_="+new Date(),{
params:data,
cache:false,
ajaxOptions: { cache: false }
}).then(function(response) {
return response.data;
});
return promise;
}
};
return myService;
}]);
$("#leaderboard-select").change(function(){
scope.period = $("#leaderboard-select").val();
scope.reloadLeaderboard();
});
Here's a fiddle with the code: http://jsfiddle.net/WFGqN/3/
Your fiddle is riddled with issues:
There's no ng-app in your mark-up
You need to change the second Framework Extensions dropdown to one of the "No wrap" options
Your service needs to be defined above your controller
Your controller is referencing "jtlanService" but you've defined "myService"
Your $http.jsonp call isn't going to work as is, but you could use can use the echo service (see Ajax Requests on the left side) to emulate requests
You can't and shouldn't be using jQuery events to call Angular controllers. You should use ng-change and not $().change (and even if you were using jQuery for event binding, you should be using $().on('change')).
You didn't need to use $scope.$apply in your loadLeaderboard function, since when you're calling it, you were already inside of of an $apply call.
There's no need for 2 load+reload leaderboard methods.
And after all that, you don't actually need jQuery.
Here's a fiddle that fixes things up and I think gets you what you want: http://jsfiddle.net/WFGqN/5/. You'll of course need to fix the service on your end, but you get the idea.
I recommend reading this SO answer: "Thinking in AngularJS" if I have a jQuery background?

Resources