AngularJS, ng-repeat doesn't repeat - javascript

I'm new in AngularJs and I'm facing a problem with ng-repeat. Basically, I want to get the comments relative to an article from my database with ajax, then to display them with ng-repeat. I then have an array in which I push my comments. My problem is that when I manually create a comment in my array, it works well, but if I push automatically this comment from the callback of my ajax function, the array is updated, but not my view.
View in html :
var articleApp = angular.module('articleApp', []);
articleApp.controller('CommentsController', function CommentsController($scope) {
$scope.comments = [];
// This push works well, my view is updated
$scope.comments.push({
content: "Hello world !",
date: "2 minutes ago",
id: 29,
author: {
pseudo: "Sean"
}
});
// When I push with this function, my array is updated, but not the view
$scope.addComment = function(comment) {
$scope.comments.push({
content: comment.comment,
id: comment.id,
date: comment.date_post,
author: {
id: comment.author.id,
pseudo: comment.author.pseudo
}
});
};
var articleID = document.getElementById('articleID').textContent;
// getComments is defined elsewhere, and returns the 20 first comments
getComments(20, articleID, 0, function(comments) {
for(var i = 0; i < comments.length; i++) {
$scope.addComment(comments[i]);
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<section id="commentsSection" class="bottom_apps" ng-controller="CommentsController as comments">
<article id = "{{comment.id}}" class="comment_container" ng-repeat="comment in comments">
<div class="comment_header">
<span class="comment_author">{{comment.author.pseudo}}</span>
<span class="comment_date">{{comment.date}}</span>
</div>
<div class="comment_content">
{{comment.content}}
</div>
</article>
</section>
I double-checked, triple-checked all my code, but I can't see where I did a mistake.

Looks like your getComments works asynchronously, since you are passing a callback function which has the comments as a parameter.
Therefore, even though you update your comments inside that callback, AngularJS does not seem to "notice it", right?
This is because you have to tell AngularJS explicitly to run a new digest cycle.
In short, just add $scope.$apply() to the end of your callback:
getComments(20, articleID, 0, function(comments) {
for(var i = 0; i < comments.length; i++) {
$scope.addComment(comments[i]);
}
$scope.$apply();
});
To learn more about this, search for "AngularJS digest cycle". In short, the thing is: AngularJS updates everything in what are called digest cycles. If a digest cycle does not happen, AngularJS will not "notice" the changes. When things runs synchronously, AngularJS automatically runs digest cycles. But for many asynchronous things, AngularJS can't figure it out automatically, so you have to tell AngularJS explicitly to perform a digest cycle.

You can try doing something like below code, Also please check this plunker link for your given example scenario with some dummy action.
Controller:
$scope.comments = [];
$scope.comment={};
// This push works well, my view is updated
$scope.comments.push({
content: "Hello world !",
date: "2 minutes ago",
id: 29,
author: {
pseudo: "Sean"
}
});
$scope.comments.push({
content: "Hello world 2!",
date: "5 minutes ago",
id: 30,
author: {
pseudo: "Jack"
}
});
// When I push with this function, my array is updated, but not the view
$scope.addComment = function() {
$scope.comments.push({
content: $scope.comment.comment,
id: $scope.comments.length+1,
date: new Date(),
author: {
id: $scope.comments.length+1,
pseudo: $scope.comment.comment
}
});
console.log($scope.comments);
};
Template:
<section id="commentsSection" class="bottom_apps" ng-controller="CommentsController">
<input type="text" ng-model="comment.comment"/>
<button type="button" ng-click="addComment()">Add Comment</button><br/>
<article id = "{{comment.id}}" class="comment_container" ng-repeat="comment in comments">
<div class="comment_header">
<span class="comment_author">{{comment.author.pseudo}}</span>
<span class="comment_date">{{comment.date}}</span>
</div>
<div class="comment_content">
{{comment.content}}
</div>
</article>
</section>

Related

Add class in angular on ng-repeat when item is contained on array

On an Angular controller I have an array of events:
vm.events = [
{ date: "18-02-2016", name: "event A" },
{ date: "18-02-2016", name: "event C" },
{ date: "24-02-2016", name: "event D" }
];
And I have an array of all days in current month:
var days = [
{ date: "01-02-2016" },
{ date: "02-02-2016" },
{ date: "03-02-2016" }
...
];
On my angular view I have the following:
<span
class="day"
ng-class="{ 'today': isToday(day) }"
ng-repeat="day in vm.days">{{getDay(day)}}</span>
How can I add a class to the span when a day has events, e.g., when the day has at least one record in vm.events?
Here is your solution:
<div ng-repeat="day in days">
<span ng-class="{true:'today', false: 'day' } [isToday(day.date)]">
{{day.date}} : {{isToday(day.date)}}
</span>
</div>
Check the working plunker
You can use ngClass, the way to do is by adding your condition like:
ng-class="{true:'today'}[isToday(day)]"
The first thought might be to write some function that takes a date and then and it determines if that dates has any events by iterating over the events array.
That will result in a lot of unnecessary iteration on each digest cycle. Instead, you can manipulate the data before it's rendered in the ng-repeat. Modify the data such that each day has an array of events. Now you can just check if that day's events array contains any events.
Ideally, I would try and change this on the server, so that data came down in such a fashion. But there's no reason the client can't do it. Here's a simple implementation:
angular.forEach(vm.days, function(day) {
// this modifies the day objects in vm.days, if you didn't want to
// do that you could copy them and produce a separate array of days
// with events...
day.events = vm.findEventsFor(day))
});
vm.findEventsFor(day) {
var events = [];
angular.forEach(vm.events, function(event) {
if (event.date === day.date) {
events.push(event);
}
}
return events;
}
Your ng-class might look like this:
ng-class="{today: isToday(day), hasEvents: day.events.length > 0}"

Rendering collection data in a template using a reactive join with Meteor JS

I would like to output data from two collections using a reactive join into my template, then pair the users, post and comments through a common id.
So far, I can see with Mongo commands that the JSON data exist, but my template doesn't render any data. What am I doing wrong?
FYI, the meteorpad doesn't compile but the github repo will.
Repo:
https://github.com/djfrsn/frontend-interview-test-long/tree/master/ontra/ontra
Meteor Pad Example:
http://meteorpad.com/pad/SvwkNrv5grgv2uXxH/Copy%20of%20Leaderboard
There's so much wrong that it's hard to know where to start.
1) When you're loading the initial post and user data you're inserting the whole returned array as one element rather than inserting each element individually into your posts collection.
2) You're creating a publish subscription with the name "postsSet", but you're trying to subscribe to it with a different name.
3) You're not calling publishComposite correctly at all. You should be publishing the user required for each post as part of the children array.
4) The template needs updating based on the above
5) The username needs to be supplied via a helper.
6) You should really map the "id" attributes to Mongo's "_id" instead.
Here's come code which works. Note that you'll need to call meteor reset everytime you restart, otherwise you'll get duplicate id errors since you currently reimport the data every time.
Posts = new Mongo.Collection("Posts");
var groundPosts = new Ground.Collection(Posts);
Users = new Mongo.Collection("Users");
var groundUsers = new Ground.Collection(Users);
if (Meteor.isClient) {
Meteor.subscribe("postsSet");
console.log('POSTS DATA = ' + Posts.find().fetch());
console.log('USERS DATA = ' + Users.find().fetch());
Template.body.events({
"submit .ontra": function (event) {
// This function is called when the new task form is submitted
var text = event.target.text.value;
Posts.insert({
content: text,
date: new Date() // current time
});
// Clear Form
event.target.text.value = "";
// Prevent default form submit
return false
}
});
Template.body.helpers({
posts: function() {
return Posts.find();
},
});
Template.post.helpers({
username: function() {
return Users.findOne({_id: this.userId}).username;
}
});
}
Meteor.methods({
'fetchJSONData': function() {
var postsResponse = Meteor.http.call("GET","https://raw.githubusercontent.com/djfrsn/frontend-interview-test-long/master/codetest/data/posts.json");
var usersResponse = Meteor.http.call("GET","https://raw.githubusercontent.com/djfrsn/frontend-interview-test-long/master/codetest/data/users.json");
var postsData = JSON.parse(postsResponse.content);
var usersData = JSON.parse(usersResponse.content);
postsData.forEach(function (post) {
post.date = new Date();
post._id = String(post.id)
delete post.id
post.userId = String(post.userId)
Posts.insert(post);
});
usersData.forEach(function (user) {
user.date = new Date() // current time
user._id = String(user.id)
delete user.id
Users.insert(user);
});
}
});
if (Meteor.isServer) {
Meteor.publishComposite('postsSet', {
find: function () {
return Posts.find({});
},
children: [
{
find: function (post) {
console.log("%j", post.userId);
console.log("%j", Users.findOne({ _id: post.userId }));
return Users.find({ _id: post.userId });
}
}
]
});
Meteor.call("fetchJSONData");
//console.log('POSTS DATA = %j', Posts.find().fetch());
//console.log('USERS DATA = %j', Users.find().fetch());
}
HTML:
<head>
<title>ontra</title>
</head>
<body>
<div class='container'>
<header>
<h1>ontra</h1>
<form class='ontra'>
<input type='text' name='text' placeholder="Type to add new post">
</form>
</header>
<ul>
{{#each posts}}
{{> post}}
{{/each}}
</ul>
</div>
</body>
<template name='post'>
<li>
<span class="text">{{content}}</span>
<span class="text">{{username}}</span>
</li>
</template>
Your code doesn't run on meteorpad because the fetchJSONData method is executed on the server before it is defined in the common.js file. You should probably be calling the method after an event triggered on the client, or not use a method at all and simply fetch your JSON data on Meteor.startup.
Regarding the reactive join, it seems you want to do something very similar to Example 1 of the documentation: https://github.com/englue/meteor-publish-composite

Extract date from last item in JSON object (AngularJS)

I am dynamically creating an object on a web app using AngularJS. The idea is that the user can load new events as they click a button, for this to work I need to get the date of the last one so Ill know which ones to load next.
This is the code that generates the JSON object
services.getEvents().then(function(data){
$scope.events = data.data;
});
and my html...
<li ng-repeat="event in events | orderBy:'-event_date' ">
<span>{{event.event_date}},{{event.event_name}}, {{event.event_venue}}, {{event.event_description}} </span>
</li>
An example of one event...
{
"event_id":"1",
"event_name":"Event 1",
"event_date":"2014-09-26 00:00:00",
"event_venue":"Limerick",
"event_description":"stuff"
}
Is it possible to extract the latest date from a list of these so I can send it back to the API? Or am I going about this a wrong way altogether.
Im also using this an opportunity to learn AngularJS
var app = angular.module("app", []);
function MainCtrl ($filter) {
this.data = [
{
"event_id":"1",
"event_name":"Event 1",
"event_date":"2014-05-26 00:00:00",
"event_venue":"Limerick",
"event_description":"stuff"
},{
"event_id":"2",
"event_name":"Event 1",
"event_date":"2015-09-26 00:00:00",
"event_venue":"Limerick",
"event_description":"stuff"
},{
"event_id":"3",
"event_name":"Event 1",
"event_date":"2014-9-27 00:00:00",
"event_venue":"Limerick",
"event_description":"stuff"
}
];
this.lastOne =$filter('orderBy')(this.data,'-event_date')[this.data.length-1].event_date ;
}
angular.module("app").controller("MainCtrl", MainCtrl);
<html ng-app="app">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-controller="MainCtrl as vm">
<div class="container">
<h3>Latest One:{{vm.lastOne}}</h3>
</div>
</body>
</html>
To access the last element of an array use length -1
events[events.length - 1].event_date
if the response from the server is not sorted in any way you should filter first
var sortedArray = $filter('orderBy')($scope.events, '-event_date');
var lastDate = sortedArray[sortedArray.length - 1].event_date
hope this gets you on the way.
If you make your server side code return an array sorted by descending date, you could just take the date off the first item in your array. Something like:
events[0].event_date;
Failing that, you could do the sorting on the client side with something like:
events.sort(function(a, b){return new Date(a.event_date) < new Date(b.event_date);});
Then get the value of:
events[0].event_date;

On click How can I cycle through JSON one by one in AngularJS

Creating my first directive as an exercise in angular —making more or less a custom carousel to learn how directives work.
I've set up a Factory with some JSON data:
directiveApp.factory("Actors", function(){
var Actors = {};
Actors.detail = {
"1": {
"name": "Russel Brand",
"profession": "Actor",
"yoga": [
"Bikram",
"Hatha",
"Vinyasa"
]
},
"2": {
"name": "Aaron Bielefeldt",
"profession": "Ambassador",
"yoga": [
"Bikram",
"Hatha",
"Vinyasa"
]
},
"3": {
"name": "Adrienne Hengels",
"profession": "Ambassador",
"yoga": [
"Bikram",
"Hatha",
"Vinyasa"
]
}
};
return Actors;
});
And an actors controller:
function actorsCtrl($scope, Actors) {
$scope.actors = Actors;
}
And am using ng-repeat to display the model data:
<div ng-controller="actorsCtrl">
<div ng-repeat="actor in actors.detail">
<p>{{actor.name}} </p>
</div>
<div ng-click="next-actor">Next Actor</div>
</div>
1) How do I only display the actor's name in the first index of my angular model actors.detail?
2) How do I properly create a click event that will fetch the following index and replace the previous actor.name
User flow:
Russell Brand is Visible
click of next-actor ->Russell Brand's name is replaced with Aaron Bielefeldt
Since you only want the current user, the ng-repeat is not what you want to use, since that would be for each element in the data;
You would want to keep track of the index you are looking at in the scope, and increment that.
<div ng-controller="TestController">
{{data[current].name}}
<div ng-click="Next();"> NEXT! </div>
</div>
Where in the controller we also have these set up, where data is your actors:
$scope.current = 0;
$scope.Next = function() {
$scope.current = ($scope.current + 1) % $scope.data.length;
};
Here's a fiddle where it's done.
I would change my serivce to return a single actor and maintain the index in the controller.
something like this. This is an incomplete solution - you need to take care of cycle etc...

