angularJS can't add html dynamically - javascript

i got an issue that I'm trying to solve but it seems I can't find a proper solution to achieve what I'm trying to do.
I'l try to explain my problem as best as I can: I need to create some tabs heading dynamically depending on the result of an AJAX call, so I don't know in advance how many tabs there will be, I'll post my HTML markup, my controller and what is the result I have in the DOM. I hope I'll be able to explain myself clear enough.
PART OF THE HTML MARKUP
<div id="tabPlacer">
</div>
PART OF ANGULARJS CONTROLLER
$http({method: 'GET', url: 'getNews.json'}).
success(function(data, status, headers, config) {
$scope.news = data;
createBase();
var contFirst=0;
var contSecond=0;
for (i=0; i< $scope.news.news[0].allNews.length; i++){
$scope.bodynews[i] = $scope.news.news[0].allNews[i].bodyNews;
if(i%2==0)
{
$scope.bodynewsR[contFirst] = $scope.news.news[0].allNews[i].bodyNews;
contFirst++;
}
else{
$scope.bodynewsL[contSecond] = $scope.news.news[0].allNews[i].bodyNews;
contSecond++;
}
}
$scope.noOfPages =Math.floor($scope.news.news[0].allNews.length / ($scope.itemsPerCol*2));
$scope.$watch('currentPage', function() {
begin = (($scope.currentPage - 1) * $scope.itemsPerCol);
end = begin + $scope.itemsPerCol;
$scope.pagedL = {
bodynewsL: $scope.bodynewsL.slice(begin, end)
}
$scope.pagedR = {
bodynewsR: $scope.bodynewsR.slice(begin, end)
}
});
}).
error(function(data, status, headers, config) {
});
function createBase() {
for (var i = 0; i < $scope.news.news[0].posizioni.length; i++) {
// $scope.tabsName[i] = $scope.news.news[0].posizioni[i][i];
$scope.baseString += "<tab heading='" + $scope.news.news[0].posizioni[i][i] + "' ng-controller='MainController'><div class='col-xs-12 col-sm-6 col-md-6' id='colonaDx"+ $scope.news.news[0].posizioni[i][i] +"'></div><div class='col-xs-12 col-sm-6 col-md-6' id='colonaDx"+ $scope.news.news[0].posizioni[i][i] +"'></div><div id='paginaz"+ $scope.news.news[0].posizioni[i][i] +"'></div></tab>";
}
$scope.baseString += "</tabset>";
$("#tabPlacer").html($scope.baseString);
}
});
HTML CREATED IN DOM
<div id="tabPlacer">
<tabset panel-tabs="true" panel-class="panel-grape" data-heading="OTHER NEWS">
<tab heading="allNews" ng-controller="MainController">
<div class="col-xs-12 col-sm-6 col-md-6" id="colonaDxallNews">
</div>
<div class="col-xs-12 col-sm-6 col-md-6" id="colonaDxallNews">
</div>
<div id="paginazallNews">
</div>
</tab>
<tab heading="SecondTab" ng-controller="MainController">
<div class="col-xs-12 col-sm-6 col-md-6" id="colonaDxSecondTab">
</div><div class="col-xs-12 col-sm-6 col-md-6" id="colonaDxSecondTab">
</div>
<div id="paginazSecondTab">
</div>
</tab>
</tabset>
</div>
So far the problem is that nothing is visualized on the page but if I manually define in the html markup the tabset structure I can see them..any ideas? Thanks very very much in advance.

As Cetia said, you don't want to "insert" html into your template in angular. Instead, your situation probably needs ng-repeat, which is one of the very basic angular directives you should learn to use.
Basically, you'll want to store the fetched data in a $scope variable, and then use that variable with ng-repeat to display things based on your data. A basic example here:
Controller:
$scope.things = [
{
name: "Thing1",
description: "I'm the first thing!"
},
{
name: "Thing2",
description: "I'm the second thing!"
}
]
HTML:
<div ng-repeat="thing in things">
<tr>
<td>{{thing.name}}:</td>
<td>{{thing.description}}</td>
</tr>
</div>
See fiddle here: http://jsfiddle.net/m8qLdhth/1/

