data not defined in link function after $http request in Angular - javascript

I have the following code.
controller.js
angular.module('LiveAPP.main',['LiveAPP.factory'])
.controller('mainCtrl', ['$scope','$http', '$location','dataFactory',mainCtrl])
.directive('ratehome',function(){
return {
restrict:"E",
template: "<div id='rateYo'></div>",
link: function(scope, ele, attrs){
console.log("NEW",scope.recentArtist)
}
}
})
function mainCtrl($scope,$http,$location,dataFactory){
$scope.getRecentArtists = function(){
return $http({
method: 'GET',
url: '/artistsearch',
params: {getArtist: "all"}
}).then(function(recent){
$scope.recentArtist = recent.data
})
};
$scope.getRecentArtists();
$scope.recentArtist = ""
$scope.$watch('recentArtist',function(newValue,oldValue){
$scope.recentArtist = newValue
})
}
test.html
<ratehome></ratehome>
<ratehome></ratehome>
<ratehome></ratehome>
What happens here is upon instantiation of my controller(routing is set up correctly) there is a $http GET request that responds with data that I need that gets assigned to $scope.recentArtist. I want this data to be accessible in my link function, but it's not. I have a feeling that my directive is compiling before this request is sent. Is there any way around this? What is odd to me is that when I console.log(scope) and check in Chrome Developer Tools my data is there. Yet when I console.log(scope.recentArtist) its empty similar to its state in the controller. I was thinking I could maybe make the $http.get in my directive, but that seems a little awkward to me.
I have been having trouble with this problem for a few days, hopefully somebody can help me out.

If you're using angular ui-router you could also use resolve. With resolve you can do the $http before the controller starts.
You can use resolve to provide your controller with content or data that is custom to the state. resolve is 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
$stateChangeSuccess event is fired.
from the docs.
Please have a look at the demo below or this jsfiddle.
angular.module('demoApp', ['ui.router'])
.config(function ($urlRouterProvider, $stateProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider.state('home', {
url: '/',
template: '<ratehome></ratehome><ratehome></ratehome><ratehome></ratehome>',
controller: 'mainCtrl',
resolve: {
artists: function (artistsService) {
console.log('Resolve');
return artistsService.get(); //'/artistsearch',//artistsService.get();
}
}
});
})
.controller('mainCtrl', ['$scope', '$http', '$location', 'artists', MainCtrl])
.directive('ratehome', function () {
return {
restrict: "E",
template: '<div id="rateYo"><ul><li ng-repeat="artist in recentArtist">{{artist}}</li></ul></div>',
link: function (scope, elem, attrs) {
console.log("NEW", scope.recentArtist);
}
}
})
.factory('artistsService', function ($http) {
return {
get: function () {
console.log('getting');
return $http({
method: 'GET',
url: 'http://crossorigin.me/http://www.mocky.io/v2/558b30615f3dcbc414067170', //'/artistsearch',
//params: {getArtist: "all"}
}).then(function (recent) {
//console.log(recent);
return recent.data;
});
}
};
});
function MainCtrl($scope, $http, $location, artists) {
$scope.recentArtist = artists;
console.log('ctrl', artists);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.1/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.15/angular-ui-router.js"></script>
<div ng-app="demoApp">
<div ui-view=""></div>
</div>

AS your directive is not using isolated scope, that does mean you can directly access your controller scope inside your directive. I'd suggest you to to put that $watch inside directive link instead of your controller. That would intimate you that the ajax has been completed and data got changed & you get those changed value inside watcher function.
Code
.directive('ratehome',function(){
return {
restrict:"E",
template: "<div id='rateYo'></div>",
link: function(scope, ele, attrs){
$scope.$watch('recentArtist',function(newValue,oldValue){
console.log("NEW",newValue)
});
}
}
})

Your link function is running before the $http response comes back as you suspect. You can wait for it by using $broadcast and $on:
angular.module('LiveAPP.main',['LiveAPP.factory'])
.controller('mainCtrl', ['$scope','$http', '$location','$rootScope','dataFactory',mainCtrl])
.directive('ratehome',function(){
return {
restrict:"E",
template: "<div id='rateYo'></div>",
link: function(scope, ele, attrs){
scope.$on('artistLoaded', function(){
console.log("NEW",scope.recentArtist);
});
}
};
});
function mainCtrl($scope,$http,$location,$rootScope,dataFactory){
$scope.getRecentArtists = function(){
return $http({
method: 'GET',
url: '/artistsearch',
params: {getArtist: "all"}
}).then(function(recent){
$scope.recentArtist = recent.data
$rootScope.$broadcast('artistLoaded');
});
};
$scope.getRecentArtists();
$scope.recentArtist = "";
$scope.$watch('recentArtist',function(newValue,oldValue){
$scope.recentArtist = newValue
});
}
This way the code will not run until the response has been returned and set

Related

AngularJS variable from controller to directive

I would like to ask you if it is possible, and if yes, then how can I pass some variable from controller to directive.
Here is a bit of my code:
app.js
var VirtualGallery = angular.module('virtualGallery', ['VirtualGalleryControllers', 'ngRoute']);
VirtualGallery.constant('apiURL', 'roomPicture');
VirtualGallery.run(['$rootScope', function ($rootScope) {
$rootScope.roomPictures = [];
}]);
var VirtualGalleryControllers = angular.module('VirtualGalleryControllers', ['ngRoute']);
VirtualGalleryControllers.controller('AppCtrl', function ($http, $rootScope, $scope, apiURL, $q) {
$scope.getallrooms = function () {
$http.get(apiURL)
.success(function (data) {
$rootScope.roomPictures = data; //idk whether to use $scope or $rootScope
});
};
});
In this app.js I'm trying to get some data from DB, and that data I need to use in directive.
Directive
angular.module('virtualGallery')
.directive('ngWebgl', function () {
return {
restrict: 'A',
scope: {
'getallrooms': '=',
'roomPictures': '='
},
link: function postLink(scope, element, attrs) {
scope.init = function () {
//here I would like to be able to access $scope or $rootScope from app.js file.
};
}
};
});
In directive I need to gain access to $scope or $rootScope in function init() where I need to use that data.
HTML
<body ng-app="virtualGallery">
<div class="container" ng-controller="AppCtrl">
<div
id="webglContainer"
ng-webgl
getallrooms="getallrooms"
roomPictures="roomPictures"
></div>
<p ng-model="roomPictures"></p>
<p ng-model="getallrooms"></p>
</div>
<script type="text/javascript" src="js/vg.js"></script>
<script type="text/javascript" src="js/ngWebgl.js"></script>
In html I'm trying to pass that data from app.js to directive.
Im quite new to Angular and this is even my first directive, so I am bit confused. Every help will be appreciated. Thanks guys :)
In your app.js use the controller like this
VirtualGalleryControllers.controller('AppCtrl', function ($http, $rootScope, $scope, apiURL, $q) {
$scope.getallrooms = function () {
$http.get(apiURL)
.success(function (data) {
$scope.roomPictures = data; //use $scope instead of $rootScope
});
};
});
Then for your directive:
angular.module('virtualGallery')
.directive('ngWebgl', function () {
return {
restrict: 'A',
scope: {
pictures: '=virtualGallery'
},
link: function postLink(scope, element, attrs) {
scope.init = function () {
// you can access the variable through the scope
scope.pictures;
};
}
};
});
Or you could simply make the http request in your directive and manipulate the data there.
You can inject $rootScope to your directive ( like you did in your controller ) and then access that rootScope variable.

