Supplying data to an Angular directive (from file or database) - javascript

I'm trying to determine the right way or the Angular way to provide data to directives for construction of the DOM. As my example, I'm trying to get JSON data from a file to construct a tree with jsTree. The problem is constructing the tree when the data has arrived and not beforehand.
I've solved the problem by $watching treeData in my controller. If I don't $watch it, the tree will be constructed with an empty array as input, and later, Angular will update the reference to populate the data. But I feel like this is the wrong way to do it since the data isn't going to change after the tree is initially constructed.
Here is my code
app.js:
var app = angular.module('jsTreeApp', ['ngResource']);
var TestCtrl = function($scope, Data) {
$scope.treeData = Data.getTreeData();
}
app.directive('jstree', function() {
return function(scope, element) {
scope.$watch('treeData.data', function() {
$(element).jstree({
"json_data" : scope.treeData,
"plugins" : [ "themes", "json_data" ]
});
})
}
});
app.factory('Data', function($resource) {
return $resource('/data/treeData.json', {}, {
getTreeData: { method: 'GET', isArray: false }
})
})
index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>jsTreeAngular</title>
</head>
<body>
<div ng-controller="TestCtrl" ng-app="jsTreeApp">
<div jstree></div>
</div>
<script src="lib/angular/angular.js"></script>
<script src="lib/angular/angular-resource.js"></script>
<script src="js/app.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script src="lib/jstree/jquery.jstree.js"></script>
</body>
</html>
Any suggestions for the correct way to do this?

I don't see a problem with using the $watch, because you bind it, you need some way to see if the data is populated or not and to act upon it.
The problem that I see here is that it not obvious where the "treeData" is coming from, and that might confuse other developers.
What I would do instead is to define an isolated scope and to pass an attribute with the treeData, that way you can keep it loosely coupled and don't have to worry about changing the name of "treeData" in the controller.
you can define isolated scope like this:
app.directive('jstree', function() {
return {
scope: {
treeData: "="
},
link: function(scope, element) {
scope.$watch('treeData.data', function() {
$(element).jstree({
"json_data" : scope.treeData,
"plugins" : [ "themes", "json_data" ]
});
})
}
}
});

Related

Access to scope in angular directive with two data bindigs

good morning i have a small problem i have directive:
return {
restrict:'E',
scope: {
user: "="
},
template: '<b>{{userLogin}}</b>',
link:function(scope,elem,attrs){
},
controller: function ($scope) {
console.log($scope.user)//always undefindet
$scope.userLogin = $scope.user;
},
};
and i want to show my parameter "user" with scope in template i must use controller because i need download some data from server
I think problem is somewhere here(in directive):
scope: {
user: "=" //when i have this response "undefined"
user: "#" //when i have this response not show id only text
},
my HTML
<get-user-login user="{{post.user_id}}"></get-user-login>
i always getting: empty value or undefined in console.
How to fix that.
When using # you have to use interpolation as:
<get-user-login user="{{post.user_id}}"></get-user-login>
Note # is ONLY for text values
WHEREAS
When using = you do not need interpolation
<get-user-login user="post.user_id"></get-user-login>
Note = is only for passing objects (two way binded)
I built this demo for you and it works just fine :
<!DOCTYPE html>
<html lang="en" ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular.min.js"></script>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body ng-controller="ctrl">
<get-user-login user="post.user_id"></get-user-login >
<script>
angular.module("app",[])
angular.module("app").
controller("ctrl",function($scope){
$scope.post = {user_id:565};
}).
directive('getUserLogin', function() {
return {
restrict:'E',
scope: {
user: "="
},
template: '<b>{{userLogin}}</b>',
link:function(scope,elem,attrs){
},
controller: function ($scope) {
console.log($scope.user)//always undefindet
$scope.userLogin = $scope.user;
},
};
});
</script>
</body>
</html>
Are you sure that you're giving correct value to directive? Because in getPost you're binding it to $scope.onePost, so maybe correct way is:
<get-user-login user="{{onePost.user_id}}"></get-user-login>

Not having it pretty clear about JSON and its uses on Angular