Related

Appending ng-repeat into HTML element dynamically

I trying to append ng-repeat into my HTML div on load.
It recognizes the ng-repeat, however the index value isn't displaying as it should
HTML
<div id="myInnerCarousel"></div>
controller:
var myCarousel = document.getElementById('myInnerCarousel');
var newItem = document.createElement("div");
newItem.setAttribute("class","item");
var newData = document.createElement("div");
newData.setAttribute("class", "col-xs-6 col-sm-6 col-md-3 col-lg-3");
newData.setAttribute("ng-repeat", "mat in " + arr);
//Create individual values seperately
var newMaterialName = document.createElement("h4");
var nameNode = document.createTextNode("{{mat.Name}}");
newMaterialName.appendChild(nameNode);
//Append everything together
newData.appendChild(newMaterialName);
newItem.appendChild(newData);
myCarousel.appendChild(newItem);
However the result is this:
https://gyazo.com/00b76c6b910d4c6701059d404783f720
It got the right idea of displaying my array, however angular isn't displaying h4 right.
EDIT: imtrying to achieve this in my html:
<div ng-controller="myController">
<div class="item">
<div class="col-xs-6 col-sm-6 col-md-3 col-lg-3" ng-repeat="mat in array1">
<h4>{{mat.Name}}</h4>
</div>
</div>
<div class="item">
<div class="col-xs-6 col-sm-6 col-md-3 col-lg-3" ng-repeat="mat in array2">
<h4>{{mat.Name}}</h4>
</div>
</div>
Lets simply state that this is not the way to do it.
A better and more angular way would be (assuming your apps name is app).
HTML
<div ng-controller="myController">
<div class="item">
<div class="col-xs-6 col-sm-6 col-md-3 col-lg-3" ng-repeat="mat in array">
<h4>{{mat.Name}}</h4>
</div>
</div>
</div>
Angular Controller
angular.module('app').controller('myController', function($scope) {
$scope.array = [{Name: 'abc'}, {Name: 'def'}]; // or whatever your data looks like.
});
Dynamically added component should be compiled using angular. you can use $compile function of angular. Below is working code.
Jsfiddle
function TodoCtrl($scope, $compile) {
$scope.arr = [{Name: "Dynamically Added cowermponent"}];
var myCarousel = document.getElementById('myInnerCarousel');
var newItem = document.createElement("div");
newItem.setAttribute("class","item");
var newData = document.createElement("div");
newData.setAttribute("class", "col-xs-6 col-sm-6 col-md-3 col-lg-3");
newData.setAttribute("ng-repeat", "mat in arr");
//Create individual values seperately
var newMaterialName = document.createElement("h4");
var nameNode = document.createTextNode("{{mat.Name}}");
newMaterialName.appendChild(nameNode);
//Append everything together
newData.appendChild(newMaterialName);
newItem.appendChild(newData);
myCarousel.appendChild(newItem);
$compile(newItem)($scope);
}

How to properly use the same AngularJS 1.5 component multiple times in a view?

