탭 메뉴는 아래와 같이 모바일 화면 하단에 위치하여 서비스의 가장 상위 레벨의 경로 처리할 수 있도록 하는 네비게이터이다.
데스크탑 화면과 달리 모바일 화면은 사용자가 보는 공간이 좁기 때문에 같은 정보라도 더 간결하게 표현되어야 한다. 또한 안드로이드에서는 경우에 따라 화면 상단/하단에 url 검색창이나 뒤로가기, 홈버튼 등의 안드로이드 자체 툴바가 있어 컨텐츠를 표시할 수 있는 공간이 더 적어지는 경우도 있다.
따라서 가장 상위 레벨의 경로에서는 탭 메뉴가 보이더라도 한단계 더 진입한 페이지(상세페이지, 내 정보 페이지…)에서는 탭 메뉴는 숨기고 해당 컨텐츠에 더 집중할 수 있도록 할 필요가 있었다.
ionic 3까지만해도 tabsHideOnSubPages
옵션을 통해 탭 메뉴들을 경우에 따라 보여지게 하거나 숨길 수 있었지만 ionic 4 이상부터는 해당 기능이 제거되었다.
TabService
먼저 탭 메뉴를 페이지 경로에 따라 숨기거나 보여지게 하도록 하는 서비스를 구현한다.
생성자에서 platform.ready()
가되는 시점에 checkNavigationEvent
를 호출한다. checkNavigationEvent
는 모든 생명주기 이벤트가 트리거되는 시점이 아닌 NavigationEnd
즉, 페이지가 변경될 때만 감지하여 처리하는 메소드이다.
import { Injectable } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { Platform } from '@ionic/angular';@Injectable({
providedIn: 'root'
})
export class TabService {
hideTabPages: string[] = [
'my-info',
'payments'
...
] constructor(private router: Router, private platform: Platform) {
this.platform.ready().then(() => {
this.checkNavigationEvent();
});
} checkNavigationEvent() {
this.router.events
.pipe(filter((e) => e instanceof NavigationEnd))
.subscribe((e: any) => {
this.hideOrShowTab(e);
});
} hideOrShowTab(e: NavigationEnd) { ... }
}
탭을 보여줄것인지 숨길 것인지는 페이지 경로를 hideTabPages
화이트리스트로 가지고 적절히 필터링을 할 것이다. 페이지 경로 예시는 다음과 같다.
/my-info
/payments
...
hideOrShowTab()
에서는 페이지 변경 이벤트를 적절히 파싱하여 페이지 경로를 가져온 다음, hideTabPages
화이트리스트와 일치하면 hide()
를 아니라면 show()
를 호출한다.
hideOrShowTab(e: NavigationEnd) {
const urlArray = e.url.split('/');
const pageUrl = urlArray[urlArray.length - 1];
const pageUrlParent = urlArray[urlArray.length - 2];
const page = pageUrl.split('?')[0];
const hideParamPage = this.routeParamPages.indexOf(pageUrlParent) > -1 && !isNaN(Number(page));
const shouldHide = this.hideTabPages.indexOf(page) > -1 || hideParamPage;
shouldHide ? this.hide() : this.show()
}
hide()
메소드에서는 탭 엘리먼트를 가져와 display: none
스타일을 적용하고 show()
메소드에서는 그 반대로 적용한다.
hide() {
const tabBar = document.getElementById('tabBar');
if (tabBar !== null && tabBar.style.display !== 'none') tabBar.style.display = 'none';
}show() {
const tabBar = document.getElementById('tabBar');
if (tabBar !== null && tabBar.style.display !== 'flex') tabBar.style.display = 'flex';
}
TabsPageComponent
탭 메뉴가 정의되어 있는 컴포넌트에서는 아래와 같이 탭 엘리먼트에 접근하기 위해 tabBar
아이디를 부여한다.
<ion-tabs>
<ion-tab-bar #tabBar id="tabBar" slot="bottom">
<ng-container *ngFor="let tab of tabs">
<ion-tab-button [tab]="tab.name">
<ion-icon [name]="tab.icon"></ion-icon>
<ion-label>{{tab.label}}</ion-label>
</ion-tab-button>
</ng-container>
</ion-tab-bar>
</ion-tabs>
AppComponent
마지막으로 TabService
의 인스턴스를 생성하기 위해(생성자를 호출하기 위해) 최상위 컴포넌트 생성자에 TabService
를 아래와 같이 추가한다.
constructor(
...
private tabService: TabService
) {this.initializeApp()}
이때까지 작업한 내용을 정리하면 다음과 같다.
TabService
에서는platform
로드 시NavigationEnd
이벤트의 경우에 탭을 보이거나 숨기는 함수를 호출한다.- 탭 메뉴 엘리먼트에 접근할 아이디를 부여한다.
- 최상위 컴포넌트에서
TabService
인스턴스를 생성한다.
라우터 파라미터 처리
마지막으로 추가해야할 부분은 다음과 같은 라우터 파라미터가 포함된 페이지경로 처리이다. 파라미터가 있는 부분을 제외한 경로를 가져와야 한다.
/event-detail/102
/group/3
...
TabService
에 다음과 같이 라우터 파라미터가 있는 화이트리스트를 추가한다.
routeParamPages: string[] = [
'event-detail',
'group',
...
];
hideOrShowTab
메소드에서 페이지 경로가 routeParamPages
화이트리스트에 포함되는지 체크하고 shouldHide
플래그에 or
연산자로 조건을 추가한다.
hideOrShowTab(e: NavigationEnd) {
...
const hideParamPage = this.routeParamPages.indexOf(pageUrlParent) > -1 && !isNaN(Number(page));
const shouldHide = this.hideTabPages.indexOf(page) > -1 || hideParamPage;shouldHide ? this.hide() : this.show())
}
정리
모바일 화면에서는 최대한 컨텐츠에 집중할 수 있도록 간결하게 표현되어야 했다.
탭을 숨길 페이지는 라우터 파리미터가 있는 경로를 포함하여 화이트리스트로 그 목록을 가지도록 하였다.
최상단 레벨의 페이지에서는 탭 메뉴를 통해 네비게이션이 수행되고, 한단계 더 진입한 화면에서는 탭 메뉴를 없애고 그만큼 컨텐츠가 보여지는 부분을 확장시킬 수 있었다.