I've got what I consider a very basic question, but I've almost never worked with JSON nor web devolpment (but yes with desktop's with Java), so I'm having really big troubles in my practices with Angular.
You see, I've got this JSON:
{
"imgs": [
{
"title": "Primera foto",
"src": "images/1.gif"
},
{
"title": "Segunda foto",
"src": "images/2.png"
},
{
"title": "Tercera foto",
"src": "images/3.gif"
},
{
"title": "Cuarta foto",
"src": "images/4.jpg"
},
{
"title": "Quinta foto",
"src": "images/5.png"
},
{
"title": "Sexta foto",
"src": "images/6.gif"
}
]
}
I retrieve it using $http and $q via a service, which works greatly. After that, I save the info in a scope variable in a controller:
ctrls.controller('imagesCtrl', ['$scope', 'getJSON', function($scope, getJSON){
$scope.images = [];
$scope.loadImages = function(){
$scope.images = getJSON.getImages();
}
$scope.loadImages();
alert($scope.images);
}])
... and I try to show it in the HTML:
<!DOCTYPE html>
<html ng-app="miApp">
<head>
<meta charset="UTF-8">
<!-- Importamos las dependencias principales !-->
<script src="angular.js"></script>
<!-- <script src="angular-routeangular-route.js"></script> !-->
<!-- Importamos nuestros propios archivos (controladores, directivas, etc...) !-->
<script src="app.js"></script>
<script src="controllers.js"></script>
<script src="directives.js"></script>
</head>
<body ng-controller="imagesCtrl">
<littleImage src="{{image.src}}" ng-repeat="image in images"></littleImage>
</body>
</html>
What am I doing wrong? Nothing appear after the ng-repeat, so I guess I'm not accessing correctly to the JSON data. In case you're doubtious, this is the directive I'm trying to use:
directives.directive('littleImage', function(){
return{
restrict: E,
scope: {
src : "#"
},
template: "<div><img src="+src+" class='img-responsive' alt='Image'></div>"
}
});
In case there are more doubts about my code, this is my service (pretty straightforward):
ctrls.factory('getJSON', ['$http', '$q', function($http, $q){
return{
getImages : getImages
}
function getImages(){
var defered = $q.defer();
var promise = defered.promise;
$http.get("data.json")
.success(function(data){
defered.resolve(data);
})
.error(function(err){
defered.reject(err);
});
return promise;
};
}]);
Anybody knows what am I doing bad?
**EDIT: **
Thanks for the help so far! You helped me a lot, but there's one last thing: now the directive, somehow, isn't computing correctly, and it doesn't wrap it's template into the actual HTML.
This is the HTML:
<div ng-repeat="image in images">
{{image.title}}
<littleImage source="image.src" ></littleImage>
</div>
And this is the directive:
directives.directive('littleImage', function(){
return{
restrict: E,
scope: {
source : "="
},
template: "<div><img ng-src="+source+" class='img-responsive' alt='Image'></div>"
}
});
Any idea on why it doesn't work?
The problem might be in your <img src="..."> Angular has this directive ng-src, because the $scope variable might not be loaded yet when your image tag gets rendered.
I think your problem lies in this line:
<littleImage src="{{image.src}}" ng-repeat="image in images"></littleImage>
The handlebars aren't required and the repeater needs changed, you can simply use:
<littleImage src="image.src" ng-repeat="image in images.imgs"></littleImage>
Your directive is wrong, you should use the normal binding and use an angular expression in the template: src variable doesn't exists, it's src attributes in scope that exists:
directives.directive('littleImage', function(){
return{
restrict: E,
scope: {
src : "="
},
template: '<div><img ng-src="src" class="img-responsive" alt="Image"></div>'
}
});
oh and also in the template, it is binded by angular expression not by text remove the curly braces:
<div ng-repeat="image in images">
{{image.src}}
<littleImage src="image.src" ></littleImage>
</div>
I slightly change your code to check if the data is well loaded, it should help the debug.
you are using Promise.Do this change in controller
getJSON.getImages().then(function(data) {
$scope.images = data.imgs;
}, function() {
//error hanlding part
})
you can't get images array synchronously, you must get it asynchronously.
you must get image list through promise like this:
ctrls.controller('imagesCtrl', ['$scope', 'getJSON', function($scope, getJSON){
$scope.images = [];
$scope.loadImages = function(){
getJSON.getImages().then(function (data) {
$scope.images = data;
})
}
//$scope.loadImages();
//alert($scope.images);
}])
without using of $q and promise way you can using of simple javascript callback to get data from service:
services.factory('getJSON',['$q', '$http', function ($q, $http) {
return {
getImages: function (cb) {
$http({
url: "YOUR_IMAGES_LIST_ADDRESS",
method: 'GET'
}).success(function (data) {
cb(data);
}).error(function (error) {
cb(null, error)
})
}
}
}]);
ctrls.controller('imagesCtrl', ['$scope', 'getJSON', function($scope, getJSON){
$scope.images = [];
$scope.loadImages = function(){
getJSON.getImages(function (data, error) {
if(error) {
//show error
}
$scope.images = data;
})
}
//$scope.loadImages();
//alert($scope.images);
}])
there is another alternative simplest way :), just return $http in your service:
services.factory('getJSON',['$q', '$http', function ($q, $http) {
return {
getImages: function () {
return $http({
url: "YOUR_IMAGES_LIST_ADDRESS",
method: 'GET'
});
}
}
}])
service return promise object of $http.
ctrls.controller('imagesCtrl', ['$scope', 'getJSON', function($scope, getJSON){
$scope.images = [];
$scope.loadImages = function(){
getJSON.getImages().then(function (response) {
$scope.images = response.data;
})
}
//$scope.loadImages();
//alert($scope.images);
}])
Ok! I voted all your posts because every one of them helped me. Thank you for that!
These were my troubles:
I wasn't getting the JSON properly all time, since I use promises. Also, I wasn't accessing to the "imgs" array. Changed my controller function to:
$scope.loadImages = function() {
getJSON.getImages().then(function(data) {
$scope.images = data.imgs;
}, function() {
alert("Ha habido un error cargando los datos");
});
}
The ng-repeat works better in a different div. Also, as you were saying, the src value must be the variable name, instead of its value (as it was being represented by using the curly braces):
{{image.title}}
As last but not least, my directive was totally wrong. Had to access to the src variable in the scope, using "=", change the src from the <img> tag to ng-src, and the last thing that nobody said: instead of writing "src" as its value, it works writing its actual value, via {{src}}:
directives.directive('littleImage', function(){
return{
restrict: "E",
scope: {
src : "="
},
template: ""
}
});
Now it's properly working! Thank you for your help!

