Smart and Dumb Component

Remember how will our component structure look like?

We will split our Chat component into three parts. (We will add ChannelHeader, ChannelAdd, and ChatAreaHeader components later since we have not done those parts yet.)

Chat will be the smart component, the other three will be dumb components.

Smart Component

Smart/parent component is responsible to interact with the server/cloud through the Service.

The first one is ChatComponent which is smart/parent component.

src/app/chat/components/chat.component.ts

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

import { Channel } from '../models/channel.model';
import { ChatService } from '../services/chat.service';

@Component({
  selector: 'my-chat',
  styles: [`
    .left {
      width: 20%;
      float: left;
      background: #2b78e4;
      height: 100vh;
      color: #c5d5df;
    }

    .right {
      width: 80%;
      float: right;
    }
  `],
  template: `
    <div class="left">
      <my-channel-list
        [channels]="chatService.channels"
        [channelId]="chatService.channel?._id"
        (selectChannel)="onSelectChannel($event)">
      </my-channel-list>
    </div>
    <div class="right">
      <my-chat-area-dialog
        [messages]="chatService.messages">
      </my-chat-area-dialog>
      <my-chat-area-bottom
        [channelId]="chatService.channel?._id"
        (sendMessage)="onSendMessage($event)">
      </my-chat-area-bottom>
    </div>
  `,
})
export class ChatComponent {
  constructor(
    private chatService: ChatService
  ) {}

  ngOnInit() {
    this.chatService.getChannels();
    this.chatService.getMessages('general');
  }

  onSelectChannel(channel: Channel) {
    this.chatService.selectChannel(channel);
  }

  onSendMessage({ channelId, messageContent }) {
    this.chatService.sendMessage(channelId, messageContent);
  }
}

Let's use <my-chat-area-bottom> to explain how smart/parent component and dumb/child component interact.

<my-chat-area-bottom
  [channelId]="chatService.channel?._id"
  (sendMessage)="onSendMessage($event)">
</my-chat-area-bottom>

Property binding

[channelId]="chatService.channel?._id"

When we want to transfer data from parent component to child component, we can use Property binding.

In our case, channelId is the property name, and chatService.channel?._id is the data we want to transfer to the child component.

Event binding

(sendMessage)="onSendMessage($event)"

When we want to pass some data from child component to parent component, we can use Event binding.

When we get event sendMessage from child component, the function onSendMessage($event) will run. $event is reserved, which stands for the value you got from the child component.

Dumb Component

Dumb/child component is only showing the data got from Smart component.

src/app/chat/components/chat-area-bottom.component.ts

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'my-chat-area-bottom',
  styles: [`
    .wrapper {
      padding: 0 .5rem;
      bottom: 1rem;
      position: fixed;
    }

    input {
      width: 70vw;
    }
  `],
  template: `
    <div class="wrapper">
      <form [formGroup]="messageForm" (ngSubmit)="onSendMessage()">
        <input formControlName="message" placeholder="Click enter to send"/>
      </form>
    </div>
  `,
})
export class ChatAreaBottomComponent implements OnInit {
  @Input() channelId: string;
  @Output() sendMessage = new EventEmitter<any>();

  messageForm: FormGroup;

  constructor(
    private fb: FormBuilder
  ) {}

  ngOnInit() {
    this.messageForm = this.fb.group({
      message: ['', Validators.required]
    });
  }

  onSendMessage() {
    if (!this.messageForm.valid) return;

    this.sendMessage.emit({ channelId: this.channelId, messageContent: this.messageForm.value.message });

    this.messageForm.reset();
  }
}

If you check the codes above, there is nothing new except for the @Input and @Output().

@Input() channelId: string;
@Output() sendMessage = new EventEmitter<any>();

channelId and sendMessage are corresponding to the values in

<my-chat-area-bottom
  [channelId]="chatService.channel?._id"
  (sendMessage)="onSendMessage($event)">
</my-chat-area-bottom>

Now you should be clear how they correspond to each other.

Notice that, sendMessage is an EventEmitter, which can help us emit the event to the parent component. In our case, it emit the event with an Object to the parent component.

this.sendMessage.emit({ channelId: this.channelId, messageContent: this.messageForm.value.message });

src/app/chat/components/channel-list.component.ts

import { Component, Input, Output, EventEmitter } from '@angular/core';

import { Channel } from '../models/channel.model';

@Component({
  selector: 'my-channel-list',
  styles: [`
    li {
      cursor: pointer;
      margin: 0 0 .5rem 0;
    }

    .selected {
      background-color: #1a64cb;
    }
  `],
  template: `
    <ul>
      <li *ngFor="let channel of channels"
          [ngClass]="{'selected': channel._id === channelId}"
          (click)="onSelectChannel(channel)">
        {{channel.name}}
      </li>
    </ul>
  `
})
export class ChannelListComponent {
  @Input() channels: Channel[];
  @Input() channelId: string;
  @Output() selectChannel = new EventEmitter<Channel>();

  onSelectChannel(channel: Channel) {
    this.selectChannel.emit(channel);
  }
}

src/app/chat/components/chat-area-dialog.component.ts

import { Component, Input } from '@angular/core';

import { Message } from '../models/message.model';

@Component({
  selector: 'my-chat-area-dialog',
  styles: [`
    .wrapper {
      list-style-type: none;
      padding: 0 .5rem;
      height: 80vh;
      overflow: scroll;
    }

    .name {
      font-weight: bold;
    }

    .timestamp {
      color: #aaa;
    }
  `],
  template: `
    <ul class="wrapper">
      <li *ngFor="let message of messages">
        <p><span class="name">{{message.userName}}</span> <span class="timestamp">{{message.timestamp | calendar}}</span></p>
        <p>{{message.content}}</p>
      </li>
    </ul>
  `
})
export class ChatAreaDialogComponent {
  @Input() messages: Message[];
}

At last, don't forget adding

ChannelListComponent,
ChatAreaDialogComponent,
ChatAreaBottomComponent

in the declarations of the src/app/app.module.ts.


Run the live example for this part.

results matching ""

    No results matching ""