I'm creating a set of widgets with AngularJS 1.5's new components. The problem is, when using the same widget multiple times, they somehow share their controller or scope. I thought one of the things about components was that their scope is completely isolated?
My main html template which hold the widgets:
<widget-list
title="Books"
class="col-xs-12 col-md-4">
</widget-list>
<widget-list
title="Movies"
class="col-xs-12 col-md-4">
</widget-list>
<widget-list
title="Albums"
class="col-xs-12 col-md-4">
</widget-list>
My widget template:
<div class="widget widget-list">
<div class="panel b-a">
<div class="panel-heading b-b b-light">
<h5>{{$widget.title}}</h5>
<div class="pull-right">
<button type="button" class="btn btn-default btn-sm" ng-click="$widget.doSomething()">
Do something
</button>
</div>
</div>
<div class="panel-content">
{{$widget.content || 'No content'}}
</div>
</div>
</div>
My widget component:
app.component('widgetList', {
templateUrl: 'template/widget/widget-list.html',
bindings: {
title : '#',
},
controllerAs: '$widget',
controller: function($timeout) {
$widget = this;
console.log('Title on init: ', $widget.title)
$timeout(function() {
console.log('Title after 3 seconds: ', $widget.title)
}, 3000)
$widget.doSomething = function() {
$widget.content = "Something";
}
}
});
When running my code, this is what my console looks like:
Title on init: Books
Title on init: Movies
Title on init: Albums
(3) Title after 3 seconds: Albums
Also after rendering, all three widgets display No content in their template. But, when clicking the doSomething() button in either one of the three widgets, only the content of the last widget updates to Something.
What is happening here? Why are my components not 'isolated'?
Looks like you have a global variable called $widget here, try this:
var $widget = this;
instead of
$widget = this;
It creates a mess since the $widget variable holds a reference to the controller that has been recently initialized, in this case to the controller of the third component.
The problem with your code is that you are declaring the $widget on window scope, that's why your controller prints the last value, bacause it was being overridden every time the controller was getting instantiated. Use a var $widget instead and your code will work fine.
The following snippet solves this issue:
angular.module('app', [])
.component('widgetList', {
templateUrl: 'template/widget/widget-list.html',
bindings: {
title: '#',
},
controllerAs: '$widget',
controller: WidgetListController
});
function WidgetListController($timeout) {
var $widget = this;
console.log('Title on init: ', $widget.title)
$timeout(function() {
console.log('Title after 3 seconds: ', $widget.title)
}, 3000)
$widget.doSomething = function() {
$widget.content = "Something";
}
}
angular.element(document).ready(function() {
angular.bootstrap(document, ['app']);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.1/angular.min.js"></script>
<widget-list title="Books" class="col-xs-12 col-md-4">
</widget-list>
<widget-list title="Movies" class="col-xs-12 col-md-4">
</widget-list>
<widget-list title="Albums" class="col-xs-12 col-md-4">
</widget-list>
<script type="text/ng-template" id="template/widget/widget-list.html">
<div class="widget widget-list">
<div class="panel b-a">
<div class="panel-heading b-b b-light">
<h5>{{$widget.title}}</h5>
<div class="pull-right">
<button type="button" class="btn btn-default btn-sm" ng-click="$widget.doSomething()">
Do something
</button>
</div>
</div>
<div class="panel-content">
{{$widget.content || 'No content'}}
</div>
</div>
</div>
</script>

Converting an ng-repeat into an infinite scroll in Angular.js

Currently in Angular I am making an http request to a JSON news feed. I'm initiating the http request on load with data-ng-init="init()". However I would like the show just one article and have an infinite scroll. In other words, I would like the other articles to load, once a user gets to the bottom of each article.
This is my current index.html
<section ng-app="sports" ng-controller="main" data-ng-init="init()">
<div ng-repeat="article in articles | limitTo:2">
<div class="col-lg-8 col-lg-offset-2 col-md-8 col-md-offset-2 col-sm-8 col-sm-offset-2">
<!-- <h3>{{ article.created }}</h3> -->
<h1>{{ article.title }}</h1>
<img class="images afkl-lazy-image" src="{{ article.thumbnail }}">
<p ng-bind-html=" article.body | html "></p>
</div>
</div>
</section>
And here is my angularApp.js:
app.controller('main', function($scope, $http){
$scope.init = function(){
$http.get("/data")
.success(function(response) {
$scope.articles = [];
$.each(response.node,function(index,item){
$scope.articles.push(item);
})
$scope.articles.reverse();
});
}}
);
I will be around to answer any questions. Thank you in advance!!
You just need to add a scroll listener to the page, catch the event and load more data one "page" at a time:
$scope.currentPage = 0;
$scope.loadPage = function(pageNum){
$http.get("/data?page=" + pageNum)
.success(function(response) {
$scope.currentPage = pageNum;
$.each(response.node,function(index,item){
$scope.articles.push(item);
})
});
}}
};
window.onscroll = function(ev) {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
$scope.loadPage($scope.currentPage + 1);
}
};
You should really inject $window service and use it:
...controller('mycontroller', function($scope, $window) {
angular.element($window).bind("scroll", function() {
///check to see if you're at the bottom and call method to load data as above
});
});

