ionic(angular) 채팅창 하단으로 스크롤 하기
in Javascript on Javascript, angular, ionic
채팅창에서 메시지가 입력됐을때 화면 제일 하단으로 스크롤 하기
version info
- @ionic/angular : 4.7.1
- @angular : 8.1.2
화면 html
<ion-header>
<ion-toolbar>
<ion-title>채팅방</ion-title>
<ion-buttons slot="start">
<ion-button>
<ion-icon slot="icon-only" name="arrow-back"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item *ngFor="let message of messages">
<ion-label></ion-label>
<ion-label></ion-label>
</ion-item>
</ion-list>
</ion-content>
<ion-footer>
<ion-toolbar>
<ion-input placeholder="message..." [(ngModel)]="send_msg"></ion-input>
<ion-buttons slot="end">
<ion-button (click)="send_message()" color="primary">
<ion-icon slot="start" name="send"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-footer>
로직처리 ts 파일
@ViewChild 로 ion-content를 참조해서 scrollToBottom() 메소드를 사용하면 된다.
import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { env } from '@env/environment';
import { IonContent } from '@ionic/angular';
import io from 'socket.io-client';
@Component({
selector: 'chat-view',
templateUrl: './chat.view.page.html',
styleUrls: ['./chat.view.page.scss']
})
export class ChatsViewPage implements OnInit, AfterViewInit {
private socket: any;
messages: any[] = [];
send_msg: string;
// ion-content 참조
@ViewChild(IonContent, { read: IonContent, static: true }) conent: IonContent;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.socket = io(`${env.socket_url}/chat`, {
query: {
// 소켓 연결할때 인증처리를 위해 요렇게 jwt token 추가로 넘길수 있음.
access_token: localStorage.getItem('access_token')
}
});
// 소켓 연결성공
this.socket.on('connect', () => {
console.log('Connected');
this.socket.emit('join', {
to: 'stove99'
});
});
// 메시지 수신시 화면에 반영하고 스크롤 제일 하단으로...
this.socket.on('chat message', data => {
this.messages.push(data);
this.scrollToBottom();
});
}
// 화면이 다 초기화 된 후 스크롤 제일 하단으로
ngAfterViewInit() {
this.scrollToBottom();
}
scrollToBottom() {
setTimeout(() => {
this.conent.scrollToBottom(300);
}, 100);
}
send_message() {
this.socket.emit('chat message', {
name: '테스트',
to: 'stove99',
msg: this.send_msg
});
this.send_msg = '';
}
}
참고로 보는 server side socket
server side 는 nestjs 로 간단하게 만들어 보았다. 실제로 쓸려면 고칠건 많다.
import { SubscribeMessage, WebSocketGateway, WebSocketServer, WsResponse, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { Logger, UseGuards } from '@nestjs/common';
import { WsGuardGuard } from '../../common/auth/ws.guard.guard';
@UseGuards(WsGuardGuard)
@WebSocketGateway({ namespace: 'chat' })
export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server: Server;
private logger: Logger = new Logger('ChatGateway');
@SubscribeMessage('join')
join(socket: Socket, data: any): WsResponse<any> {
// room에 join한다
const room = [data.user.id, data.to].sort().join('-');
socket.join(room);
return { event: 'room created', data: { room } };
}
@SubscribeMessage('chat message')
async message(socket: Socket, data: any): Promise<WsResponse<any>> {
this.logger.log(`message from client: ${data}`);
const room = [data.user.id, data.to].sort().join('-');
socket = socket.join(room).to(room);
socket.emit('chat message', data);
return { event: 'chat message', data };
}
afterInit(server: Server) {
this.logger.log('Init');
}
handleDisconnect(client: Socket) {
this.logger.log(`Client disconnected: ${client.id}`);
}
handleConnection(client: Socket, ...args: any[]) {
this.logger.log(`Client connected: ${client.id}`);
}
}
WsGuardGuard
추가 예제로 소켓 접속시 아무나 접속 못하도록 jwt 체크를 위한 Guard.
위 예제에서 @UseGuards(WsGuardGuard) 요런식으로 적용시켜서 쓸 수 있다.
import { CanActivate, ExecutionContext, Injectable, Inject, Logger } from '@nestjs/common';
import { Observable } from 'rxjs';
import * as jwt from 'jsonwebtoken';
import * as config from 'config';
@Injectable()
export class WsGuardGuard implements CanActivate {
private logger: Logger = new Logger('WsGuardGuard');
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
try {
const user = jwt.verify(context.switchToWs().getClient().handshake.query.access_token, config.get('JWT_SECRET'));
context.switchToWs().getData().user = user;
return Boolean(user);
} catch (err) {
console.log(err);
this.logger.error('socket access token error');
}
return false;
}
}