diff --git a/angular.json b/angular.json index b255a7e..d926e78 100644 --- a/angular.json +++ b/angular.json @@ -112,6 +112,7 @@ }, "defaultProject": "Angular-learnjs-130622", "cli": { + "analytics": false, "defaultCollection": "@angular-eslint/schematics" } } diff --git a/src/app/app.component.html b/src/app/app.component.html index 3b61242..8ab0559 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -2,7 +2,11 @@ - + + + + + Item 1 @@ -11,4 +15,4 @@ - \ No newline at end of file + diff --git a/src/app/components/sidenav/sidenav.component.ts b/src/app/components/sidenav/sidenav.component.ts index eed51ce..d9610ac 100644 --- a/src/app/components/sidenav/sidenav.component.ts +++ b/src/app/components/sidenav/sidenav.component.ts @@ -45,5 +45,19 @@ export class SidenavComponent implements OnInit { private insertList(template: TemplateRef) { this.drawerViewPortElement.clear(); this.drawerViewPortElement.createEmbeddedView(template); + + // setTimeout(() => { + // this.drawerViewPortElement.clear(); + // this.drawerViewPortElement.createEmbeddedView(template, + // { + // name: 'Egor', + // $implicit: 'Angular LearnJs', + // user: { + // age: '35', + // } + // }); + // + // this.changeDetectorRef.markForCheck(); + // }, 3000) } } diff --git a/src/app/pages/products-list/product-card/product-card.component.html b/src/app/pages/products-list/product-card/product-card.component.html index 8dec404..c36c7bb 100644 --- a/src/app/pages/products-list/product-card/product-card.component.html +++ b/src/app/pages/products-list/product-card/product-card.component.html @@ -3,20 +3,22 @@ {{product?.name}} {{product?.price}} Р -
- - Product photo - -
+ +
+ + Product photo + +
+ - \ No newline at end of file + diff --git a/src/app/pages/products-list/product-card/product-card.component.less b/src/app/pages/products-list/product-card/product-card.component.less index ce7648f..528d053 100644 --- a/src/app/pages/products-list/product-card/product-card.component.less +++ b/src/app/pages/products-list/product-card/product-card.component.less @@ -3,7 +3,8 @@ } .card { - width: 100%; + width: 500px; + margin-bottom: 10px; box-sizing: border-box; display: flex; flex-direction: column; diff --git a/src/app/pages/products-list/product-card/product-card.component.ts b/src/app/pages/products-list/product-card/product-card.component.ts index afab068..3c84dc5 100644 --- a/src/app/pages/products-list/product-card/product-card.component.ts +++ b/src/app/pages/products-list/product-card/product-card.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { IProduct } from '../../../shared/products/product.interface'; @Component({ @@ -7,8 +7,8 @@ import { IProduct } from '../../../shared/products/product.interface'; styleUrls: ['./product-card.component.less'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ProductCardComponent { - @Input() product!: IProduct; +export class ProductCardComponent implements OnInit { + @Input() product: IProduct | undefined; @Output() buyProduct = new EventEmitter(); @@ -17,4 +17,20 @@ export class ProductCardComponent { this.buyProduct.emit(); } + + // prev!: () => void; + // next!: () => void; + // + // onRegisterPrev(fn: () => void) { + // this.prev = fn; + // } + // + + // get imgSrc(): string { + // return this.product?.images[0].url || ''; + // } + + ngOnInit() { + console.log(this.product, 'ProductCardComponent created'); + } } diff --git a/src/app/pages/products-list/products.component.html b/src/app/pages/products-list/products.component.html index 6e57a9b..6771372 100644 --- a/src/app/pages/products-list/products.component.html +++ b/src/app/pages/products-list/products.component.html @@ -1,5 +1,61 @@ - - - - +
+ + + + + + + + + + {{content}} + {{product}} + + + + + + + + + + + + + +
+ + + + + diff --git a/src/app/pages/products-list/products.component.less b/src/app/pages/products-list/products.component.less index 885c68c..e772d39 100644 --- a/src/app/pages/products-list/products.component.less +++ b/src/app/pages/products-list/products.component.less @@ -15,3 +15,17 @@ // color: red; // background-color: red; // } + +.spinner { + margin: 0 auto; +} + +.pagination { + display: flex; + align-items: center; + &__button { + &--active { + color: red; + } + } +} diff --git a/src/app/pages/products-list/products.component.ts b/src/app/pages/products-list/products.component.ts index 703fd21..e04e3cd 100644 --- a/src/app/pages/products-list/products.component.ts +++ b/src/app/pages/products-list/products.component.ts @@ -1,5 +1,7 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { productsMock } from '../../shared/products/products.mock'; +import { IProduct } from '../../shared/products/product.interface'; +import { Subject } from 'rxjs'; @Component({ selector: 'app-products', @@ -7,19 +9,46 @@ import { productsMock } from '../../shared/products/products.mock'; styleUrls: ['./products.component.less'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ProductsComponent { - products = productsMock; +export class ProductsComponent implements OnInit { + products: IProduct[] | undefined = undefined; - // isHovered = true; + items = [2, 34, 5, 6, 7, 3, 3, 5, 4]; + constructor(private changeDetectorRef: ChangeDetectorRef) {} - // constructor( - // private changeDetectorRef: ChangeDetectorRef, - // ) {} + ngOnInit() { + // setTimeout(() => { + // this.products = productsMock.slice(0, 4); + // this.changeDetectorRef.markForCheck(); + // }, 2000) + // + // setTimeout(() => { + // console.log(' ') + // }, 3000) - // ngOnInit() { - // setTimeout(() => { - // this.isHovered = false; - // this.changeDetectorRef.markForCheck(); - // }, 3000) - // } + console.log(this.items.slice(2, 4), 'slice'); + setTimeout(() => { + this.products = productsMock.map((item) => ({ ...item })); + this.changeDetectorRef.markForCheck(); + }, 2000); + // + // setTimeout(() => { + // console.log(' ') + // }, 6000) + + setTimeout(() => { + // this.products = productsMock.map(item => ({...item, name: item.name + '1245'})); + // this.products?.forEach((_, index: number) => { + // if (this.products) { + // this.products[index].name = '1345'; + // } + // }) + this.changeDetectorRef.markForCheck(); + }, 3000); + } + + trackBy(_: number, item: IProduct) { + return item._id; + // return item.name; + // return item; + } } diff --git a/src/app/pages/products-list/products.module.ts b/src/app/pages/products-list/products.module.ts index 7cc3a97..7e9036e 100644 --- a/src/app/pages/products-list/products.module.ts +++ b/src/app/pages/products-list/products.module.ts @@ -6,10 +6,24 @@ import { MatCardModule } from '@angular/material/card'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { NgClassModule } from '../../shared/ng-class/ng-class.module'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { ForModule } from '../../shared/for/for.module'; +import { CarouselModule } from '../../shared/carousel/carousel.module'; +import { PaginationModule } from '../../shared/pagination/pagination.module'; @NgModule({ declarations: [ProductsComponent, ProductCardComponent], - imports: [CommonModule, MatCardModule, MatButtonModule, MatIconModule, NgClassModule], + imports: [ + CommonModule, + MatCardModule, + MatButtonModule, + MatIconModule, + NgClassModule, + MatProgressSpinnerModule, + ForModule, + CarouselModule, + PaginationModule, + ], exports: [ProductsComponent], }) export class ProductsModule {} diff --git a/src/app/shared/carousel/carousel.directive.spec.ts b/src/app/shared/carousel/carousel.directive.spec.ts new file mode 100644 index 0000000..9675cbe --- /dev/null +++ b/src/app/shared/carousel/carousel.directive.spec.ts @@ -0,0 +1,8 @@ +import { CarouselDirective } from './carousel.directive'; + +describe('CarouselDirective', () => { + it('should create an instance', () => { + const directive = new CarouselDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/src/app/shared/carousel/carousel.directive.ts b/src/app/shared/carousel/carousel.directive.ts new file mode 100644 index 0000000..ac1785d --- /dev/null +++ b/src/app/shared/carousel/carousel.directive.ts @@ -0,0 +1,138 @@ +import { Directive, EventEmitter, Input, OnInit, Output, TemplateRef, ViewContainerRef } from '@angular/core'; +import { BehaviorSubject, filter, map, withLatestFrom } from 'rxjs'; + +interface ICarouselDirective { + $implicit: T; + index: number; + prev: () => void; + next: () => void; +} + +@Directive({ + selector: '[appCarousel]', +}) +export class CarouselDirective implements OnInit { + @Input() set appCarouselOf(items: T[] | undefined) { + console.log(items, 'item in from component'); + // if (!(items && items.length)) { + // this.viewContainerRef.clear(); + // } + + if (!items?.length) { + this.viewContainerRef.clear(); + + return; + } + + // this.items = items; + + this.items$.next(items); + this.currentIndex$.next(0); + + // this.currentIndex = 0; + // this.insertTemplateWithCurrentIndex(); + } + + // + // @Output() emitNext = new EventEmitter<() => void>(); + // @Output() emitPrev = new EventEmitter<() => void>(); + + // private items: T[] | undefined = undefined; + private readonly items$ = new BehaviorSubject(undefined); + // private currentIndex: number = 0; + + private readonly currentIndex$ = new BehaviorSubject(0); + + constructor(private templateRef: TemplateRef>, private viewContainerRef: ViewContainerRef) {} + + ngOnInit() { + this.listenCurrentIndexChange(); + // this.emitPrev.emit(this.prev.bind(this)); + // this.emitNext.emit(() => { + // this.next(); + // }); + } + + private listenCurrentIndexChange() { + this.currentIndex$ + .pipe( + withLatestFrom(this.items$.pipe(filter(Boolean))), + map(([index, items]) => this.getCurrentContext(index, items)) + ) + .subscribe((context) => { + // this.insertTemplateWithCurrentIndex(); + this.viewContainerRef.clear(); + this.viewContainerRef.createEmbeddedView(this.templateRef, context); + }); + } + + // метод вставки темплейта с выбранным индексом + // private insertTemplateWithCurrentIndex() { + // this.viewContainerRef.clear(); // зачищаем viewContainer. + // this.viewContainerRef.createEmbeddedView(this.templateRef, this.getCurrentContext(this.currentIndex, this.items as T[])); + // } + + private getCurrentContext(index: number, items: T[]): ICarouselDirective { + return { + index, + $implicit: items[index], // items c необходимым индексом. + prev: this.prev.bind(this), + next: () => { + this.next(); + }, + }; + } + + private next() { + // const nextIndex = this.currentIndex + 1; + // const itemsLength = this.items?.length; + + const nextIndex = this.currentIndex$.value + 1; + const itemsLength = this.items$.value?.length; + + console.log('next method'); + + if (!itemsLength) { + return; + } + + // short version + // this.currentIndex = nextIndex < itemsLength ? nextIndex : 0; + this.currentIndex$.next(nextIndex < itemsLength ? nextIndex : 0); + + // if (nextIndex < itemsLength) { + // this.currentIndex = nextIndex; + // this.insertTemplateWithCurrentIndex(); + // return; + // } + // this.currentIndex = 0; + // this.insertTemplateWithCurrentIndex(); + } + + private prev() { + // const prevIndex = this.currentIndex - 1; + // const itemsLength = this.items?.length; + + const prevIndex = this.currentIndex$.value - 1; + const itemsLength = this.items$.value?.length; + + console.log('prev method'); + + if (!itemsLength) { + return; + } + + // short version + // this.currentIndex = prevIndex >= 0 ? prevIndex : itemsLength - 1; + + this.currentIndex$.next(prevIndex >= 0 ? prevIndex : itemsLength - 1); + // if (prevIndex >= 0) { + // this.currentIndex = prevIndex; + // this.insertTemplateWithCurrentIndex(); + // return; + // } + + // this.currentIndex = itemsLength - 1; + // this.insertTemplateWithCurrentIndex(); + } +} diff --git a/src/app/shared/carousel/carousel.module.ts b/src/app/shared/carousel/carousel.module.ts new file mode 100644 index 0000000..2a3ac43 --- /dev/null +++ b/src/app/shared/carousel/carousel.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { CarouselDirective } from './carousel.directive'; + +@NgModule({ + declarations: [CarouselDirective], + imports: [CommonModule], + exports: [CarouselDirective], +}) +export class CarouselModule {} diff --git a/src/app/shared/for/for.directive.spec.ts b/src/app/shared/for/for.directive.spec.ts new file mode 100644 index 0000000..53d9e76 --- /dev/null +++ b/src/app/shared/for/for.directive.spec.ts @@ -0,0 +1,8 @@ +import { ForDirective } from './for.directive'; + +describe('ForDirective', () => { + it('should create an instance', () => { + const directive = new ForDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/src/app/shared/for/for.directive.ts b/src/app/shared/for/for.directive.ts new file mode 100644 index 0000000..5dcee79 --- /dev/null +++ b/src/app/shared/for/for.directive.ts @@ -0,0 +1,25 @@ +import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'; + +interface IContextAppFor { + $implicit: T; +} + +// IContextAppFor => $implicit: Product; +// IContextAppFor => $implicit: string; + +@Directive({ + selector: '[appFor]', +}) +export class ForDirective { + @Input() set appForOf(array: T[]) { + this.viewContainerRef.clear(); + if (array) { + array.forEach((item) => { + const context: IContextAppFor = { $implicit: item }; + this.viewContainerRef.createEmbeddedView(this.templateRef, context); + }); + } + } + + constructor(private templateRef: TemplateRef>, private viewContainerRef: ViewContainerRef) {} +} diff --git a/src/app/shared/for/for.module.ts b/src/app/shared/for/for.module.ts new file mode 100644 index 0000000..f03967c --- /dev/null +++ b/src/app/shared/for/for.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ForDirective } from './for.directive'; + +@NgModule({ + declarations: [ForDirective], + imports: [CommonModule], + exports: [ForDirective], +}) +export class ForModule {} diff --git a/src/app/shared/pagination/interfaces/pagination-directive.interface.ts b/src/app/shared/pagination/interfaces/pagination-directive.interface.ts new file mode 100644 index 0000000..6f41b03 --- /dev/null +++ b/src/app/shared/pagination/interfaces/pagination-directive.interface.ts @@ -0,0 +1,8 @@ +export interface IPaginationDirective { + $implicit: T | T[]; + index: number; + prev: () => void; + next: () => void; + allIndexes: number[]; + selectIndex: (index: number) => void; +} diff --git a/src/app/shared/pagination/pagination.directive.spec.ts b/src/app/shared/pagination/pagination.directive.spec.ts new file mode 100644 index 0000000..8ce8792 --- /dev/null +++ b/src/app/shared/pagination/pagination.directive.spec.ts @@ -0,0 +1,8 @@ +import { PaginationDirective } from './pagination.directive'; + +describe('PaginationDirective', () => { + it('should create an instance', () => { + const directive = new PaginationDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/src/app/shared/pagination/pagination.directive.ts b/src/app/shared/pagination/pagination.directive.ts new file mode 100644 index 0000000..655ed6a --- /dev/null +++ b/src/app/shared/pagination/pagination.directive.ts @@ -0,0 +1,133 @@ +import { + Directive, + EventEmitter, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, + TemplateRef, + ViewContainerRef, +} from '@angular/core'; +import { IPaginationDirective } from './interfaces/pagination-directive.interface'; + +@Directive({ + selector: '[appPagination]', +}) +export class PaginationDirective implements OnInit, OnChanges { + @Input() appPaginationOf: T[] | undefined; + @Input('appPaginationElementSize') elementSize = 1; + @Output() emitSelectedIndex = new EventEmitter<() => void>(); + + private items: T[] | Array | undefined = undefined; + private currentIndex: number = 0; + // private allItems: T[] | undefined = undefined; + + constructor( + private templateRef: TemplateRef>, + private viewContainerRef: ViewContainerRef + ) {} + + ngOnInit() { + console.log(this.appPaginationOf, 'appPaginationOf'); + } + + ngOnChanges({ appPaginationOf, appPaginationElementSize }: SimpleChanges) { + if (appPaginationOf || appPaginationElementSize) { + if (!(this.appPaginationOf && this.appPaginationOf.length)) { + this.viewContainerRef.clear(); + } + } + + this.items = this.elementSize > 1 ? this.getGroupedItemsListByElementsSize() : this.appPaginationOf; + this.currentIndex = 0; + + this.insertTemplateWithCurrentIndex(); + } + + private getGroupedItemsListByElementsSize() { + return this.appPaginationOf?.reduce( + (acc: Array, item: T): Array => { + const lastGroupIndex = acc.length - 1; + const lastGroupLength = acc[lastGroupIndex].length; + + if (lastGroupLength === this.elementSize) { + return [...acc, [item]]; + } + + acc[lastGroupIndex].push(item); + return [...acc]; + }, + [[]] + ); + } + + private insertTemplateWithCurrentIndex() { + this.viewContainerRef.clear(); + this.viewContainerRef.createEmbeddedView( + this.templateRef, + this.getCurrentContext(this.currentIndex, this.items as T[]) + ); + } + + private getCurrentContext(index: number, items: T[]): IPaginationDirective { + return { + index, + $implicit: items[index], + prev: () => { + this.prev(); + }, + next: () => { + this.next(); + }, + allIndexes: items.map((_, index) => index), + selectIndex: (index: number) => { + this.activeSelectedIndex(index); + }, + }; + } + + private activeSelectedIndex(index: number) { + // this.emitSelectedIndex(index) + console.log(index, 'index from template'); + } + + prev() { + const prevIndex = this.currentIndex - 1; + const itemsLength = this.items?.length; + + if (!itemsLength) { + return; + } + + if (prevIndex >= 0) { + this.currentIndex = prevIndex; + this.insertTemplateWithCurrentIndex(); + return; + } + + // если он у нас prevIndex меньше нуля, то есть если это бесконечная itemsLength - 1, то есть последний индекс массива. + this.currentIndex = itemsLength - 1; + + this.insertTemplateWithCurrentIndex(); + } + + next() { + const nextIndex = this.currentIndex + 1; + const itemsLength = this.items?.length; + + if (!itemsLength) { + return; + } + + if (nextIndex < itemsLength) { + this.currentIndex = nextIndex; + this.insertTemplateWithCurrentIndex(); + return; + } + + // когда следущуюий индек больше или равен длине массима тогда зануляем currentIndex. + this.currentIndex = 0; + this.insertTemplateWithCurrentIndex(); + } +} diff --git a/src/app/shared/pagination/pagination.module.ts b/src/app/shared/pagination/pagination.module.ts new file mode 100644 index 0000000..0a61525 --- /dev/null +++ b/src/app/shared/pagination/pagination.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { PaginationDirective } from './pagination.directive'; + +@NgModule({ + declarations: [PaginationDirective], + imports: [CommonModule], + exports: [PaginationDirective], +}) +export class PaginationModule {}