How init angularjs's $rootScope in multiply js files

Sorry, for my English.
I have angularjs app with many pages and forms.
For any page created file with Models for this page.
Example:
file userDict.js:
$rootScope.dicts.user = {
userTypes : { ... },
maxAge : ...,
minAge : ... ,
...
}
file companyDict.js:
$rootScope.dicts.company = {
companyTypes : { ... },
blabla : ...,
blabla : ... ,
...
}
files *Dict.js
....
....
And in UserController I use $rootScope.dicts.user, in CompanyController - $rootScope.dicts.company.
This is done for ease of separation large models.
But how to make that in each js models file calls $rootScope.dicts.??? = { ... } ?
How wrapping each file with models, that they calling during init app?
for example, if I wrote it using plane jQuery, I would do it so for each file
file userDict.js:
$(document).ready(function(){
$rootScope.dicts.user = {
userTypes : { ... },
maxAge : ...,
minAge : ... ,
...
}
});
If I clearly understood, what you want, you have two options to do it.
1) In the first option you should do it smth like this:
angular.module('myApp', [])
.run(function($rootScope) {
$rootScope.dicts = {};
$rootScope.dicts.user = "rootScope";
})
.controller('myCtrl', function($scope, $rootScope) {
$scope.userDict = $rootScope.dicts.user;
})
.controller('myCtrl2', function($scope, $rootScope) {
$scope.userDict = $rootScope.dicts.user;
});
This is JSFiddle for this example.
This code provide possibility to getting of $rootScope variables from any controller.
But you don't forget to inject $rootScope to controller.
2) The second option is using of shared service.
myApp.factory('sharedService', function () {
var sharedService = {};
sharedService.dict = {};
sharedService.dict.user = "any value";
return sharedService;
});
myApp.controller('FirstCtrl', function ($scope, sharedService) {
$scope.user = sharedService.dict.user;
});
myApp.controller('SecondCtrl', function ($scope, sharedService) {
$scope.user = sharedService.dict.user;
});
Based on #artyom-paranovich answer, i think you have 2 options.
The first and better solution is to put your data as a service. Note that Services aren't only for DB data but for general data as you are doing.
I would write an example but #artyom-paranovich did an excellent job for it.
My Advice that you will take this approach.
The second options is to wrap your data in div and to give that div a controller so it would be the parent of all of your sub controller. That way you can initiate your data in the parent controller and all your sub controller could have access to it
Your HTML:
<html ng-app>
<head>
....
</head>
<body>
<div ng-contoller="ParentController">
<div ng-contoller="subController1"> </div>
<div ng-contoller="subController2"> </div>
</div>
</body>
</html>
ParentController.js
myApp.controller('ParentController', function ($scopeScope) {
$rootScope.dicts.user = {
userTypes : { ... },
maxAge : ...,
minAge : ... ,
...
};
}
});

How to render a directive only after data is loaded from tsv in angular js

