How to identify the first and last element of a chain in an array of objects
Guys, I was adding up when I was trying to set the correct chat message classes.
I have a component with the following pattern:
...
<ul>
<li *ngFor="#mes of chatMessages; #index = index;">
user {{mes.user.id}}:
<span
[class.header-recent]="isRecentHeader(chatMessages, mes, index)"
[class.is-recent]="isRecent(chatMessages, mes, index)"
[class.last-recent]="isLastRecent(chatMessages, mes, index)">
{{mes.text}}
</span>
</li>
</ul>
...
I'm trying to apply three different classes to each post .header-recent
, which should be applied to the first post in a thread from one user. Class .is-recent
for messages that were sent by the user in less than 60 seconds. And .is-last-recent
for the last message in the last message thread. Like on a picture.
The message array looks like
[...
{
"type": "message",
"id": "19372",
"text": "43\n",
"created": 1495026352,
"user": {
"id": 2
}
},
{
"type": "message",
"id": "19373",
"text": "safaf",
"created": 1495026357,
"user": {
"id": 1
}
},
{
"type": "system",
"id": "19374",
"text": "SYSTEM MESSAGE",
"created": 1495027801,
"user":{
"id": "SYSTEM"
}
}
...]
I need to write 3 functions, each defining one class for messages. I currently have two of them.
isRecentHeader(mess, mes, index){
let lastMessage;
if( index == mess.length-1){
lastMessage = mess[index];
} else if( index => 0 && index != mess.length-1) {
lastMessage = mess[index-1];
}
return lastMessage && !this.calcRecent(mes, lastMessage) && lastMessage.type != 'system';
}
isRecent(mess, mes, index){
let lastMes = mess[index-1];
return this.calcRecent(mes, lastMes);
}
isLastRecent(mess, mes, index){
// confuse here
}
calcRecent(message, lastMessage){
const messageDate = moment(message.created * 1000);
const lastMessageDate = lastMessage ? moment(lastMessage.created *1000) : null;
return lastMessage && (lastMessage.type != 'system')
&& (message.type != 'system')
&& (lastMessage.user.id == message.user.id)
&& ((messageDate.valueOf() - lastMessageDate.valueOf()) / 1000 <= 60)
}
So the function isRecent()
works fine, isRecentHeader()
works pretty much the same as we will see, but applies the class .header-recent
to a single message. And the function doesn't work isLastRecent()
. So my question is how to change the code in isRecentHeader()
so that it doesn't apply the class to a single post and what should I do in the function isLastRecent()
. Here's the codepen of my example.
PS If my question seems unclear or confusing to you, please tell me and I will try to change or clarify everything.
source to share
Thanks to my team leader at work, he gave me the answer. He used setter and getter to handle incoming messages in component.
...
private _messages: any[];
recents = [];
@Input()
set messages(messages: any[]) {
this._messages = messages;
this.recents = [];
for(let i = 0; i < messages.length; i++) {
const message = messages[i];
const nextMessage = i == messages.length - 1 ? null : messages[i + 1];
const prevMessage = i == 0 ? null : messages[i - 1];
const isRecentWithNext = this.isRecentMessages(message, nextMessage);
const isRecentWithPrevious = this.isRecentMessages(message, prevMessage);
if(!isRecentWithPrevious && isRecentWithNext) {
this.recents[i] = 'first-recent'
} else if(isRecentWithPrevious && isRecentWithNext) {
this.recents[i] = 'recent';
} else if(isRecentWithPrevious && !isRecentWithNext) {
this.recents[i] = 'last-recent';
}
}
}
get messages() {
return this._messages;
}
isRecentMessage(index) {
return this.recents[index] == 'recent' || this.recents[index] == 'last-recent';
}
isRecentMessages(message, message2?) {
return message && message2
&& message.type!='system' && message2.type != 'system'
&& (message.user.id == message2.user.id)
&& (Math.abs((message.created - message2.created)) <= 60)
}
And in the template for installing classes, now the following code
<li *ngFor="#mes of messages; #index = index;">
<span [class]="recents[index]>
{{mes.text}}
</span>
</li>
source to share
You can achieve this easily using basic conditions as shown below
<ul>
<li *ngFor="#mes of chatMessages; #index = index;">
user {{mes.user.id}}:
<span
[class.header-recent]="index === 0"
[class.is-recent]="index > 0 && index < (chatMessages.length + 1)"
[class.last-recent]="(chatMessages.length) === index+1">
{{mes.text}}
</span>
</li>
</ul>
source to share
Regarding your problems:
Single message error:
- In order not to add the class to one post, you need to make sure that the next post is from the same user id.
Last message:
- The last message is the same logic of the first message, you just need to check that the next message is from a different user ID.
I just made your code work, although some refactoring can be done.
isRecentHeader(mess, mes, index){
let lastMessage;
let nextMessage;
if( index == mess.length-1){
lastMessage = mess[index];
} else if( index >= 0 && index != mess.length-1) {
lastMessage = mess[index-1];
if (index < mess.length-1){
nextMessage = mess[index+1];
}
}
return lastMessage && nextMessage && mes.user.id==nextMessage.user.id && !this.calcRecent(mes, lastMessage) && lastMessage.type != 'system';
}
source to share