@ts-core/angular 21.0.1 → 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 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