Using ngInfiniteScroll to output JSON from a remote source

I have made a small app using AngularJS 1.4, that outputs JSON data from a remote source.
Here is the JSON data in question.
Here is my view - the link to ngInfiniteScroll comes in at the top of the container:
<body ng-app="cn" ng-controller="MainCtrl as main">
<div id="header" class="container-fluid">
<p>Camper News</p>
</div>
<div class="container">
<div infinite-scroll="feed.loadMore()" infinite-scroll-distance="3">
<div ng-repeat="newsItem in myData" class="news-row col-sm-12 col-xs-12">
<div class="col-sm-2 col-xs-12">
<div id="img-container">
<img ng-src={{newsItem.author.picture}} id="user-avatar" />
</div>
</div>
<div class="news-text col-sm-10 col-xs-12">
<a href={{newsItem.link}}><em>{{newsItem.headline}}</em></a>
</div>
<div class="col-sm-10 col-xs-12" id="link-description">
{{newsItem.metaDescription}}
</div>
<div class="col-sm-12 col-xs-12" id="bottom-text">
<div class="col-sm-4 col-xs-12" id="author">
<a href='http://www.freecodecamp.com/{{newsItem.author.username}}'>{{newsItem.author.username}}</a>
</div>
<div class="col-sm-4 col-xs-12" id="likes">
<i class="fa fa-thumbs-o-up"></i> {{newsItem.upVotes.length}}
</div>
<div class="col-sm-4 col-xs-12" id="timestamp">
<span am-time-ago={{newsItem.timePosted}} | amFromUnix></span>
</div>
</div>
</div>
</div>
</div>
</body>
I am attempting to stagger the loading and outputting of the data using ngInfiniteScroll. I'm just not sure how it would work:
app.controller('MainCtrl', function($scope, Feed) {
$scope.feed = new Feed();
});
app.factory('Feed', function($http) {
var Feed = function() {
this.items = [];
this.after = '';
};
Feed.prototype.loadMore = function() {
var url = "http://www.freecodecamp.com/news/hot";
$http.get(url).success(function(data) {
var items = data;
// Insert code to append to the view, as the user scrolls
}.bind(this));
};
return Feed;
});
Here is a link to the program on Codepen. The code that relates to ngInfiniteScroll (controller and factory) is currently commented out in favour of a working version, but this of course loads all links at once.
What do I need to insert in my factory in order to make the JSON load gradually?
From what I can tell, by looking at FreeCodeCamps' story routes, there is no way to query for specific ranges of the "hot news" stories.
It looks to just return json with a fixed 100 story limit.
function hotJSON(req, res, next) {
var query = {
order: 'timePosted DESC',
limit: 1000
};
findStory(query).subscribe(
function(stories) {
var sliceVal = stories.length >= 100 ? 100 : stories.length;
var data = stories.sort(sortByRank).slice(0, sliceVal);
res.json(data);
},
next
);
}
You can use infinite scroll for the stories it does return to display the local data you retrieve from the request in chunks as you scroll.

How do I refresh my ng-repeat?