I want to integrate d3.js and angularjs. I have to draw a ling graph. Data is loaded from tsv file.
I am having problem in rending the graph and error is that data has not yet been loaded and graph is rendered. I want to that when data is loaded in scope variable, graph should be rendered else not. Please help.
Here is code of controller
phonecatControllers.controller('MainCtrl', ['$scope',
function($scope) {
d3.tsv("sample.tsv", function(error, data) {
$scope.d3Data = data;
});
}]);
And here is directive code
directives.directive('d3Bar', [ function() {
return {
restrict : 'E',
scope : {
data : '='
},
link : function(scope, element) {
scope.$watch('data', function(newData, oldData) {
drawLine(newData);
}, true);
}
}
}
and html is
<body ng-controller='MainCtrl'>
<d3-bar data='d3Data'></d3-bar>
</body>
Use ng-if with your condition that data is available or not, to resolve this problem.
HTML:
<body ng-controller='MainCtrl'>
<div ng-if="d3Data">
<d3-bar data='d3Data'></d3-bar>
<div>
</body>
So your directive will load only when d3Data has any data.
put an if control in your watch to check if your data item is ready to go or not
directives.directive('d3Bar', [function() {
return {
restrict : 'E',
scope : {
data : '='
},
link : function(scope, element) {
scope.$watch('data', function(newData, oldData) {
if(newData){
drawLine(newData);
}
}, true);
}
}
}])

Passing method through a directive to a parent directive

edit: Modified the code as per stevuu's suggestion as well as added a plunkr to here
I'm currently attempting to have a child directive call a method(resolve) through another directive all the way up to a parent directive but I'm having difficulties identifying the problem with my approach.
The problem right now seems to be that although resolve() does get called as expected on click, selected remains undefined.
the html:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Angular: directive using & - jsFiddle demo</title>
<script type='text/javascript' src='//code.jquery.com/jquery-1.9.1.js'></script>
<link rel="stylesheet" type="text/css" href="/css/normalize.css">
<link rel="stylesheet" type="text/css" href="/css/result-light.css">
<script type='text/javascript' src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<style type='text/css'>
</style>
</head>
<body ng-app="myApp">
<div grand-parent>
<span>selected: {{text}}</span>
<div parent resolve="resolve"></div>
</div>
</body>
</html>
And the js:
var myApp = angular.module('myApp',[]);
myApp.directive('grandParent', function() {
return {
scope:{
resolve: "&"
},
link: function($scope, $element) {
$scope.resolve = function(selected){
$scope.text = selected
}
}
};
});
myApp.directive('parent', function(){
return{
scope: {
resolve: "&"
},
template: "<div child resolve='resolve'></div>"
};
});
myApp.directive('child', function() {
return {
scope: {
resolve: "&"
},
template: "<div>Click me!</div>",
link: function($scope, $element) {
$element.on("click", function(){
$scope.$apply(function(){
$scope.resolve({selected: "Yahoo!!"});
});
});
}
};
});
resolve: "&" is a mapping. So here:
myApp.directive('grandParent', function() {
return {
scope:{
resolve: "&"
},
link: function($scope, $element) {
$scope.resolve = function(selected){
$scope.text = selected
}
}
};
});
you are trying to map "resolve" to ... nothing, because "grandParent" doesn't have any attr named "resolve".
If you want to share some staff betweens directives you should do something like that:
view
<div data-grand-parent resolve="resolved()">
<div data-parent ></div>
</div>
Directives
var app = angular.module('test');
app.directive('grandParent', function() {
return {
scope : {
resolve : "&" // in view we defined resolve attr
// with "resolve()" value, so now resolve=resolved()
// in grandParent scope too.
},
controller: function($scope){
this.getResolve = function(){
return $scope.resolve;
};
}
};
});
app.directive('parent', function() {
return {
require: "^grandParent",
link: function(scope, element, attr, grandParentCtrl){
grandParentCtrl.getResolve()();
},
template : ""
};
});
controller
angular.module('desktop')
.controller('MainCtrl', function ($scope, mocks) {
$scope.resolved = function(){
console.log("calling $scope.resolved() ...");
};
});
output
calling $scope.resolved() ...
So, how does it work?
We defined resolved function in our controller, then we sign this function to attr "resolve" in grandParent directive. Thx to resolve : "&" we could mapped that resolved() function to "resolve" property in grandParent scope. At the end we inject grandParent to other directives. That's all.
I recommend you to read angularJs by Brad Green, Shyam Seshadri. it's not the best book but could be worse and it's free. You can find very good tutorial too on http://www.egghead.io/
ps. Sorry for my english ;/

Categories