I have two divs with the same ng-controller in AngularJS, how can I make them share information?

I have two different div tags in my html code referencing the same controller in AngularJS. What I suspect is that since these divs aren't nested they each have their own instance of the controller, thus the data is different in both.
<div ng-controller="AlertCtrl">
<ul>
<li ng-repeat="alert in alerts">
<div class="span4">{{alert.msg}}</div>
</li>
</ul>
</div>
<div ng-controller="AlertCtrl">
<form ng-submit="addAlert()">
<button type="submit" class="btn">Add Alert</button>
</form>
</div>
I know this could easily be fixed by including the button in the first div but I feel this is a really clean and simple example to convey what I am trying to achieve. If we were to push the button and add another object to our alerts array the change will not be reflected in the first div.
function AlertCtrl($scope) {
$scope.alerts = [{
type: 'error',
msg: 'Oh snap! Change a few things up and try submitting again.'
}, {
type: 'success',
msg: 'Well done! You successfully read this important alert message.'
}];
$scope.addAlert = function() {
$scope.alerts.push({
type: 'sucess',
msg: "Another alert!"
});
};
}
This is a very common question. Seems that the best way is to create a service/value and share between then.
mod.service('yourService', function() {
this.sharedInfo= 'default value';
});
function AlertCtrl($scope, yourService) {
$scope.changeSomething = function() {
yourService.sharedInfo = 'another value from one of the controllers';
}
$scope.getValue = function() {
return yourService.sharedInfo;
}
}
<div ng-controller="AlertCtrl">{{getValue()}}</div>
<div ng-controller="AlertCtrl">{{getValue()}}</div>
If I understand the question correctly, you want to sync two html areas with the same controller, keeping data synced.
since these divs aren't nested they each have their own instance of the controller, thus the data is different in both
This isn't true, if you declare the controllers with the same alias (I'm using more recente angular version):
<div ng-controller="AlertCtrl as instance">
{{instance.someVar}}
</div>
<div ng-controller="AlertCtrl as instance">
{{instance.someVar}} (this will be the same as above)
</div>
However, if you WANT them to be different and comunicate each other, you will have to declare different aliases:
<div ng-controller="AlertCtrl as instance1">
{{instance1.someVar}}
</div>
<div ng-controller="AlertCtrl as instance2">
{{instance2.someVar}} (this will not necessarily be the same as above)
</div>
Then you can use services or broadcasts to comunicate between them (the second should be avoided, tough).

Categories