I have a controller (called "catalogueController") that manages my search box and my search page. I have the controller initially set the page to automatically call the search function defined in "catalogueController" when the app loads to pre-load my array of items (called Inventory) to be repeated via ng-repeat in the page.
The process runs like this:
1. I submit the search form.
2. "catalogueController" will send the search term to my factory (called "Search").
3. "Search" will have a function which will make a server call to query my database for that particular search.
4. The database will send the results of the search to the "Search" factory.
5. The "Search" factory will send the results to the "catalogueController" controller.
6. "catalogueController" will update the $scope.Inventory to be equal to the new result that I was received.
My problem is that ng-repeat does not refresh itself to display my new and updated $scope.Inventory array. $scope.Inventory definitely is updated (I have made sure of this through various console logs).
I have also tried to use $scope.$apply(). It did not work for me.
Thank you in advance for your help!
Here is my code:
HTML Template
<form role="search" class="navbar-form navbar-left" ng-controller="catalogueController" ng-submit="search(search_term)">
<div class="form-group">
<input type="text" placeholder="Search" class="form-control" ng-model="search_term">
</div>
<button type="submit" class="btn btn-default">Search</button>
</form>
<main ng-view></main>
catalogue.html partial
<div id="main" class="margin-top-50 clearfix container">
<div ng-repeat="items in inventory" class="row-fluid">
<div class="col-sm-6 col-md-3">
<div class="thumbnail"><img src="image.jpg" alt="..." class="col-md-12">
<div class="caption">
<h3>{{ items.itemName }}</h3>
<p>{{ items.description }}</p>
<p>Buy <a href="#" role="button" class="btn btn-default">More Info</a></p>
</div>
</div>
</div>
</div>
"app.js" Angular App
var myApp = angular.module('qcbApp', ['ngRoute', 'ngCookies', 'appControllers']);
myApp.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/login', {
templateUrl: 'html/partials/login.html',
controller: 'registrationController'
}).
when('/sign-up', {
templateUrl: 'html/partials/sign-up.html',
controller: 'registrationController'
}).
when('/catalogue', {
templateUrl: 'html/partials/catalogue.html',
controller: 'catalogueController'
}).
when('/', {
templateUrl: 'html/partials/qcbhome.html'
}).
otherwise({
redirectTo: '/'
});
}]);
"catalogueController" Controller
myApp.controller('catalogueController', ['$scope', 'Search', function($scope, Search) {
var time = 0;
var searchCatalogue = function(search) {
$scope.inventory = null;
console.log("Controller -- "+search);
Search.searchCatalogue(search)
.then(function(results) {
console.log(results);
$scope.inventory = results;
});
};
if(time == 0)
{
searchCatalogue('');
time++;
}
$scope.search = function(term) {
searchCatalogue(term);
}
}]);
"Search" Factory
myApp.factory('Search', ['$http', '$q', function($http, $q) {
function searchCatalogue(term) {
var deferred = $q.defer();
console.log("Factory -- "+term);
$http.post('/catalogue_data', {term: term}, {headers: {'Content-Type': 'application/json'}})
.success(function(result) {
console.log(result[0].SKU);
deferred.resolve(result);
console.log("Factory results -- "+result);
});
return deferred.promise;
}
return {
searchCatalogue: searchCatalogue
}; //return
}]);
I think the problem is the ng-repeat can not access the inventory in scope. You have to create a div which contains both the form and the ng-repeat.
The html should be:
<div ng-controller="catalogueController">
<!-- Move the controller from the form to parent div -->
<form role="search" class="navbar-form navbar-left" ng-submit="search(search_term)">
<div class="form-group">
<input type="text" placeholder="Search" class="form-control" ng-model="search_term">
</div>
<button type="submit" class="btn btn-default">Search</button>
</form>
<div id="main" class="margin-top-50 clearfix container">
<div ng-repeat="items in inventory" class="row-fluid">
<div class="col-sm-6 col-md-3">
<div class="thumbnail"><img src="image.jpg" alt="..." class="col-md-12">
<div class="caption">
<h3>{{ items.itemName }}</h3>
<p>{{ items.description }}</p>
<p>Buy <a href="#" role="button" class="btn btn-default">More Info</a></p>
</div>
</div>
</div>
</div>
</div>
I've seen the situation a few times where when you are updating a property directly on the $scope object there are interesting problems around databinding to that value (such as inventory). However if you databind to an object property of an object then the databinding works as expected. So for example use a property on $scope. I believe this is a copy by value vs copy by reference issue.
Update all your inventory references as follows
$scope.data.inventory = result;
Also don't forget to update your inventory reference in the html template:
<div ng-repeat="items in data.inventory" class="row-fluid">
Update: I made this plunk to figure it out - http://plnkr.co/edit/0ZLagR?p=preview
I think the primary problem is you have the controller specified twice. I removed it from the form and it started working.

Categories