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

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!

Related

Highlight.js in AngularJS SPA doesn't work

I have an AngularJS SPA which loads articles into the view. Some articles have code examples and I want to use highlight.js to highlight it.
In my example below I have simulated a get request, 'cause that's how I load my dynamic content in the actual app. The $scope.test is very similar to what my actual app could get returned; some regular HTML to print out which includes code examples.
My problem: it doesn't really seem to work.
Specifically, nothing gets highlighted. It seems to me like I am missing an init or something... Halp?
I've also tried <div hljs/> with the same (lack of) result. There are no console errors.
This answer provides a solution that uses ng-model in the template. However, I don't use ng-model anywhere.
EDIT: I've made some changes to my example code to further explain the problem.
Here's my app (simplified):
var app = angular.module('app', ['ngSanitize']);
app.controller('ctrl', ['$scope', '$http',
function($scope, $http) {
"use strict";
$http.get('/echo/html').then(function successCallback(response) {
$scope.title = 'Some Title';
$scope.metaStuff = 'Written by Awesome MacFluffykins';
$scope.articleBody = '<p>Here\'s an example of a simple SQL SELECT:</p><pre><code class="sql" highlight>SELECT * FROM table WHERE user = \'1\'</code></pre>';
}, function errorCallback(response) {
console.log("Error: %d %s", response.code, response.message);
});
}
]);
Here's my HTML:
<div ng-app="app" ng-controller="ctrl">
<h2>{{ title }}</h2>
<p><small>{{ metaStuff }}</small></p>
<div ng-bind-html="articleBody"></div>
</div>
And finally a jsFiddle.
In my opinion it's best to use a directive for DOM manipulations like this. Pass your sourcecode through ng-model (you could also use another attribute) and run HLJS in the directive. Since you're using a asynchronous method to supply the value to your scope, you'll need to use $watch to catch the value and then run HLJS:
HTML:
<div highlight ng-model="test"></div>
Directive:
.directive('highlight', [
function () {
return {
replace: false,
scope: {
'ngModel': '='
},
link: function (scope, element, attributes) {
scope.$watch('ngModel', function (newVal, oldVal) {
if (newVal !== oldVal) {
element.html(scope.ngModel);
var items = element[0].querySelectorAll('code,pre');
angular.forEach(items, function (item) {
hljs.highlightBlock(item);
});
}
});
}
};
}
]);
Working JSFiddle: https://jsfiddle.net/1qy0j6qk/
Fiddle
https://jsfiddle.net/vg75ux6v/
var app = angular.module('app', ['hljs', 'ngSanitize']);
app.controller('ctrl', ['$scope', '$http',
function($scope, $http) {
"use strict";
$http.get('/echo/html').then(function successCallback(response) {
$scope.test = '<h2>Here\'s some code:</h2><pre><code hljs class="sql">SELECT * FROM table WHERE user = \'1\'</code></pre>';
}, function errorCallback(response) {
console.log("Error: %d %s", response.code, response.message);
});
}
]).directive('compile', ['$compile', function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.compile);
},
function(value) {
element.html(value);
$compile(element.contents())(scope);
}
);
};
}]);

angular js $scope.apply function not updating template

