@ts-core/angular 21.0.0 → 21.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.MD +1041 -0
- package/fesm2022/ts-core-angular.mjs +274 -263
- package/fesm2022/ts-core-angular.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ts-core-angular.d.ts +19 -13
package/README.MD
ADDED
|
@@ -0,0 +1,1041 @@
|
|
|
1
|
+
# @ts-core/angular
|
|
2
|
+
|
|
3
|
+
Angular библиотека с базовыми утилитами, директивами, пайпами и сервисами для разработки веб-приложений. Предоставляет инструменты для работы с языками, темами, окнами, авторизацией, хранилищами данных и многим другим.
|
|
4
|
+
|
|
5
|
+
## Содержание
|
|
6
|
+
|
|
7
|
+
- [Установка](#установка)
|
|
8
|
+
- [Зависимости](#зависимости)
|
|
9
|
+
- [Быстрый старт](#быстрый-старт)
|
|
10
|
+
- [Модули](#модули)
|
|
11
|
+
- [Директивы](#директивы)
|
|
12
|
+
- [Пайпы](#пайпы)
|
|
13
|
+
- [Сервисы](#сервисы)
|
|
14
|
+
- [Авторизация](#авторизация)
|
|
15
|
+
- [Управление окнами](#управление-окнами)
|
|
16
|
+
- [Хранилища данных](#хранилища-данных)
|
|
17
|
+
- [Примеры использования](#примеры-использования)
|
|
18
|
+
- [Связанные пакеты](#связанные-пакеты)
|
|
19
|
+
|
|
20
|
+
## Установка
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @ts-core/angular
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
yarn add @ts-core/angular
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm add @ts-core/angular
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Зависимости
|
|
35
|
+
|
|
36
|
+
| Пакет | Описание |
|
|
37
|
+
|-------|----------|
|
|
38
|
+
| `@angular/core` | Angular фреймворк |
|
|
39
|
+
| `@ts-core/common` | Базовые классы и интерфейсы |
|
|
40
|
+
| `@ts-core/frontend` | Фронтенд утилиты |
|
|
41
|
+
| `@ts-core/language` | Поддержка локализации |
|
|
42
|
+
| `moment` | Работа с датами |
|
|
43
|
+
| `numeral` | Форматирование чисел |
|
|
44
|
+
| `ngx-cookie` | Работа с cookies |
|
|
45
|
+
| `interactjs` | Drag-and-drop и resize |
|
|
46
|
+
|
|
47
|
+
## Быстрый старт
|
|
48
|
+
|
|
49
|
+
### Подключение модуля
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { NgModule } from '@angular/core';
|
|
53
|
+
import { VIModule } from '@ts-core/angular';
|
|
54
|
+
|
|
55
|
+
@NgModule({
|
|
56
|
+
imports: [
|
|
57
|
+
VIModule.forRoot({
|
|
58
|
+
loggerLevel: LoggerLevel.ALL,
|
|
59
|
+
languageOptions: {
|
|
60
|
+
defaultLocale: 'ru',
|
|
61
|
+
supportedLocales: ['ru', 'en']
|
|
62
|
+
},
|
|
63
|
+
themeOptions: {
|
|
64
|
+
defaultTheme: 'light',
|
|
65
|
+
supportedThemes: ['light', 'dark']
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
]
|
|
69
|
+
})
|
|
70
|
+
export class AppModule {}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Для standalone компонентов
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { Component } from '@angular/core';
|
|
77
|
+
import { VIModule } from '@ts-core/angular';
|
|
78
|
+
|
|
79
|
+
@Component({
|
|
80
|
+
standalone: true,
|
|
81
|
+
imports: [VIModule]
|
|
82
|
+
})
|
|
83
|
+
export class MyComponent {}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Модули
|
|
87
|
+
|
|
88
|
+
### VIModule (главный модуль)
|
|
89
|
+
|
|
90
|
+
Объединяет все подмодули и предоставляет базовые сервисы:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { VIModule, IVIOptions } from '@ts-core/angular';
|
|
94
|
+
|
|
95
|
+
const options: IVIOptions = {
|
|
96
|
+
loggerLevel: LoggerLevel.DEBUG,
|
|
97
|
+
languageOptions: {
|
|
98
|
+
defaultLocale: 'ru',
|
|
99
|
+
supportedLocales: ['ru', 'en', 'de']
|
|
100
|
+
},
|
|
101
|
+
themeOptions: {
|
|
102
|
+
defaultTheme: 'light',
|
|
103
|
+
supportedThemes: ['light', 'dark']
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
@NgModule({
|
|
108
|
+
imports: [VIModule.forRoot(options)]
|
|
109
|
+
})
|
|
110
|
+
export class AppModule {}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### LanguageModule
|
|
114
|
+
|
|
115
|
+
Модуль локализации:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { LanguageModule } from '@ts-core/angular';
|
|
119
|
+
|
|
120
|
+
@NgModule({
|
|
121
|
+
imports: [LanguageModule]
|
|
122
|
+
})
|
|
123
|
+
export class MyModule {}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### ThemeModule
|
|
127
|
+
|
|
128
|
+
Модуль тем:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { ThemeModule } from '@ts-core/angular';
|
|
132
|
+
|
|
133
|
+
@NgModule({
|
|
134
|
+
imports: [ThemeModule]
|
|
135
|
+
})
|
|
136
|
+
export class MyModule {}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### AssetModule
|
|
140
|
+
|
|
141
|
+
Модуль для работы с ресурсами:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { AssetModule } from '@ts-core/angular';
|
|
145
|
+
|
|
146
|
+
@NgModule({
|
|
147
|
+
imports: [AssetModule]
|
|
148
|
+
})
|
|
149
|
+
export class MyModule {}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### CookieModule
|
|
153
|
+
|
|
154
|
+
Модуль для работы с cookies:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { CookieModule } from '@ts-core/angular';
|
|
158
|
+
|
|
159
|
+
@NgModule({
|
|
160
|
+
imports: [CookieModule]
|
|
161
|
+
})
|
|
162
|
+
export class MyModule {}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Директивы
|
|
166
|
+
|
|
167
|
+
### Интерактивные директивы
|
|
168
|
+
|
|
169
|
+
#### FocusDirective
|
|
170
|
+
|
|
171
|
+
Автоматическая фокусировка элемента:
|
|
172
|
+
|
|
173
|
+
```html
|
|
174
|
+
<input viFocus>
|
|
175
|
+
<input [viFocus]="shouldFocus">
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### ClickToCopyDirective
|
|
179
|
+
|
|
180
|
+
Копирование текста по клику:
|
|
181
|
+
|
|
182
|
+
```html
|
|
183
|
+
<span viClickToCopy="Текст для копирования">Кликните чтобы скопировать</span>
|
|
184
|
+
<span [viClickToCopy]="dynamicText">{{ dynamicText }}</span>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### ClickToSelectDirective
|
|
188
|
+
|
|
189
|
+
Выделение текста по клику:
|
|
190
|
+
|
|
191
|
+
```html
|
|
192
|
+
<input viClickToSelect value="Текст будет выделен">
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### SelectOnFocusDirective
|
|
196
|
+
|
|
197
|
+
Выделение текста при фокусе:
|
|
198
|
+
|
|
199
|
+
```html
|
|
200
|
+
<input viSelectOnFocus value="Выделится при фокусе">
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Директивы прокрутки
|
|
204
|
+
|
|
205
|
+
#### InfiniteScrollDirective
|
|
206
|
+
|
|
207
|
+
Бесконечная прокрутка:
|
|
208
|
+
|
|
209
|
+
```html
|
|
210
|
+
<div viInfiniteScroll (scrolled)="loadMore()">
|
|
211
|
+
<div *ngFor="let item of items">{{ item }}</div>
|
|
212
|
+
</div>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### ScrollDirective
|
|
216
|
+
|
|
217
|
+
Отслеживание прокрутки:
|
|
218
|
+
|
|
219
|
+
```html
|
|
220
|
+
<div viScroll (scrollChanged)="onScroll($event)">
|
|
221
|
+
Контент
|
|
222
|
+
</div>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### AutoScrollBottomDirective
|
|
226
|
+
|
|
227
|
+
Автопрокрутка вниз:
|
|
228
|
+
|
|
229
|
+
```html
|
|
230
|
+
<div viAutoScrollBottom>
|
|
231
|
+
<div *ngFor="let message of messages">{{ message }}</div>
|
|
232
|
+
</div>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
#### ScrollCheckDirective
|
|
236
|
+
|
|
237
|
+
Проверка возможности прокрутки:
|
|
238
|
+
|
|
239
|
+
```html
|
|
240
|
+
<div viScrollCheck (canScrollChanged)="canScroll = $event">
|
|
241
|
+
Контент
|
|
242
|
+
</div>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Директивы размера
|
|
246
|
+
|
|
247
|
+
#### ResizeDirective
|
|
248
|
+
|
|
249
|
+
Отслеживание изменения размера:
|
|
250
|
+
|
|
251
|
+
```html
|
|
252
|
+
<div viResize (resized)="onResize($event)">
|
|
253
|
+
Контент
|
|
254
|
+
</div>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
onResize(event: { width: number; height: number }): void {
|
|
259
|
+
console.log('Новый размер:', event);
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
#### AspectRatioResizeDirective
|
|
264
|
+
|
|
265
|
+
Сохранение пропорций:
|
|
266
|
+
|
|
267
|
+
```html
|
|
268
|
+
<div viAspectRatioResize [ratio]="16/9">
|
|
269
|
+
Видео контейнер
|
|
270
|
+
</div>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Директивы платформы
|
|
274
|
+
|
|
275
|
+
#### IsBrowserDirective
|
|
276
|
+
|
|
277
|
+
Отображение только в браузере:
|
|
278
|
+
|
|
279
|
+
```html
|
|
280
|
+
<div *viIsBrowser>
|
|
281
|
+
Отображается только в браузере (не в SSR)
|
|
282
|
+
</div>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
#### IsServerDirective
|
|
286
|
+
|
|
287
|
+
Отображение только на сервере:
|
|
288
|
+
|
|
289
|
+
```html
|
|
290
|
+
<div *viIsServer>
|
|
291
|
+
Отображается только на сервере (SSR)
|
|
292
|
+
</div>
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Директивы форм
|
|
296
|
+
|
|
297
|
+
#### NullEmptyValueDirective
|
|
298
|
+
|
|
299
|
+
Преобразование пустой строки в null:
|
|
300
|
+
|
|
301
|
+
```html
|
|
302
|
+
<input viNullEmptyValue [(ngModel)]="value">
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### UppercaseValueDirective
|
|
306
|
+
|
|
307
|
+
Автоматический uppercase:
|
|
308
|
+
|
|
309
|
+
```html
|
|
310
|
+
<input viUppercaseValue [(ngModel)]="code">
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Директивы заголовков
|
|
314
|
+
|
|
315
|
+
#### HTMLTitleDirective
|
|
316
|
+
|
|
317
|
+
Установка заголовка страницы:
|
|
318
|
+
|
|
319
|
+
```html
|
|
320
|
+
<div [viHTMLTitle]="pageTitle"></div>
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
#### HTMLContentTitleDirective
|
|
324
|
+
|
|
325
|
+
Установка заголовка из контента элемента:
|
|
326
|
+
|
|
327
|
+
```html
|
|
328
|
+
<h1 viHTMLContentTitle>Заголовок страницы</h1>
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Пайпы
|
|
332
|
+
|
|
333
|
+
### Форматирование дат
|
|
334
|
+
|
|
335
|
+
#### MomentDatePipe
|
|
336
|
+
|
|
337
|
+
```html
|
|
338
|
+
{{ date | viMomentDate }}
|
|
339
|
+
{{ date | viMomentDate:'DD.MM.YYYY' }}
|
|
340
|
+
{{ date | viMomentDate:'DD MMMM YYYY':'ru' }}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
#### MomentTimePipe
|
|
344
|
+
|
|
345
|
+
```html
|
|
346
|
+
{{ date | viMomentTime }}
|
|
347
|
+
{{ date | viMomentTime:'HH:mm:ss' }}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
#### MomentDateFromNowPipe
|
|
351
|
+
|
|
352
|
+
```html
|
|
353
|
+
{{ date | viMomentDateFromNow }}
|
|
354
|
+
<!-- "2 часа назад", "вчера", etc. -->
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
#### MomentDateAdaptivePipe
|
|
358
|
+
|
|
359
|
+
```html
|
|
360
|
+
{{ date | viMomentDateAdaptive }}
|
|
361
|
+
<!-- Показывает время если сегодня, дату если нет -->
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Форматирование чисел
|
|
365
|
+
|
|
366
|
+
#### FinancePipe
|
|
367
|
+
|
|
368
|
+
```html
|
|
369
|
+
{{ 1234567.89 | viFinance }}
|
|
370
|
+
<!-- "1 234 567.89" -->
|
|
371
|
+
|
|
372
|
+
{{ 1234567.89 | viFinance:'0,0.00' }}
|
|
373
|
+
<!-- "1,234,567.89" -->
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
#### TimePipe
|
|
377
|
+
|
|
378
|
+
```html
|
|
379
|
+
{{ 3661 | viTime }}
|
|
380
|
+
<!-- "1:01:01" -->
|
|
381
|
+
|
|
382
|
+
{{ 125 | viTime }}
|
|
383
|
+
<!-- "2:05" -->
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Преобразование текста
|
|
387
|
+
|
|
388
|
+
#### TruncatePipe
|
|
389
|
+
|
|
390
|
+
```html
|
|
391
|
+
{{ longText | viTruncate:50 }}
|
|
392
|
+
{{ longText | viTruncate:50:'...' }}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
#### CamelCasePipe
|
|
396
|
+
|
|
397
|
+
```html
|
|
398
|
+
{{ 'hello world' | viCamelCase }}
|
|
399
|
+
<!-- "helloWorld" -->
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
#### StartCasePipe
|
|
403
|
+
|
|
404
|
+
```html
|
|
405
|
+
{{ 'hello world' | viStartCase }}
|
|
406
|
+
<!-- "Hello World" -->
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Безопасность
|
|
410
|
+
|
|
411
|
+
#### SanitizePipe
|
|
412
|
+
|
|
413
|
+
```html
|
|
414
|
+
<div [innerHTML]="htmlContent | viSanitize:'html'"></div>
|
|
415
|
+
<a [href]="url | viSanitize:'url'">Ссылка</a>
|
|
416
|
+
<div [style.backgroundImage]="'url(' + imageUrl + ')' | viSanitize:'style'"></div>
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Форматирование
|
|
420
|
+
|
|
421
|
+
#### PrettifyPipe
|
|
422
|
+
|
|
423
|
+
```html
|
|
424
|
+
<pre>{{ jsonObject | viPrettify }}</pre>
|
|
425
|
+
<!-- Форматированный JSON -->
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
#### NgModelErrorPipe
|
|
429
|
+
|
|
430
|
+
```html
|
|
431
|
+
<span *ngIf="form.controls.email.errors">
|
|
432
|
+
{{ form.controls.email.errors | viNgModelError }}
|
|
433
|
+
</span>
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## Сервисы
|
|
437
|
+
|
|
438
|
+
### WindowService
|
|
439
|
+
|
|
440
|
+
Абстрактный сервис для управления окнами:
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
import { WindowService, IWindowConfig } from '@ts-core/angular';
|
|
444
|
+
|
|
445
|
+
@Component({...})
|
|
446
|
+
export class MyComponent {
|
|
447
|
+
constructor(private windowService: WindowService) {}
|
|
448
|
+
|
|
449
|
+
openDialog(): void {
|
|
450
|
+
const content = this.windowService.open(MyDialogComponent, {
|
|
451
|
+
data: { message: 'Hello!' },
|
|
452
|
+
width: '400px'
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
content.events.subscribe(event => {
|
|
456
|
+
console.log('Window event:', event);
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
showInfo(): void {
|
|
461
|
+
this.windowService.info('info.message');
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
askQuestion(): void {
|
|
465
|
+
const question = this.windowService.question('confirm.delete');
|
|
466
|
+
question.yesClick.subscribe(() => {
|
|
467
|
+
console.log('Подтверждено');
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### NotificationService
|
|
474
|
+
|
|
475
|
+
Сервис уведомлений:
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
import { NotificationService, NotificationConfig } from '@ts-core/angular';
|
|
479
|
+
|
|
480
|
+
@Component({...})
|
|
481
|
+
export class MyComponent {
|
|
482
|
+
constructor(private notifications: NotificationService) {}
|
|
483
|
+
|
|
484
|
+
showNotification(): void {
|
|
485
|
+
this.notifications.show({
|
|
486
|
+
message: 'Операция выполнена успешно',
|
|
487
|
+
type: 'success',
|
|
488
|
+
duration: 3000
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### BottomSheetService
|
|
495
|
+
|
|
496
|
+
Сервис нижних листов:
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
import { BottomSheetService } from '@ts-core/angular';
|
|
500
|
+
|
|
501
|
+
@Component({...})
|
|
502
|
+
export class MyComponent {
|
|
503
|
+
constructor(private bottomSheet: BottomSheetService) {}
|
|
504
|
+
|
|
505
|
+
openSheet(): void {
|
|
506
|
+
this.bottomSheet.open(MySheetComponent, {
|
|
507
|
+
data: { items: this.items }
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### CookieService
|
|
514
|
+
|
|
515
|
+
Работа с cookies:
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
import { CookieService } from '@ts-core/angular';
|
|
519
|
+
|
|
520
|
+
@Component({...})
|
|
521
|
+
export class MyComponent {
|
|
522
|
+
constructor(private cookies: CookieService) {}
|
|
523
|
+
|
|
524
|
+
setCookie(): void {
|
|
525
|
+
this.cookies.put('token', 'value', {
|
|
526
|
+
expires: new Date(Date.now() + 86400000),
|
|
527
|
+
path: '/'
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
getCookie(): string {
|
|
532
|
+
return this.cookies.get('token');
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
removeCookie(): void {
|
|
536
|
+
this.cookies.remove('token');
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### LocalStorageService
|
|
542
|
+
|
|
543
|
+
Работа с localStorage:
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
import { LocalStorageService } from '@ts-core/angular';
|
|
547
|
+
|
|
548
|
+
@Component({...})
|
|
549
|
+
export class MyComponent {
|
|
550
|
+
constructor(private storage: LocalStorageService) {}
|
|
551
|
+
|
|
552
|
+
saveData(): void {
|
|
553
|
+
this.storage.setItem('key', 'value');
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
loadData(): string {
|
|
557
|
+
return this.storage.getItem('key');
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### PlatformService
|
|
563
|
+
|
|
564
|
+
Определение платформы:
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
import { PlatformService } from '@ts-core/angular';
|
|
568
|
+
|
|
569
|
+
@Component({...})
|
|
570
|
+
export class MyComponent {
|
|
571
|
+
constructor(private platform: PlatformService) {}
|
|
572
|
+
|
|
573
|
+
checkPlatform(): void {
|
|
574
|
+
if (this.platform.isBrowser) {
|
|
575
|
+
// Код для браузера
|
|
576
|
+
}
|
|
577
|
+
if (this.platform.isServer) {
|
|
578
|
+
// Код для SSR
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### FocusManager
|
|
585
|
+
|
|
586
|
+
Управление фокусом:
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
import { FocusManager } from '@ts-core/angular';
|
|
590
|
+
|
|
591
|
+
@Component({...})
|
|
592
|
+
export class MyComponent {
|
|
593
|
+
constructor(private focusManager: FocusManager) {}
|
|
594
|
+
|
|
595
|
+
focusElement(): void {
|
|
596
|
+
this.focusManager.focus(this.inputElement);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### ResizeManager
|
|
602
|
+
|
|
603
|
+
Отслеживание изменения размера окна:
|
|
604
|
+
|
|
605
|
+
```typescript
|
|
606
|
+
import { ResizeManager } from '@ts-core/angular';
|
|
607
|
+
|
|
608
|
+
@Component({...})
|
|
609
|
+
export class MyComponent implements OnInit {
|
|
610
|
+
constructor(private resizeManager: ResizeManager) {}
|
|
611
|
+
|
|
612
|
+
ngOnInit(): void {
|
|
613
|
+
this.resizeManager.events.subscribe(event => {
|
|
614
|
+
console.log('Window resized:', event);
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
## Авторизация
|
|
621
|
+
|
|
622
|
+
### LoginServiceBase
|
|
623
|
+
|
|
624
|
+
Базовый класс для сервиса авторизации:
|
|
625
|
+
|
|
626
|
+
```typescript
|
|
627
|
+
import { LoginServiceBase, LoginServiceBaseEvent } from '@ts-core/angular';
|
|
628
|
+
|
|
629
|
+
@Injectable({ providedIn: 'root' })
|
|
630
|
+
export class AuthService extends LoginServiceBase<AuthEvent, LoginResponse, UserData> {
|
|
631
|
+
constructor(private http: HttpClient) {
|
|
632
|
+
super();
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Реализация абстрактных методов
|
|
636
|
+
protected async loginRequest(credentials: LoginCredentials): Promise<LoginResponse> {
|
|
637
|
+
return this.http.post<LoginResponse>('/api/auth/login', credentials).toPromise();
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
protected async loginSidRequest(): Promise<UserData> {
|
|
641
|
+
return this.http.get<UserData>('/api/auth/me').toPromise();
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
protected async logoutRequest(): Promise<void> {
|
|
645
|
+
await this.http.post('/api/auth/logout', {}).toPromise();
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
protected getSavedSid(): string {
|
|
649
|
+
return localStorage.getItem('sid');
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
protected parseLoginResponse(response: LoginResponse): void {
|
|
653
|
+
this._sid = response.token;
|
|
654
|
+
localStorage.setItem('sid', response.token);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Публичный метод входа
|
|
658
|
+
public login(credentials: LoginCredentials): void {
|
|
659
|
+
this.loginByParam(credentials);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
### Использование AuthService
|
|
665
|
+
|
|
666
|
+
```typescript
|
|
667
|
+
@Component({...})
|
|
668
|
+
export class LoginComponent {
|
|
669
|
+
constructor(private auth: AuthService) {
|
|
670
|
+
// Подписка на события
|
|
671
|
+
this.auth.logined.subscribe(userData => {
|
|
672
|
+
console.log('Пользователь вошёл:', userData);
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
this.auth.logouted.subscribe(() => {
|
|
676
|
+
console.log('Пользователь вышел');
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
login(): void {
|
|
681
|
+
this.auth.login({ email: 'user@example.com', password: '123' });
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
logout(): void {
|
|
685
|
+
this.auth.logout();
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
get isLoggedIn(): boolean {
|
|
689
|
+
return this.auth.isLoggedIn;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
### LoginGuard
|
|
695
|
+
|
|
696
|
+
Защита маршрутов:
|
|
697
|
+
|
|
698
|
+
```typescript
|
|
699
|
+
import { LoginGuard, LoginNotGuard, LoginIfCanGuard } from '@ts-core/angular';
|
|
700
|
+
|
|
701
|
+
const routes: Routes = [
|
|
702
|
+
{
|
|
703
|
+
path: 'dashboard',
|
|
704
|
+
component: DashboardComponent,
|
|
705
|
+
canActivate: [LoginGuard] // Только для авторизованных
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
path: 'login',
|
|
709
|
+
component: LoginComponent,
|
|
710
|
+
canActivate: [LoginNotGuard] // Только для неавторизованных
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
path: 'profile',
|
|
714
|
+
component: ProfileComponent,
|
|
715
|
+
canActivate: [LoginIfCanGuard] // Попытка авторизации если есть токен
|
|
716
|
+
}
|
|
717
|
+
];
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### LoginTokenStorage
|
|
721
|
+
|
|
722
|
+
Хранение токена авторизации:
|
|
723
|
+
|
|
724
|
+
```typescript
|
|
725
|
+
import { LoginTokenStorage } from '@ts-core/angular';
|
|
726
|
+
|
|
727
|
+
@Injectable()
|
|
728
|
+
export class AuthService {
|
|
729
|
+
constructor(private tokenStorage: LoginTokenStorage) {}
|
|
730
|
+
|
|
731
|
+
saveToken(token: string): void {
|
|
732
|
+
this.tokenStorage.set(token);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
getToken(): string {
|
|
736
|
+
return this.tokenStorage.get();
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
clearToken(): void {
|
|
740
|
+
this.tokenStorage.remove();
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
## Управление окнами
|
|
746
|
+
|
|
747
|
+
### WindowBase
|
|
748
|
+
|
|
749
|
+
Базовый класс для содержимого окна:
|
|
750
|
+
|
|
751
|
+
```typescript
|
|
752
|
+
import { WindowBase, IWindowContent } from '@ts-core/angular';
|
|
753
|
+
|
|
754
|
+
@Component({
|
|
755
|
+
template: `
|
|
756
|
+
<div class="window">
|
|
757
|
+
<h2>{{ config.data.title }}</h2>
|
|
758
|
+
<p>{{ config.data.message }}</p>
|
|
759
|
+
<button (click)="close()">Закрыть</button>
|
|
760
|
+
</div>
|
|
761
|
+
`
|
|
762
|
+
})
|
|
763
|
+
export class MyWindowComponent extends WindowBase<MyWindowData> implements IWindowContent<MyWindowData> {
|
|
764
|
+
close(): void {
|
|
765
|
+
this.config.data.result = 'closed';
|
|
766
|
+
this.destroy();
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
interface MyWindowData {
|
|
771
|
+
title: string;
|
|
772
|
+
message: string;
|
|
773
|
+
result?: string;
|
|
774
|
+
}
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
### WindowConfig
|
|
778
|
+
|
|
779
|
+
Конфигурация окна:
|
|
780
|
+
|
|
781
|
+
```typescript
|
|
782
|
+
import { WindowConfig, IWindowConfig } from '@ts-core/angular';
|
|
783
|
+
|
|
784
|
+
const config: IWindowConfig<MyData> = {
|
|
785
|
+
data: { title: 'Заголовок' },
|
|
786
|
+
width: '500px',
|
|
787
|
+
height: 'auto',
|
|
788
|
+
disableClose: false,
|
|
789
|
+
panelClass: 'my-dialog'
|
|
790
|
+
};
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
### QuestionManager
|
|
794
|
+
|
|
795
|
+
Управление вопросами:
|
|
796
|
+
|
|
797
|
+
```typescript
|
|
798
|
+
import { QuestionManager, IQuestion } from '@ts-core/angular';
|
|
799
|
+
|
|
800
|
+
@Component({...})
|
|
801
|
+
export class MyComponent {
|
|
802
|
+
constructor(private questionManager: QuestionManager) {}
|
|
803
|
+
|
|
804
|
+
askConfirmation(): void {
|
|
805
|
+
const question = this.questionManager.question('Вы уверены?');
|
|
806
|
+
|
|
807
|
+
question.yesClick.subscribe(() => {
|
|
808
|
+
this.deleteItem();
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
question.noClick.subscribe(() => {
|
|
812
|
+
console.log('Отменено');
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
## Хранилища данных
|
|
819
|
+
|
|
820
|
+
### ValueStorage
|
|
821
|
+
|
|
822
|
+
Типизированное хранилище:
|
|
823
|
+
|
|
824
|
+
```typescript
|
|
825
|
+
import { ValueStorage, BooleanValueStorage, DateValueStorage, JSONValueStorage } from '@ts-core/angular';
|
|
826
|
+
|
|
827
|
+
// Boolean хранилище
|
|
828
|
+
const isDarkMode = new BooleanValueStorage(localStorage, 'darkMode', false);
|
|
829
|
+
isDarkMode.value = true;
|
|
830
|
+
console.log(isDarkMode.value); // true
|
|
831
|
+
|
|
832
|
+
// Date хранилище
|
|
833
|
+
const lastVisit = new DateValueStorage(localStorage, 'lastVisit');
|
|
834
|
+
lastVisit.value = new Date();
|
|
835
|
+
console.log(lastVisit.value); // Date object
|
|
836
|
+
|
|
837
|
+
// JSON хранилище
|
|
838
|
+
interface UserSettings {
|
|
839
|
+
theme: string;
|
|
840
|
+
language: string;
|
|
841
|
+
}
|
|
842
|
+
const settings = new JSONValueStorage<UserSettings>(localStorage, 'settings', {
|
|
843
|
+
theme: 'light',
|
|
844
|
+
language: 'ru'
|
|
845
|
+
});
|
|
846
|
+
settings.value = { theme: 'dark', language: 'en' };
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
## Примеры использования
|
|
850
|
+
|
|
851
|
+
### Полная настройка приложения
|
|
852
|
+
|
|
853
|
+
```typescript
|
|
854
|
+
// app.module.ts
|
|
855
|
+
import { NgModule } from '@angular/core';
|
|
856
|
+
import { BrowserModule } from '@angular/platform-browser';
|
|
857
|
+
import { VIModule, IVIOptions } from '@ts-core/angular';
|
|
858
|
+
import { LoggerLevel } from '@ts-core/common';
|
|
859
|
+
|
|
860
|
+
const viOptions: IVIOptions = {
|
|
861
|
+
loggerLevel: LoggerLevel.DEBUG,
|
|
862
|
+
languageOptions: {
|
|
863
|
+
defaultLocale: 'ru',
|
|
864
|
+
supportedLocales: ['ru', 'en', 'de'],
|
|
865
|
+
loadUrl: '/api/language'
|
|
866
|
+
},
|
|
867
|
+
themeOptions: {
|
|
868
|
+
defaultTheme: 'light',
|
|
869
|
+
supportedThemes: ['light', 'dark', 'auto']
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
@NgModule({
|
|
874
|
+
imports: [
|
|
875
|
+
BrowserModule,
|
|
876
|
+
VIModule.forRoot(viOptions)
|
|
877
|
+
],
|
|
878
|
+
bootstrap: [AppComponent]
|
|
879
|
+
})
|
|
880
|
+
export class AppModule {}
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
### Компонент с локализацией
|
|
884
|
+
|
|
885
|
+
```typescript
|
|
886
|
+
import { Component } from '@angular/core';
|
|
887
|
+
import { LanguageService } from '@ts-core/frontend';
|
|
888
|
+
|
|
889
|
+
@Component({
|
|
890
|
+
selector: 'app-greeting',
|
|
891
|
+
template: `
|
|
892
|
+
<h1>{{ 'greeting.title' | viLanguage }}</h1>
|
|
893
|
+
<p>{{ 'greeting.message' | viLanguage:{ name: userName } }}</p>
|
|
894
|
+
|
|
895
|
+
<select (change)="changeLanguage($event.target.value)">
|
|
896
|
+
<option *ngFor="let locale of locales" [value]="locale">
|
|
897
|
+
{{ locale }}
|
|
898
|
+
</option>
|
|
899
|
+
</select>
|
|
900
|
+
`
|
|
901
|
+
})
|
|
902
|
+
export class GreetingComponent {
|
|
903
|
+
userName = 'Иван';
|
|
904
|
+
locales = ['ru', 'en', 'de'];
|
|
905
|
+
|
|
906
|
+
constructor(private language: LanguageService) {}
|
|
907
|
+
|
|
908
|
+
changeLanguage(locale: string): void {
|
|
909
|
+
this.language.setLocale(locale);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
### Компонент с темами
|
|
915
|
+
|
|
916
|
+
```typescript
|
|
917
|
+
import { Component } from '@angular/core';
|
|
918
|
+
import { ThemeService } from '@ts-core/frontend';
|
|
919
|
+
|
|
920
|
+
@Component({
|
|
921
|
+
selector: 'app-theme-toggle',
|
|
922
|
+
template: `
|
|
923
|
+
<button (click)="toggleTheme()">
|
|
924
|
+
{{ isDark ? 'Светлая тема' : 'Тёмная тема' }}
|
|
925
|
+
</button>
|
|
926
|
+
|
|
927
|
+
<div [viThemeStyle]="{ color: 'primary' }">
|
|
928
|
+
Текст в цвете темы
|
|
929
|
+
</div>
|
|
930
|
+
`
|
|
931
|
+
})
|
|
932
|
+
export class ThemeToggleComponent {
|
|
933
|
+
constructor(private theme: ThemeService) {}
|
|
934
|
+
|
|
935
|
+
get isDark(): boolean {
|
|
936
|
+
return this.theme.current === 'dark';
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
toggleTheme(): void {
|
|
940
|
+
this.theme.setTheme(this.isDark ? 'light' : 'dark');
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
### Список с бесконечной прокруткой
|
|
946
|
+
|
|
947
|
+
```typescript
|
|
948
|
+
import { Component } from '@angular/core';
|
|
949
|
+
|
|
950
|
+
@Component({
|
|
951
|
+
selector: 'app-infinite-list',
|
|
952
|
+
template: `
|
|
953
|
+
<div class="list" viInfiniteScroll (scrolled)="loadMore()">
|
|
954
|
+
<div *ngFor="let item of items" class="item">
|
|
955
|
+
{{ item.name }}
|
|
956
|
+
</div>
|
|
957
|
+
<div *ngIf="isLoading" class="loading">Загрузка...</div>
|
|
958
|
+
</div>
|
|
959
|
+
`
|
|
960
|
+
})
|
|
961
|
+
export class InfiniteListComponent {
|
|
962
|
+
items: any[] = [];
|
|
963
|
+
isLoading = false;
|
|
964
|
+
page = 1;
|
|
965
|
+
|
|
966
|
+
async loadMore(): Promise<void> {
|
|
967
|
+
if (this.isLoading) return;
|
|
968
|
+
|
|
969
|
+
this.isLoading = true;
|
|
970
|
+
const newItems = await this.loadPage(this.page++);
|
|
971
|
+
this.items = [...this.items, ...newItems];
|
|
972
|
+
this.isLoading = false;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
private async loadPage(page: number): Promise<any[]> {
|
|
976
|
+
// Загрузка данных с сервера
|
|
977
|
+
return fetch(`/api/items?page=${page}`).then(r => r.json());
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
## API Reference
|
|
983
|
+
|
|
984
|
+
### VIModule
|
|
985
|
+
|
|
986
|
+
| Метод | Описание |
|
|
987
|
+
|-------|----------|
|
|
988
|
+
| `forRoot(options?)` | Создать модуль с настройками |
|
|
989
|
+
|
|
990
|
+
### IVIOptions
|
|
991
|
+
|
|
992
|
+
| Свойство | Тип | Описание |
|
|
993
|
+
|----------|-----|----------|
|
|
994
|
+
| `loggerLevel` | `LoggerLevel` | Уровень логирования |
|
|
995
|
+
| `languageOptions` | `ILanguageServiceOptions` | Настройки локализации |
|
|
996
|
+
| `themeOptions` | `IThemeServiceOptions` | Настройки тем |
|
|
997
|
+
|
|
998
|
+
### WindowService
|
|
999
|
+
|
|
1000
|
+
| Метод | Описание |
|
|
1001
|
+
|-------|----------|
|
|
1002
|
+
| `open(component, config)` | Открыть окно |
|
|
1003
|
+
| `get(id)` | Получить окно по ID |
|
|
1004
|
+
| `has(id)` | Проверить наличие окна |
|
|
1005
|
+
| `close(id)` | Закрыть окно |
|
|
1006
|
+
| `closeAll()` | Закрыть все окна |
|
|
1007
|
+
| `info(translationId, translation?, options?)` | Показать информационное окно |
|
|
1008
|
+
| `question(translationId, translation?, options?)` | Показать окно подтверждения |
|
|
1009
|
+
|
|
1010
|
+
### LoginServiceBase
|
|
1011
|
+
|
|
1012
|
+
| Свойство/Метод | Тип | Описание |
|
|
1013
|
+
|----------------|-----|----------|
|
|
1014
|
+
| `isLoggedIn` | `boolean` | Авторизован ли пользователь |
|
|
1015
|
+
| `isLoading` | `boolean` | Идёт процесс авторизации |
|
|
1016
|
+
| `sid` | `string` | Текущий токен сессии |
|
|
1017
|
+
| `loginData` | `V` | Данные пользователя |
|
|
1018
|
+
| `login(param)` | `void` | Выполнить вход |
|
|
1019
|
+
| `logout()` | `Promise<void>` | Выполнить выход |
|
|
1020
|
+
| `logined` | `Observable<V>` | Событие успешного входа |
|
|
1021
|
+
| `logouted` | `Observable<void>` | Событие выхода |
|
|
1022
|
+
|
|
1023
|
+
## Связанные пакеты
|
|
1024
|
+
|
|
1025
|
+
| Пакет | Описание |
|
|
1026
|
+
|-------|----------|
|
|
1027
|
+
| `@ts-core/angular-material` | Material компоненты для Angular |
|
|
1028
|
+
| `@ts-core/frontend` | Базовые фронтенд утилиты |
|
|
1029
|
+
| `@ts-core/common` | Общие классы и интерфейсы |
|
|
1030
|
+
| `@ts-core/language` | Поддержка локализации |
|
|
1031
|
+
|
|
1032
|
+
## Автор
|
|
1033
|
+
|
|
1034
|
+
**Renat Gubaev** — [renat.gubaev@gmail.com](mailto:renat.gubaev@gmail.com)
|
|
1035
|
+
|
|
1036
|
+
- GitHub: [ManhattanDoctor](https://github.com/ManhattanDoctor)
|
|
1037
|
+
- Репозиторий: [ts-core-frontend-angular](https://github.com/ManhattanDoctor/ts-core-frontend-angular)
|
|
1038
|
+
|
|
1039
|
+
## Лицензия
|
|
1040
|
+
|
|
1041
|
+
ISC
|