Message thread

Service

Create a new method UserService.getMessageThread

getMessageThread(fromUserId: number, toUserId: number) {
  return this.http
    .get<Message[]>(this.baseUrl + fromUserId + '/messages/thread/' + toUserId);
}

Component

Create a new component member/member-messages and add it to the register section off app.module.ts

export class MemberMessagesComponent implements OnInit {
  @Input() recipientId: number;
  messages: Message[];

  constructor(private userService: UserService, private authService: AuthService, private alertify: AlertifyService) {}

  ngOnInit() {
    this.loadMessages();
  }

  loadMessages() {
    this.userService
      .getMessageThread(this.authService.getUserId(), this.recipientId)
      .subscribe(next => (this.messages = next), error => this.alertify.error(error));
  }
}

Add some simple rendering so that we can test the component

<p *ngFor="let message of messages">
  {{message.content}}
</p>

Use the new component in member-detail.component.html

<tab heading="Messages">
  <app-member-messages [recipientId]="user.id"></app-member-messages>
</tab>

Test the application.

Template

Remove the test code in member-messages.component.html and add the new template

<div class="card">
  <div class="card-body">
    <div *ngIf="messages?.length === 0">
      <p>No messages yet... say hi using the message box below</p>
    </div>

    <ul class="chat">
      <li *ngFor="let message of messages">
        <!-- to them -->
        <div *ngIf="message.senderId == recipientId">
          <span class="chat-img float-left">
            <img src="{{message.senderPhotoUrl}}" alt="{{message.senderKnownAs}}" class="rounded-circle">
          </span>
          <div class="chat-body">
            <div class="header">
              <strong class="primary-font">{{message.senderKnownAs}}</strong>
              <small class="text-muted float-right">
                <span class="fa fa-clock">{{message.sentDate | timeAgo}}</span>
              </small>
            </div>
          </div>
          <p>{{message.content}}</p>
        </div>

        <!-- to me -->
        <div *ngIf="message.senderId != recipientId">
            <span class="chat-img float-right">
              <img src="{{message.senderPhotoUrl}}" alt="{{message.senderKnownAs}}" class="rounded-circle">
            </span>
            <div class="chat-body">
              <div class="header">
                <small class="text-muted">
                  <span class="fa fa-clock">{{message.sentDate | timeAgo}}</span>
                  <span *ngIf="!message.isRead" class="text-danger">(unread)</span>
                  <span *ngIf="message.isRead" class="text-success">(read {{message.readDate | timeAgo}})</span>
                </small>
                <strong class="primary-font float-right">{{message.senderKnownAs}}</strong>
              </div>
            </div>
            <p>{{message.content}}</p>
          </div>
      </li>
    </ul>
  </div>
  <div class="card-footer">
    <form action="">
      <div class="input-group">
        <input type="text" class="form-control input-sm" placeholder="send a private message">
        <div class="input-group-append">
          <button class="btn btn-primary">Send</button>
        </div>
      </div>
    </form>
  </div>
</div>
.card {
  border: none;
}

.chat {
  list-style: none;
  margin: 0;
  padding: 0;
}

.chat li {
  margin-bottom: 10px;
  padding-bottom: 10px;
  border-bottom: 1px dotted #b3a9a9;
}

.rounded-circle {
  height: 50px;
  width: 50px;
}

.card-body {
  overflow-y: scroll;
  height: 400px;
}

Tab selection

Feature: when the user clicks the message button in the profile component then the tab messages is activated.

Valor-soft example.

Open members-detail.component.html and add a template reference to the tabset

<tabset class="member-tabset" #memberTabs>

Add a ViewChild variable in members-detail.component.ts and a method to activate a tab by id

@ViewChild('memberTabs') memberTabs: TabsetComponent;
// ...
selectTab(tabId: number) {
  if (tabId >= 0 && tabId < this.memberTabs.tabs.length) {
    this.memberTabs.tabs[tabId].active = true;
  }
}

Back in the html change the Message button code to open the Messages tab

<button class="btn btn-success w-100" (click)="selectTab(3)">Message</button>

Query parameters

Feature: when the user clicks a message in his inbox then the profile component is opened and the tab messages is activated.

Open messages.component.html and add a query string parameter to the routerLink of each message

<tr *ngFor="let message of messages" [routerLink]="['/members',
        messageContainer == 'Outbox' ? message.recipientId : message.senderId]" [queryParams]="{tab: 3}">

Back in members-detail.component.ts subscribe to the query parameter from the route in ngOnInit

this.route.queryParams.subscribe(params => {
  const tabId = Number(params['tab'] || 0);

  if (!isNaN(tabId)) {
    this.selectTab(tabId);
  }
});

Add the queryParams and routerLink in member-card.component.html

<li class="list-inline-item">
  <button class="btn btn-primary btn-responsive" [routerLink]="['/members/', user.id]" [queryParams]="{tab: 3}">
    <i class="fa fa-envelope"></i>
  </button>
</li>