Data is not being transferred from one custom directive to another if we use $http to fetch data

I have two different custom directive and wanted to pass data from one directive to another. The data is coming from server call. Problem is http being a asyn call doesn't return data upfront and controller of another widget doesnt receive it and it renders it's html without having it. here is the complete code (I have removed some code that might not make sense in the problem)-
The service which hits server is -
angular.module('myModule')
.service('MyService', [
'$http',
function($http) {
this.getData = (someId) => {
var url = someUrl + '/' + someId;
return $http.get(url);
};
}
]);
and the first directive that calls service and set "anotherData" in the scope to be transferred to another directive is -
angular.module('myModule')
.directive('myDirective', ['MyService',
function(MyService) {
return {
restrict: 'E',
scope: {
data: '='
},
templateUrl: 'my-template.html',
controller: ['$scope', function($scope) {
MyService.getData ($scope.data.id).then((response) => {
$scope.anotherData = response.data;
});
}]
}
}]);
and my-template.html from which i am calling another directive is (notice anotherData is passed here -
<other-directive mode="display" data="data" anotherData="anotherData" ></other-directive>
The other directive that should receive "anotherData" but giving me no result is -
angular.module('otherModule')
.directive('otherDirective', [function() {
return {
restrict: 'E',
scope: {
id: '#',
data: '=',
mode: '#',
anotherData: '#'
},
templateUrl: 'other-template.html',
controller: ['$scope', '$element', function ($scope, $element) {
console.log("other data in widget after server call:");
///THIS IS UNDEFINED.
console.log($scope.anotherData);
}],
link: function ($scope, $element) {
}
}
}]);
and other-template.html has iframe to display youtube widget -
<iframe width="{{anotherData.videoWidth}}"
height="{{anotherData.videoHeight}}"
src="{{anotherData.videoURL}}" frameborder="0" allowfullscreen></iframe>
You should use dashes instead of camel case, like this:
<other-directive mode="display" data="data" another-data="anotherData" ></other-directive>
Also, you are binding for text instead of two-way binding an object, so change the definition in your directive to this:
anotherData: '='
This should do the trick
<other-directive another-data=anotherData></other-directive>
You need to be caarefull with the camel case in html!

AngularJs directive with HTTP request data

I'm relatively new to AngularJs, I have a problem using a custom directive when data comes from an HTTP request.
I have a service with an HTTP get request.
app.service('someService', function($http, $q){
this.getData = function(){
var deferred = $q.defer();
$http({
method: 'GET',
url: 'theUrl'
})
.success(function(data, status){
deferred.resolve(data);
})
.error(function(data, status){
deferred.reject;
})
return deferred.promise;
}
})
and a controller that calls the service.
app.controller('someConroller', function($scope, someService){
someService.getData().then(function(response){
$scope.data = response;
})
$scope.someArrayData = [
{.....}, {.....}, ...
]
}
Here is a very simple custom directive.
app.directive('customDirective', function(){
return {
link: function(scope, element, attrs){
console.log(scope[attrs['customDirective']]);
}
}
})
The problem is when I get an instance of the directive using someArrayData it works fine. But when I get an instance of the directive using data (the data that I get from the http service) console.log(data) gives me undefined.
<div custom-directive="someArrayData"></div><!-- console.log(scope[attrs['customDirective']]) gives the expected result -->
<div custom-directive="data"></div><!-- console.log(scope[attrs['customDirective']]) undefined -->
Thanks for helping.
You'll need a $watch to "listen" for that new value inside your directive once resolved by your service. There are various ways to do this, but this will be the most straightforward for understanding the concept. Also, you can likely clean this up a bit if you bind your value to that directives scope - essentially your call to scope[attrs[... can be streamlined. Observe the following...
angular.module('app', [])
.controller('ctrl', function($scope, $timeout) {
// -- simulate ajax call
$timeout(function() {
$scope.data = ['A', 'B', 'C'];
}, 500)
})
.directive('customDirective', function() {
return {
scope: {
data: '=customDirective'
},
link: function(scope, elem, attrs) {
scope.$watch('data', function(newVal, oldValue) {
console.log(newVal) // -- or console.log(scope.data)
});
}
}
});
JSFiddle Link - demo
That's because the data is not yet retrieved when the directive is linked.
You can simply wrap the html element with ng-if:
<div ng-if="data">
<div custom-directive="data"></div>
</div>
The controller and the directive have different scopes, so when you assign $scope.data in your controller, you aren't doing it for your directive. So you should inject your service in your directive and request the data there.
If you are having trouble understanding scope heirarchies, read up on them in the Angular documentation for scope.
I would suggest downloading the Angular JS Batarang extension for Chrome - it allows you to inspect all the different scopes on your page.

In Angular, how to force template to recompile when I receive some new data?

Suppose I have a directive in which a template contains some data that provide another JS to draw a piechart:
mymodule.directive('myGauge', ['$http', function() {
return {
restrict: 'E',
template: '<div class="quota-dynamic"><h3>Limit Summary</h3><div class="d3_quota_bar"><div class="pie_chart" data-used="{$ dataUsed $}"></div></div>'
}
}]);
whereas "pie_chart" is used in another JS and it relies data-used value to draw the piechart.
I have another controller which receive the data:
mymodule.controller('MyCtrl', ['$scope', '$http', function($scope, $http) {
$scope.$parent.$watch("toggled", function(toggled) {
var tenant_id = 1234;
if (!$scope.$parent.isCollapsed()) {
$http({
method: 'get',
url: 'http://localhost:8080/admin/projects/' + tenant_id + '/test/'
}).success(function (data, status) {
$scope.dataUsed = data.totalInstancesUsed / data.maxTotalInstances * 100;
}).error(function () {
});
}
});
}]);
When I receive the data, it is too late for the piechart as it is drawn already. I would like to know if there is a way to force the directive to update the template with new data again?
Thanks.
If you need to recompile the template because there are others directive that have to be rerendered, you can add this inside your directive:
controller: function($scope,$element){
$scope.$watch( 'dataUsed', function( newVal, oldVal ){
$compile($element.contents())($scope);
});
}
This is valid if the scope of the directive is the same as MyCtrl
You will need to keep watch in the controller of your directive.
$scope.$watch('dataUsed', function(oldValue, newValue) {
console.log(oldValue, newValue)
}, true)

ng-model is not updating in nested directive

I have text-angular embedded in an directive that has the scope variable... scope.htmlContent.content. In the directive I have
template:
'''
// This updates just fine. I use it to debug so I will take this out from time to time
<p ng-bind='htmlContent.content'></p>
// ng-model htmlContent.content stays blank and does not update
<text-angular ng-model='htmlContent.content'>
</text-angular>
''',
link: function(scope, ele, attr, ctrl) {
//some code
$http({
method: 'GET'
url: 'someurl.com'
}).success(function(data,headers,config) {
// This does not update text-angular
scope.htmlContent.content = data;
// If I add this, it will error out
scope.$apply()
})
}
Anyway, ng-model is not updating properly. Only when I explicitly set scope.htmlContent.content in the beginning of the link function out side of some async fxn then it works. How can I update ng-model?
You need to create a factory for your http get call something like this:
//Please change it as per your needs
app.factory('factoryProvider', function(){
return {
yourData:function(callback){
$http.get('url').success(callback);
}
}
});
Then in your directive you need to inject the factory
app.directive('myDiv',['factoryProvider', function(factoryProvider) {
return {
restrict: 'E',
replace: true,
template: '<p>{{name}}</p>',
controller: function($scope) {
},
link: function(scope) {
scope.data=factoryProvider.yourData;
}
};
}]);
Hope it helps!!

Categories

Resources