Skip to content

Commit b15f35f

Browse files
authoredAug 14, 2022
feat(theme:title): add selector property (#1487)
1 parent 6204cb8 commit b15f35f

File tree

4 files changed

+124
-53
lines changed

4 files changed

+124
-53
lines changed
 

‎packages/theme/src/services/title/index.en-US.md

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Get the `title` value according to the following order:
2323
| Name | Type | Description |
2424
| ---------------------------------------------- | ---------- | ------------------------------- |
2525
| `default` | `property` | Default title of document title |
26+
| `selector` | `property` | Set the default CSS selector string |
2627
| `separator` | `property` | Separator |
2728
| `prefix` | `property` | Prefix of document title |
2829
| `suffix` | `property` | Suffix of document title |

‎packages/theme/src/services/title/index.zh-CN.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type: Service
1313

1414
根据以下顺序获取 `title` 值:
1515

16-
1. 路由配置 `{ data: { title: 'page name', titleI18n: 'page-name' } }`
16+
1. 路由配置 `{ data: { title: 'page name', titleI18n: 'page-name' } as RouteTitle }`
1717
2. 根据当前 URL 解析菜单数据
1818
3. 页面 `alain-default__content-title``page-header__title` 中获取 `h1` 内容
1919
4. 默认标题名
@@ -23,6 +23,7 @@ type: Service
2323
| 名称 | 类型 | 描述 |
2424
| ---------------------------------------------- | ---------- | -------------- |
2525
| `default` | `property` | 设置默认标题名 |
26+
| `selector` | `property` | 设置默认CSS选择器字符串 |
2627
| `separator` | `property` | 设置分隔符 |
2728
| `prefix` | `property` | 设置前缀 |
2829
| `suffix` | `property` | 设置后缀 |

‎packages/theme/src/services/title/title.service.spec.ts

+28-1
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import { fakeAsync, TestBed, tick } from '@angular/core/testing';
33
import { Title } from '@angular/platform-browser';
44
import { ActivatedRoute } from '@angular/router';
55
import { RouterTestingModule } from '@angular/router/testing';
6+
import { of } from 'rxjs';
67

78
import { NzSafeAny } from 'ng-zorro-antd/core/types';
89

910
import { AlainThemeModule } from '../../theme.module';
1011
import { AlainI18NService, AlainI18NServiceFake, ALAIN_I18N_TOKEN } from '../i18n/i18n';
1112
import { Menu } from '../menu/interface';
1213
import { MenuService } from '../menu/menu.service';
13-
import { TitleService } from './title.service';
14+
import { RouteTitle, TitleService } from './title.service';
1415

1516
describe('Service: Title', () => {
1617
let getPathByUrlData: NzSafeAny;
@@ -141,6 +142,25 @@ describe('Service: Title', () => {
141142
tick(srv.DELAY_TIME + 1);
142143
expect(title.setTitle).toHaveBeenCalledWith(alain);
143144
}));
145+
it('with observable', fakeAsync(() => {
146+
genModule([
147+
{
148+
provide: ActivatedRoute,
149+
useValue: {
150+
firstChild: {
151+
snapshot: {
152+
data: {
153+
title: of('a')
154+
} as RouteTitle
155+
}
156+
}
157+
}
158+
}
159+
]);
160+
srv.setTitle();
161+
tick(srv.DELAY_TIME + 1);
162+
expect(title.setTitle).toHaveBeenCalledWith('a');
163+
}));
144164
it('without', fakeAsync(() => {
145165
genModule([
146166
{
@@ -210,6 +230,13 @@ describe('Service: Title', () => {
210230
tick(srv.DELAY_TIME + 1);
211231
expect(title.setTitle).toHaveBeenCalledWith(notPageName);
212232
}));
233+
it('without custom selector', fakeAsync(() => {
234+
genModule([]);
235+
srv.selector = 'test';
236+
srv.setTitle();
237+
tick(srv.DELAY_TIME + 1);
238+
expect(title.setTitle).toHaveBeenCalledWith(notPageName);
239+
}));
213240
});
214241
});
215242

‎packages/theme/src/services/title/title.service.ts

+93-51
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,26 @@ import { DOCUMENT } from '@angular/common';
22
import { Inject, Injectable, Injector, OnDestroy, Optional } from '@angular/core';
33
import { Title } from '@angular/platform-browser';
44
import { ActivatedRoute, Router } from '@angular/router';
5-
import { Subscription, filter } from 'rxjs';
5+
import { Observable, of, map, delay, isObservable, switchMap, Subject, takeUntil, Subscription } from 'rxjs';
66

77
import type { NzSafeAny } from 'ng-zorro-antd/core/types';
88

99
import { AlainI18NService, ALAIN_I18N_TOKEN } from '../i18n/i18n';
1010
import { MenuService } from '../menu/menu.service';
1111

12+
export interface RouteTitle {
13+
title?: string | Observable<string>;
14+
titleI18n?: string;
15+
}
16+
1217
@Injectable({ providedIn: 'root' })
1318
export class TitleService implements OnDestroy {
1419
private _prefix: string = '';
1520
private _suffix: string = '';
1621
private _separator: string = ' - ';
1722
private _reverse: boolean = false;
18-
private i18n$: Subscription;
23+
private destroy$ = new Subject<void>();
24+
private tit$?: Subscription;
1925

2026
readonly DELAY_TIME = 25;
2127

@@ -28,92 +34,126 @@ export class TitleService implements OnDestroy {
2834
private i18nSrv: AlainI18NService,
2935
@Inject(DOCUMENT) private doc: NzSafeAny
3036
) {
31-
this.i18n$ = this.i18nSrv.change.pipe(filter(() => !!this.i18n$)).subscribe(() => this.setTitle());
37+
this.i18nSrv.change.pipe(takeUntil(this.destroy$)).subscribe(() => this.setTitle());
3238
}
3339

34-
/** 设置分隔符 */
40+
/**
41+
* Set separator
42+
*
43+
* 设置分隔符
44+
*/
3545
set separator(value: string) {
3646
this._separator = value;
3747
}
3848

39-
/** 设置前缀 */
49+
/**
50+
* Set prefix
51+
*
52+
* 设置前缀
53+
*/
4054
set prefix(value: string) {
4155
this._prefix = value;
4256
}
4357

44-
/** 设置后缀 */
58+
/**
59+
* Set suffix
60+
*
61+
* 设置后缀
62+
*/
4563
set suffix(value: string) {
4664
this._suffix = value;
4765
}
4866

49-
/** 设置是否反转 */
67+
/**
68+
* Set whether to reverse
69+
*
70+
* 设置是否反转
71+
*/
5072
set reverse(value: boolean) {
5173
this._reverse = value;
5274
}
5375

54-
/** 设置默认标题名 */
76+
/**
77+
* Set the default CSS selector string
78+
*
79+
* 设置默认CSS选择器字符串
80+
*/
81+
selector?: string | null;
82+
83+
/**
84+
* Set default title name
85+
*
86+
* 设置默认标题名
87+
*/
5588
default = `Not Page Name`;
5689

57-
private getByElement(): string {
58-
const el = (this.doc.querySelector('.alain-default__content-title h1') ||
59-
this.doc.querySelector('.page-header__title')) as HTMLElement;
60-
if (el) {
61-
let text = '';
62-
el.childNodes.forEach(val => {
63-
if (!text && val.nodeType === 3) {
64-
text = val.textContent!.trim();
90+
private getByElement(): Observable<string> {
91+
return of('').pipe(
92+
delay(this.DELAY_TIME),
93+
map(() => {
94+
const el = ((this.selector != null ? this.doc.querySelector(this.selector) : null) ||
95+
this.doc.querySelector('.alain-default__content-title h1') ||
96+
this.doc.querySelector('.page-header__title')) as HTMLElement;
97+
if (el) {
98+
let text = '';
99+
el.childNodes.forEach(val => {
100+
if (!text && val.nodeType === 3) {
101+
text = val.textContent!.trim();
102+
}
103+
});
104+
return text || el.firstChild!.textContent!.trim();
65105
}
66-
});
67-
return text || el.firstChild!.textContent!.trim();
68-
}
69-
return '';
106+
return '';
107+
})
108+
);
70109
}
71110

72-
private getByRoute(): string {
111+
private getByRoute(): Observable<string> {
73112
let next = this.injector.get(ActivatedRoute);
74113
while (next.firstChild) next = next.firstChild;
75-
const data = (next.snapshot && next.snapshot.data) || {};
114+
const data: RouteTitle = (next.snapshot && next.snapshot.data) || {};
76115
if (data.titleI18n && this.i18nSrv) data.title = this.i18nSrv.fanyi(data.titleI18n);
77-
return data.title;
116+
return isObservable(data.title) ? data.title : of(data.title!);
78117
}
79118

80-
private getByMenu(): string {
119+
private getByMenu(): Observable<string> {
81120
const menus = this.menuSrv.getPathByUrl(this.injector.get<Router>(Router).url);
82-
if (!menus || menus.length <= 0) return '';
121+
if (!menus || menus.length <= 0) return of('');
83122

84123
const item = menus[menus.length - 1];
85124
let title;
86125
if (item.i18n && this.i18nSrv) title = this.i18nSrv.fanyi(item.i18n);
87-
return title || item.text!;
88-
}
89-
90-
private _setTitle(title?: string | string[]): void {
91-
if (!title) {
92-
title = this.getByRoute() || this.getByMenu() || this.getByElement() || this.default;
93-
}
94-
if (title && !Array.isArray(title)) {
95-
title = [title];
96-
}
97-
98-
let newTitles: string[] = [];
99-
if (this._prefix) {
100-
newTitles.push(this._prefix);
101-
}
102-
newTitles.push(...(title as string[]));
103-
if (this._suffix) {
104-
newTitles.push(this._suffix);
105-
}
106-
if (this._reverse) {
107-
newTitles = newTitles.reverse();
108-
}
109-
this.title.setTitle(newTitles.join(this._separator));
126+
return of(title || item.text!);
110127
}
111128

112129
/**
113-
* Set the document title, will be delay `25ms`, pls refer to [#1261](https://github.com/ng-alain/ng-alain/issues/1261)
130+
* Set the document title
114131
*/
115132
setTitle(title?: string | string[]): void {
116-
setTimeout(() => this._setTitle(title), this.DELAY_TIME);
133+
this.tit$?.unsubscribe();
134+
this.tit$ = of(title)
135+
.pipe(
136+
switchMap(tit => (tit ? of(tit) : this.getByRoute())),
137+
switchMap(tit => (tit ? of(tit) : this.getByMenu())),
138+
switchMap(tit => (tit ? of(tit) : this.getByElement())),
139+
map(tit => tit || this.default),
140+
map(title => (!Array.isArray(title) ? [title] : title)),
141+
takeUntil(this.destroy$)
142+
)
143+
.subscribe(titles => {
144+
let newTitles: string[] = [];
145+
if (this._prefix) {
146+
newTitles.push(this._prefix);
147+
}
148+
newTitles.push(...(titles as string[]));
149+
if (this._suffix) {
150+
newTitles.push(this._suffix);
151+
}
152+
if (this._reverse) {
153+
newTitles = newTitles.reverse();
154+
}
155+
this.title.setTitle(newTitles.join(this._separator));
156+
});
117157
}
118158

119159
/**
@@ -124,6 +164,8 @@ export class TitleService implements OnDestroy {
124164
}
125165

126166
ngOnDestroy(): void {
127-
this.i18n$.unsubscribe();
167+
this.tit$?.unsubscribe();
168+
this.destroy$.next();
169+
this.destroy$.complete();
128170
}
129171
}

0 commit comments

Comments
 (0)
Please sign in to comment.