Async nature of AngularJS with digest/apply - javascript

I've often had a problem where I had a scope variable set up in a parent controller, and the child controller calls this scope variable. However, it calls it before the function has been able to set the scope element, causing it to return undefined. Example:
Parent Controller:
module.controller('parent', '$scope', '$http', function ($scope, $http) {
$scope.init = function(profileID, profileViewStatus) {
//Initiiaze user properities
$http.get(requestUserInformationGetURL + profileID)
.success(function(profile) {
$scope.profile = profile;
$scope.userID = profile.user_id;
$scope.username = profile.username;
console.log($scope.userID);
})
.error(function() {
exit();
});
}
Child Controller:
module.controller('child', function($scope, $http, fetchInfo) {
console.log($scope.userID);
//Fetch the HTTP POST data for the user profile
var promise = $http({
method: "post",
url: fetchInfo,
data: {
user_id: $scope.userID //From the parent controller
},
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
promise.then(function(successResponse) {
//Populate the scope, log the data
console.log(successResponse);
$scope.data = successResponse.data;
}, function(error) {
alert(error);
});
HTML:
<div ng-controller="parent" init="init('<?php $user_id;?>')">
<div ng-controller="child">
</div>
</div>
What often happens is that the userID will be reported back as undefined in the child controller, but then right after, it will be reported back as defined in the parent controller. Obviously, the child controller using the $scope.userID is being called before the init function in the parent controller is complete. How do I force AngularJS to wait in the child controller until the init function is complete? I've tried something like:
if (!$scope.userID) {
$scope.$digest();
}
But it didn't work and I don't think it's the correct syntax. I guess, I don't fully understand the Asycn nature of AngularJS and this occurs multiple times. How do you control the DOM loading elements to solve something like this problem?

Proper way in this case would be to use dedicated service to handle async operations, requests, data caching, etc. But since you don't have service layer yet, I will propose simple Promise-based solution using controller scope promise object.
Check you modified code:
module.controller('parent', ['$scope', '$http', function ($scope, $http) {
$scope.init = function (profileID, profileViewStatus) {
$scope.profilePromise = $http.get(requestUserInformationGetURL + profileID).success(function (profile) {
$scope.profile = profile;
$scope.userID = profile.user_id;
$scope.username = profile.username;
})
.error(exit);
}
}]);
module.controller('child', function($scope, $http, fetchInfo) {
// Fetch the HTTP POST data for the user profile
$scope.profilePromise.then(function() {
return $http({
method: "post",
url: fetchInfo,
data: { user_id: $scope.userID },
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
})
.then(function(successResponse) {
console.log(successResponse);
$scope.data = successResponse.data;
}, function(error) {
alert(error);
});
});
As you can see, parent controller init method is still called, but now it immediately sets scope property profilePromise, which is accessible in child controller.
Child controller uses then method of the parent controller profilePromise object, which guaranties that $http request using $scope.userID will fire after profile is already available.

Generally you would use a route resolve with the UI Router to ensure the work is done before either controller is constructed. Child states automatically have access to the resolves of their parent.
//Router configuration
.state('app.inspections.list', {
url: '',
templateUrl: 'Template/parent',
controller: "Parent as vm",
resolve: {
profile: ['$http', function ($http) {
return $http.get(requestUserInformationGetURL + profileID)
.success(function(profile) {
console.log(profile.userID);
return profile;
})
.error(function() {
exit();
});
}]
}
}).state('parent.child', {
url: 'child',
templateUrl: 'Template/child',
controller: "Child as vm"
})
//parent controller
module.controller('parent', '$scope', 'profile', function ($scope, profile){
$scope.profile = profile;
$scope.userID = profile.user_id;
$scope.username = profile.username;
}
//child controller
module.controller('child', 'profile', function($scope, $http, fetchInfo, profile){
console.log(profile.userID);
//Fetch the HTTP POST data for the user profile
var promise = $http({
method: "post",
url: fetchInfo,
data: {
user_id: profile.userID //From the parent controller
},
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
promise.then(function(successResponse) {
//Populate the scope, log the data
console.log(successResponse);
$scope.data = successResponse.data;
}, function(error) {
alert(error);
});

you can use promise ($q service) :try using this code:
parent controller :
$scope.init = function(profileID, profileViewStatus) {
var deferred = $q.defer();
$http.get(requestUserInformationGetURL + profileID)
.success(function(profile) {
$scope.profile = profile;
$scope.userID = profile.user_id;
$scope.username = profile.username;
deferred.resolve($scope.userID);
console.log($scope.userID);
})
.error(function() {
deferred.reject('error');
exit();
});
return deferred.promise;
}
Don't call init method in parent contrller.
in child controller:
$scope.init().then(function(userID){
var promise = $http({
method: "post",
url: fetchInfo,
data: {
user_id: userID //From the parent controller
},
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
promise.then(function(successResponse) {
//Populate the scope, log the data
console.log(successResponse);
$scope.data = successResponse.data;
}, function(error) {
alert(error);
});
})
.catch(function(error){
console.log('error');
})

Your problem might be that $.get is being called asynchronously, which is the default behavior. Your init method might actually be called in the order you're expecting but what's happening is:
Parent init is called
$.get is called, but the server's response is non-instantaneous
Child init is called
GET data bounces back from the server
$.get(..).success(function(data){...}); is called to deal with the data
I'd suggest what other people are, using promises to defer execution.

Related

How to pass data with Angularjs

How do i pass data from the controller to a component to show to the user?
app.js
(function(angular) {
'use strict';
angular.module('app', []).controller('MainCtrl', function MainCtrl($scope, $http) {
$http({
method: 'GET',
url: '/team',
}).then(function successCallback(response) {
console.log(response.data);
this.teams = response.data;
$scope.teams = response.data;
// var keys = Object.keys($scope.agendaEventData);
// $scope.eventslength = keys.length;
}, function errorCallback(response) {
});
}).config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
}]);
})(window.angular);
component
(function(angular) {
'use strict';
angular.module('app').component('bringTeamToEvent', {
templateUrl: '/assets/ng/app/team/bringTeamToEvent.html',
bindings: {
teams: '<'
},
});
})(window.angular);
Template
{{$ctrl.teams}}
{{teams}}
The data is coming from the api no problems, i do not understand the complexity to get it to work.
Learning from https://docs.angularjs.org/tutorial/step_13
and https://docs.angularjs.org/guide/component#!
Setting the data on this.teams is correct, and correct to access it like $ctrl.teams. The issue here is that your $http callback functions are injecting their own function context so that this doesn't refer to the component.
For this reason, it's generally good practice to use arrow functions for callback functions:
$http({
method: 'GET',
url: '/team',
}).then(response => {
this.teams = response.data;
}, response => {
});
Here is a demo

How to call php file via factory/service method using Angular.js

I need to call php file using service/Factory method using Angular.js. Here instead of calling $http repeatedly in each file to call diferent php file for different purpose, I need to make it common. I am explaining one example below.
logincontroller.js:
var loginAdmin=angular.module('Takeme');
loginAdmin.controller('loginController',function($scope,$http,$location,$window,inputField){
$http({
method: 'POST',
url: "php/Login/verify.php",
data: userData,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}).then(function successCallback(response){
},function errorCallback(response) {
});
}
I have one common route.js file which is common for all controller and given below.
route.js:
var Admin=angular.module('Takeme',['ui.router', '720kb.datepicker','ngMessages','ngCapsLock','ui.bootstrap','ngFileUpload','angularUtils.directives.dirPagination']);
Admin.run(function($rootScope, $state) {
$rootScope.$state = $state;
});
Admin.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('/',{
url: '/',
templateUrl: 'view/login.html',
controller: 'loginController'
})
})
Admin.factory('inputField',function($timeout,$window){
return{
borderColor:function(id){
$timeout(function() {
var element = $window.document.getElementById(id);
if(element){
element.focus();
element.style.borderColor = "red";
}
});
},
clearBorderColor:function(id){
$timeout(function() {
var element = $window.document.getElementById(id);
if(element){
element.style.borderColor = "#cccccc";
}
});
}
};
});
Here I need to that $http service to call the php file common for which in every controller I will call that $http repeatedly. I need to pass only the parameters for $http service and return the response.
create a factory/service
angular.module('myApp').factory('DataService', DataService);
DataService.$inject = ['$http', '$q'];
function DataService($http, $q) {
return {
getData: getData,
}
function getData(userData) {
var deferred = $q.defer();
$http({
method: 'POST',
url: "php/Login/verify.php",
data: userData,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then(function(response) {
deferred.resolve(response.data);
},
function(error) {
deferred.reject(error.data);
});
return deferred.promise;
};
}
then use this factory whenever you need in a controller
angular.module('myApp')
.controller('MyController', ['$scope', 'DataService',
function($scope, DataService ) {
$scope.getMyData = function() {
var data = {};
DataService.getData(data)
.then(function(response) {
}, function(error) {
});
};
}
]);

AngularJS: console.log does not display anything

I wrote a controller for login page. Here is my controller:
var authApp = angular.module('loginApp', [])
authApp.controller('LoginCtrl', ['$scope', '$location', 'loginFactory', function($scope, $location, loginFactory){
$scope.authenticate = function() {
loginFactory.login($scope.username, $scope.password)
.then(function(response) {
console.log(response.$statusText);
}, function errorCallBack(response) {
console.log(response.$statusText);
});
}
}]);
My service:
authApp.factory("loginFactory", function ($http) {
return{
login: function(username, password) {
var data = "username="+username+"&password="+password+"&submit=Login";
return $http({
method: 'POST',
url: 'http://localhost:8080/login',
data: data,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}
});
}
When I debug the code, authentication seems successful and it did get into then function. However nothing displays in console. And I got a warning(?) saying undefined for the line console.log(response.$statusText);. It is not an error since it is not red. Why doesn't it print out anything?
Use response.statusText not response.$statusText. The documentation for AngularJS $http requests lists statusText as one of the properties of the response object - https://docs.angularjs.org/api/ng/service/$http

not getting data from $http from service to scope

we are trying to get data from service agrService with $http its working but when i reccive data to controller i am not able to access it outside that function
$scope.resource return data inside function but not outside please help.
var app = angular.module('app', ['ui.router','ngTasty']);
app.config(['$urlRouterProvider', '$stateProvider',function($urlRouterProvider, $stateProvider, $routeProvider, $locationProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('home', {
url: '/',
templateUrl: 'templates/home.html',
controller: function($scope, $http, $location, agrService) {
agrService.bannerSlides().then(function(data) {
//its working here
$scope.resource = data;
}, function(error) {
// do something else
});
I NEED TO ACCCESS DATA HERE CAN ANY BODY HELP
console.log($scope.resource);
}
});
}]);
app.service('agrService', function($q, $http) {this.bannerSlides = function() {
var dataUrl = 'http://WWW.EXP.COM/codeIgniter_ver/main/home';
var ret = $q.defer();
$http({
method: 'GET',
dataType: "json",
url: dataUrl
})
.success(function(data, status, headers, config) {
ret.resolve(data);
}).error(function(data, status, headers, config) {
ret.reject("Niente, Nada, Caput");
});
return ret.promise;
};
});
My suggestion would be to rethink the logic a bit. You want to do something with the data after you receive it, so why not make a function that you call once the data is received?
You'll never be able to access the resource data in that console log, simply because $http is an async call, and no matter if you return a promise or not, it's simply not ready at that point.
However, if you use it in a template or elsewhere that uses angular's double binding, it will work just fine.
To fix your issue, you can define a function with what happens after that service call and simply call it from the success callback:
agrService.bannerSlides().then(function(data) {
//its working here
$scope.resource = data;
myAfterFunction(); // <--- here
}, function(error) {
// do something else
});
and the function can be:
function myAfterFunction() {
console.log($scope.resource);
}
And btw. your service is an example of deferred antipattern, you can simply do this:
app.service('agrService', function($q, $http) {this.bannerSlides = function() {
var dataUrl = 'http://WWW.EXP.COM/codeIgniter_ver/main/home';
return $http({
method: 'GET',
dataType: "json",
url: dataUrl
})
};
});

Angular ng-repeat renders template before iterator is populated

When my ng page loads containing the ng-repeat markup below, it renders the IMG tag before the iterator is populated, but because no src value is present, that generates a 404 for the partial src.
<div ng-repeat="item in preview_data.items">
<h4>{{item.title}}</h4>
<img src="{{item.thumb}}" />
</div>
Then my controller kicks in and populates it with the right list of videos.
How do I stop the HTML from getting rendered until the controller is ready with the data?
The page is called by this route:
app.config(function ($routeProvider, $locationProvider ) {
$locationProvider.html5Mode(true);
console.log('config');
$routeProvider.when("/", {
templateUrl: "/templates/createFeed.html",
controller: "CreateFeedController"
});
});
which calls a factory to get a list of videos to preview from the backend api:
app.controller("CreateFeedController", function ($scope, $route, $sce, Preview) {
var keywords = decodeURIComponent($route.current.params.keywords);
$scope.preview_data = {
keywords: keywords
}
//pass parameters to web preview API
Preview.get(keywords, function (data) {
$scope.preview_data.items = data;
});
});
app.factory('Preview', function ($http) {
return{
get: function (keywords, next) {
$http({method: 'GET', url: '/api/preview/', json:true,
params: {keywords: keywords}}
).success(function (data) {
// prepare data here
//console.log(data);
next(data);
});
}
};
});
You must use the ng-src directive instead of the plain src attribute in your img tag.
From the Angular API for ng-src:
The browser will fetch from the URL with the literal text {{hash}} until Angular replaces the expression inside {{hash}}. The ngSrc directive solves this problem.
Check the ng-cloak directive.It's intended exactly for this.
http://docs.angularjs.org/api/ng.directive:ngCloak
As you know $http returns promise. Therefore your factory is async.
So factory should be like:
app.factory('Preview', function ($http, $q) {
return{
get: function (keywords, next) {
var deferred = $q.defer();
$http({method: 'GET', url: '/api/preview/', json:true,
params: {keywords: keywords}}
).success(function (data) {
deferred.resolve(data);
}).error(function() {
deferred.reject("Error ...");
});
//Returning the promise object
return deferred.promise;
}
};
});
And controller:
Preview.get(keywords) // returns promise
.then(function (result) {
$scope.preview_data.items = result;
}, function (result) {
alert("Error: No data returned");
});

Categories