When I return from my service call I seem unable to update my view. Why does 'not broken' never get out putted to the console?
the services returns [{test: 'service workies'}]
app.controller('foo-controller', ['fooService','$scope', function (fooService,$scope) {
var ctrl = this;
ctrl.Results = [{ test: 'no workies' }];
ctrl.Search = function () {
fooService.GetFoos().then(function (result) {
console.log('test');
console.log(ctrl.Results);
ctrl.Results = result;
console.log(ctrl.Results);
$scope.$apply(function () {
console.log('not broken');//never fires!!
ctrl.Results = [{test : 'workies' }]
});
});
};
return ctrl;
}]);
app.directive('fooLogo', function () {
return {
restrict: 'E',
templateUrl: './App/Templates/foo.html',
controller: 'foo-controller',
controllerAs: 'f'
};
});
edit foo service
.service('fooService', ['$http', function ($http) {
return $http.get("https://www.googleapis.com/books/v1/volumes?q=harry+potter").then(
function(result){ return [{ test: 'service workies'}]},
function(error) { return [{test: 'service call no workies'}] );
I see a few issues in your code. I don't see anywhere inside fooService where GetFoos() is declared, so that's one issue. Try the following:
app.controller('MainCtrl', ['$scope', 'BookQueryService',
function($scope, BookQueryService) {
$scope.search = function() {
BookQueryService.getBooks().then(function(data) {
$scope.books = data.data.items;
});
};
// call immediately for the sake of this example
$scope.search();
}
]);
app.service('BookQueryService', ['$http',
function($http) {
var service = {};
service.getBooks = function() {
return $http.get("https://www.googleapis.com/books/v1/volumes?q=harry+potter");
};
return service;
}
]);
app.directive('myBookList', function() {
return {
restrict: 'E',
templateUrl: 'BookList.html',
controller: 'MainCtrl'
}
});
With the following html:
<body>
<my-book-list></my-book-list>
</body>
And the following directive template:
<div>
<ul>
<li data-ng-repeat="book in books">
{{book.volumeInfo.title}}
</li>
</ul>
</div>
Here's a plunker with a working example:
http://plnkr.co/edit/KJPUWj0ghDi1tyojHNzI?p=preview
Is anything inside the fooService.GetFoos().then(function(result){...}) being run? If the code you posted is all there is for fooService, then it looks like there is no .GetFoos method & therefore nothing inside the following .then would get run.
Try adding a .error after the original .then that is chained onto fooService.GetFoos:
fooService.GetFoos().then(function (result) {
// your code
}).error(function (data, status){
console.log("Error!\t", status);
};
This will help you figure out what exactly is going on. Whenever your using any sort of promise, make sure you have a .catch or .error — they can save you a lot of trouble when debugging. Check out angular's $http documentation for more details.
Additionally, it looks like the original call to $scope.$apply() is unnecessary. You would only use that if you want to run a function outside of angular, or if you manually want to trigger the digest cycle (if that were the case calling $scope.$digest() explicitly would be much more appropriate than $scope.$apply.
Check out this blog post about when to use $scope.$apply and the $scope.$apply documentation page for more info

Angular: using transclude with ng-bind-html

I have the following angular setup:
var app = angular.module('app', []);
app.filter('unsafe', function($sce) {
return $sce.trustAsHtml;
});
app.controller('SearchController', ['$scope', '$http', function($scope, $http) {
$scope.searchString = '';
$scope.searchType = 'Artist';
$scope.updateHtml = function() {
$http.get('/search', {
params: {
searchString: $scope.searchString,
searchType: $scope.searchType
}
}).success(function(data) {
$scope.html = data;
}).error(function(err){
$scope.html = err;
});
};
$scope.updateHtml();
}]);
app.directive('searchDirective', function() {
return {
restrict: 'A',
transclude: true,
template: '<div ng-bind-html="html | unsafe" ng-transclude></div>'
};
});
It pulls raw html markup through ajax in the controller and stores it in #scope.html. In the directive, this html is inserted into the DOM through ng-bind-html.
The html (jade) looks as follows:
#search(ng-controller="SearchController" search-directive)
It basically works. But inside this html that is included, i have some transclusive content, like {{searchType}} that i want to be resolved. Unfortunatly, that is not the case, it shows "{{searchType}}" in the browser. What can i change to make the transclusion work?
I read about $compile and $transclude, but i don't know how to use it or if it can help me solve my issue. thx!
with the help of Patrick, i was able to solve it. i changed my controller to
app.controller('SearchController', ['$scope', '$http', '$interpolate',
function($scope, $http, $interpolate) {
$scope.searchString = '';
$scope.searchType = 'Artist';
$scope.updateHtml = function() {
$http.get('/search', {
params: {
searchString: $scope.searchString,
searchType: $scope.searchType
}
}).success(function(data) {
$scope.html = $interpolate(data)($scope); // <<-- difference here
}).error(function(err){
$scope.html = err;
});
};
$scope.updateHtml();
}]);
and now my html is interpolated based on the passed-in scope. thank you!
edit:
$interpolate is only for rendering the DOM and parsing it through the scope. it simply returns plain html. if you need to actually retrieve a full working html template, with angular code in it, use $compile. i found this answer extremely helpful in sorting out the differences between $interpolate, $compile and $parse.

Angular- detecting a change in scope

I have a service that grabs JSON data for me and hands it off to a controller:
Service snippet:
...
getP2PKeywordData: function(global_m, global_y) {
// Return the promise
return $http({
url: base_url + 'get/P2P/kwords/',
method: "GET",
// Set the proper parameters
params: {
year: global_y,
month: global_m
}
})
.then(function(result) {
// Resolve the promise as the data
return result.data;
},
function(data) {
// Error handling
});
}
...
The controller successfully grabs the data, which I have tested with a console.log underneath the $scope.d3Data = data; line.
Controller snippet:
myApp.controller('DownloadsCloudCtrl', ['$scope',
'$rootScope',
'requestService',
'$cookieStore',
function($scope, $rootScope, requestService, $cookieStore) {
$rootScope.$on('updateDashboard', function(event, month, year) {
updateDashboard(month, year);
});
var updateDashboard = function(month, year) {
requestService.getP2PKeywordData(month, year).then(function(data) {
$scope.d3Data = data;
});
};
updateDashboard($cookieStore.get('month'), $cookieStore.get('year'));
}]);
The controller is hooked up to a d3-cloud directive (d3 word cloud) that actually appends the proper svg elements and draws the word cloud with the data. However, for some reason the controller above isn't passing the $scope.d3Data to the directive.
This is confusing because when I hardcode in an array of data into the controller, something like this...
$scope.d3Data = [
{
'kword': 'a',
'count': 20,
},{
'kword': 'b',
'count': 10,
...
... it connects to the directive perfectly!
Directive snippet:
myApp.directive('d3Cloud', ['$window',
'd3Service',
'd3Cloud',
function($window,
d3Service,
d3Cloud) {
return {
restrict: 'EA',
scope: {
data: '=',
label: '#'
},
link: function(scope, element, attrs) {
d3Service.d3().then(function(d3) {
window.onresize = function() {
scope.$apply();
};
scope.$watch(function() {
return angular.element($window)[0].innerWidth;
}, function() {
scope.render(scope.data);
});
scope.render = function(data) {
HTML snippet:
<div class="col-md-6">
<div class="module">
<div class="inner-module" ng-controller="DownloadsCloudCtrl">
<div class="module-graph">
<d3-cloud data="d3Data"></d3-cloud>
</div>
</div>
</div>
</div>
What have I tried:
I tried to add a manual $scope.$apply() after the $scope.d3Data = data; line in the controller. This, oddly, worked the first time I did it, but on every page refresh after that I got a "$digest already in progress" error (which was to be expected...).
In order to fix the $digest error, I tried encapsulating my $apply function in a $timeout code chunk, and even the dreaded $$phase conditional. Both of these solutions fixed the console error, but failed to solve the original problem of passing the data from the controller to the directive.
TL;DR: I'm fairly lost. Ideas on where to troubleshoot next?
It seems you are treating the response as a promise twice. So once in the service:
.then(function(result) {
// Resolve the promise as the data
return result.data;
},
And in the controller you resolve the promise again:
requestService.getP2PKeywordData(month, year).then(function(data) {
$scope.d3Data = data;
});
This can work because (from my understanding) Angular sometimes resolves promises automatically when binding to the scope.
It would be better to just handle the promise in the controller only. So the service becomes:
getP2PKeywordData: function(global_m, global_y) {
// Return the promise
return $http({
url: base_url + 'get/P2P/kwords/',
method: "GET",
// Set the proper parameters
params: {
year: global_y,
month: global_m
}
});
}
UPDATE:
Try to initialize the d3Data scope property to an empty collection, and then push the response data into it. For example:
myApp.controller('DownloadsCloudCtrl', ['$scope',
'$rootScope',
'requestService',
'$cookieStore',
function($scope, $rootScope, requestService, $cookieStore) {
//added
$scope.d3Data = [];
$rootScope.$on('updateDashboard', function(event, month, year) {
updateDashboard(month, year);
});
var updateDashboard = function(month, year) {
requestService.getP2PKeywordData(month, year).then(function(data) {
//then
angular.forEach(data, function(thing) {
$scope.d3Data.push(thing);
)};
});
};
updateDashboard($cookieStore.get('month'), $cookieStore.get('year'));
}]);

How to pass a collection to a directive in angular.js?

Please forgive my lack of understanding.
I pass the name of a collection to my directive:
<ul tag-it tag-src="preview_data.preview.extract.keywords"><li>Tag 1</li><li>Tag 2</li></ul>
The directive is defined:
app.directive('tagIt', function (){
return {
restrict: 'A',
link: function(scope,elem, attr) {
elem.tagit();
console.log(attr.tagSrc); //the name of my collection, but how do I access it?
}
}
});
How do I access my collection from the directive and make sure my directive is called when the collection is populated? Here is how preview_data.preview.extract.keywords gets populated.
app.config(function ($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true);
console.log('config');
$routeProvider.when("/", {
templateUrl: "/templates/addItem.html",
controller: "AddItemController",
resolve: {
loadData: addItemCtrl.loadData
}
});
});
var addItemCtrl=app.controller("AddItemController", function ($scope, $route, $sce, Preview) {
var title = decodeURIComponent($route.current.params.title);
var ua = decodeURIComponent($route.current.params.ua);
var uri = decodeURIComponent($route.current.params.uri);
$scope.preview_data = {
uri: uri,
title: title,
ua: ua
}
//pass parameters to web preview API
Preview.get(uri, ua, title).then(function (data) {
$scope.preview_data.preview = data;
if (data.embed.html) {
$scope.preview_data.preview.embed.html = $sce.trustAsHtml(data.embed.html);
}
}, function (data) {
alert('Error: no data returned')
});
});
You need to set the variable in the directive scope and set the template to iterate between the tags:
template : '<li data-ng-repeat="tag in tagSrc">{{tag.name}}</li>',
scope : {
tagSrc : '='
},
And will became this:
app.directive('tagIt', function (){
return {
restrict: 'A',
template : '<li data-ng-repeat="tag in tagSrc">{{tag.name}}</li>',
scope : {
tagSrc : '='
},
link: function(scope,elem, attr) {
console.log(attr.tagSrc);
}
}
});
the '=' attribute will tells to angular to use a tw way binding with the array passed in the directive declaration in the HTML.
Here is a plunker with a working example.
And here is a good arcticle explaning the directive's attributes and life cycle.
I hope it helps.
[EDIT]
If you want just iterate the array, without creating some different behavior in the list items, you can just simply use the ng-repeat directive:
<ul>
<li data-ng-repeat="tag in tags">{{tag.name}}</li>
<ul>

Categories