diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index abc5bb8..214064f 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: - node-version: ['20', '18.13.0'] + node-version: ['20', '18.19.1'] steps: - uses: actions/checkout@v4 diff --git a/apps/ngu-carousel-example/server.ts b/apps/ngu-carousel-example/server.ts index 29f3e87..d4f3797 100644 --- a/apps/ngu-carousel-example/server.ts +++ b/apps/ngu-carousel-example/server.ts @@ -1,16 +1,16 @@ -import 'zone.js'; - import { APP_BASE_HREF } from '@angular/common'; import { CommonEngine } from '@angular/ssr'; import express from 'express'; -import { join } from 'node:path'; -import { AppServerModule } from './src/main.server'; +import { fileURLToPath } from 'node:url'; +import { dirname, join, resolve } from 'node:path'; +import bootstrap from './src/main.server'; // The Express app is exported so that it can be used by serverless Functions. export function app(): express.Express { const server = express(); - const browserDistFolder = join(process.cwd(), 'dist/apps/ngu-carousel-example/browser'); - const pathToIndexHtml = join(browserDistFolder, 'index.html'); + const serverDistFolder = dirname(fileURLToPath(import.meta.url)); + const browserDistFolder = resolve(serverDistFolder, '../browser'); + const indexHtml = join(serverDistFolder, 'index.server.html'); const commonEngine = new CommonEngine(); @@ -21,21 +21,23 @@ export function app(): express.Express { // server.get('/api/**', (req, res) => { }); // Serve static files from /browser server.get( - '*.*', + '**', express.static(browserDistFolder, { - maxAge: '1y' + maxAge: '1y', + index: 'index.html' }) ); // All regular routes use the Angular engine - server.get('*', (req, res, next) => { + server.get('**', (req, res, next) => { const { protocol, originalUrl, baseUrl, headers } = req; commonEngine .render({ - bootstrap: AppServerModule, - documentFilePath: pathToIndexHtml, + bootstrap, + documentFilePath: indexHtml, url: `${protocol}://${headers.host}${originalUrl}`, + publicPath: browserDistFolder, providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }] }) .then(html => res.send(html)) @@ -55,14 +57,4 @@ function run(): void { }); } -// Webpack will replace 'require' with '__webpack_require__' -// '__non_webpack_require__' is a proxy to Node 'require' -// The below code is to ensure that the server is run only when not requiring the bundle. -declare const __non_webpack_require__: NodeRequire; -const mainModule = __non_webpack_require__.main; -const moduleFilename = mainModule && mainModule.filename || ''; -if (moduleFilename === __filename || moduleFilename.includes('iisnode')) { - run(); -} - -export * from './src/main.server'; +run(); diff --git a/apps/ngu-carousel-example/src/app/app-server.config.ts b/apps/ngu-carousel-example/src/app/app-server.config.ts index 8e4b9ea..2149731 100644 --- a/apps/ngu-carousel-example/src/app/app-server.config.ts +++ b/apps/ngu-carousel-example/src/app/app-server.config.ts @@ -1,9 +1,9 @@ -import { ApplicationConfig } from '@angular/core'; -import { provideAnimations } from '@angular/platform-browser/animations'; -import { provideRouter } from '@angular/router'; +import { mergeApplicationConfig, ApplicationConfig } from '@angular/core'; import { provideServerRendering } from '@angular/platform-server'; -import { APP_ROUTES } from './app.routes'; +import { appConfig } from './app.config'; -export const appServerConfig: ApplicationConfig = { - providers: [provideRouter(APP_ROUTES), provideAnimations(), provideServerRendering()] +const serverConfig: ApplicationConfig = { + providers: [provideServerRendering()] }; + +export const config = mergeApplicationConfig(appConfig, serverConfig); diff --git a/apps/ngu-carousel-example/src/app/app.component.ts b/apps/ngu-carousel-example/src/app/app.component.ts index a29de60..e627eae 100644 --- a/apps/ngu-carousel-example/src/app/app.component.ts +++ b/apps/ngu-carousel-example/src/app/app.component.ts @@ -1,13 +1,4 @@ -import { - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component -} from '@angular/core'; -import { NguCarouselConfig } from '@ngu/carousel'; -import { interval, Observable } from 'rxjs'; -import { map, startWith, take } from 'rxjs/operators'; -import { slider } from './slide-animation'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MainNavComponent } from './main-nav/main-nav.component'; @Component({ @@ -15,45 +6,7 @@ import { MainNavComponent } from './main-nav/main-nav.component'; selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], - animations: [slider], changeDetection: ChangeDetectionStrategy.OnPush, imports: [MainNavComponent] }) -export class AppComponent implements AfterViewInit { - images = ['assets/bg.jpg', 'assets/car.png', 'assets/canberra.jpg', 'assets/holi.jpg']; - - public carouselTileItems$: Observable; - public carouselTileConfig: NguCarouselConfig = { - grid: { xs: 1, sm: 1, md: 1, lg: 5, all: 0 }, - speed: 250, - point: { - visible: true - }, - touch: true, - loop: true, - interval: { timing: 1500 }, - animation: 'lazy' - }; - tempData: any[]; - - constructor(private cdr: ChangeDetectorRef) { - this.tempData = []; - - this.carouselTileItems$ = interval(500).pipe( - startWith(-1), - take(30), - map(() => { - const data = (this.tempData = [ - ...this.tempData, - this.images[Math.floor(Math.random() * this.images.length)] - ]); - - return data; - }) - ); - } - - ngAfterViewInit() { - this.cdr.detectChanges(); - } -} +export class AppComponent {} diff --git a/apps/ngu-carousel-example/src/app/banner-vertical/banner-vertical.component.html b/apps/ngu-carousel-example/src/app/banner-vertical/banner-vertical.component.html index cb7137b..f3498bc 100644 --- a/apps/ngu-carousel-example/src/app/banner-vertical/banner-vertical.component.html +++ b/apps/ngu-carousel-example/src/app/banner-vertical/banner-vertical.component.html @@ -1,9 +1,5 @@

Banner Vertical

- + diff --git a/apps/ngu-carousel-example/src/app/banner-vertical/banner-vertical.component.ts b/apps/ngu-carousel-example/src/app/banner-vertical/banner-vertical.component.ts index 5e86426..0d1e90b 100644 --- a/apps/ngu-carousel-example/src/app/banner-vertical/banner-vertical.component.ts +++ b/apps/ngu-carousel-example/src/app/banner-vertical/banner-vertical.component.ts @@ -1,9 +1,6 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, afterNextRender, signal } from '@angular/core'; import { NguCarouselConfig } from '@ngu/carousel'; -import { interval, Observable } from 'rxjs'; -import { map, startWith, take } from 'rxjs/operators'; import { slider } from '../slide-animation'; -import { AsyncPipe } from '@angular/common'; import { NguItemComponent, NguCarouselPrevDirective, @@ -24,8 +21,7 @@ import { NguCarouselPrevDirective, NguCarouselDefDirective, NguItemComponent, - NguCarouselNextDirective, - AsyncPipe + NguCarouselNextDirective ] }) export class BannerVerticalComponent { @@ -52,23 +48,23 @@ export class BannerVerticalComponent { }; tempData: any[]; - public carouselTileItems$: Observable; + public items = signal([]); constructor() { - this.tempData = []; - - this.carouselTileItems$ = interval(500).pipe( - startWith(-1), - take(30), - map(() => { - const data = (this.tempData = [ - ...this.tempData, - this.images[Math.floor(Math.random() * this.images.length)] - ]); + this.addItem(); + afterNextRender(() => { + const id = setInterval(() => { + this.addItem(); + if (this.items().length >= 30) { + clearInterval(id); + } + }, 500); + }); + } - return data; - }) - ); + addItem() { + const i = Math.floor(Math.random() * this.images.length); + this.items.update(items => [...items, this.images[i]]); } /* It will be triggered on every slide*/ diff --git a/apps/ngu-carousel-example/src/app/banner/banner.component.html b/apps/ngu-carousel-example/src/app/banner/banner.component.html index 5beed99..826ec27 100644 --- a/apps/ngu-carousel-example/src/app/banner/banner.component.html +++ b/apps/ngu-carousel-example/src/app/banner/banner.component.html @@ -1,7 +1,7 @@

Banner

diff --git a/apps/ngu-carousel-example/src/app/banner/banner.component.ts b/apps/ngu-carousel-example/src/app/banner/banner.component.ts index 3437ead..c30f040 100644 --- a/apps/ngu-carousel-example/src/app/banner/banner.component.ts +++ b/apps/ngu-carousel-example/src/app/banner/banner.component.ts @@ -1,9 +1,6 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, afterNextRender, signal } from '@angular/core'; import { NguCarouselConfig } from '@ngu/carousel'; -import { interval, Observable } from 'rxjs'; -import { map, startWith, take } from 'rxjs/operators'; import { slider } from '../slide-animation'; -import { AsyncPipe } from '@angular/common'; import { NguItemComponent, NguCarouselPrevDirective, @@ -24,8 +21,7 @@ import { NguCarouselPrevDirective, NguCarouselDefDirective, NguItemComponent, - NguCarouselNextDirective, - AsyncPipe + NguCarouselNextDirective ] }) export class BannerComponent { @@ -52,23 +48,23 @@ export class BannerComponent { }; tempData: any[]; - public carouselTileItems$: Observable; + public items = signal([]); constructor() { - this.tempData = []; - - this.carouselTileItems$ = interval(500).pipe( - startWith(-1), - take(30), - map(() => { - const data = (this.tempData = [ - ...this.tempData, - this.images[Math.floor(Math.random() * this.images.length)] - ]); + this.addItem(); + afterNextRender(() => { + const id = setInterval(() => { + this.addItem(); + if (this.items().length >= 30) { + clearInterval(id); + } + }, 500); + }); + } - return data; - }) - ); + addItem() { + const i = Math.floor(Math.random() * this.images.length); + this.items.update(items => [...items, this.images[i]]); } /* It will be triggered on every slide*/ diff --git a/apps/ngu-carousel-example/src/app/main-nav/main-nav.component.html b/apps/ngu-carousel-example/src/app/main-nav/main-nav.component.html index d6d59da..fcb4a91 100644 --- a/apps/ngu-carousel-example/src/app/main-nav/main-nav.component.html +++ b/apps/ngu-carousel-example/src/app/main-nav/main-nav.component.html @@ -3,9 +3,9 @@ #drawer class="sidenav" fixedInViewport - [attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'" - [mode]="(isHandset$ | async) ? 'over' : 'side'" - [opened]="(isHandset$ | async) === false" + [attr.role]="isHandset() ? 'dialog' : 'navigation'" + [mode]="isHandset() ? 'over' : 'side'" + [opened]="isHandset() === false" > Banner Types @@ -18,7 +18,7 @@ - @if (isHandset$ | async) { + @if (isHandset()) { diff --git a/apps/ngu-carousel-example/src/app/main-nav/main-nav.component.ts b/apps/ngu-carousel-example/src/app/main-nav/main-nav.component.ts index 067b9c4..5dd4500 100644 --- a/apps/ngu-carousel-example/src/app/main-nav/main-nav.component.ts +++ b/apps/ngu-carousel-example/src/app/main-nav/main-nav.component.ts @@ -1,14 +1,13 @@ import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Observable } from 'rxjs'; -import { map, shareReplay } from 'rxjs/operators'; -import { AsyncPipe } from '@angular/common'; +import { toSignal } from '@angular/core/rxjs-interop'; import { MatIcon } from '@angular/material/icon'; import { MatIconButton, MatAnchor } from '@angular/material/button'; import { RouterLink, RouterOutlet } from '@angular/router'; import { MatNavList, MatListItem } from '@angular/material/list'; import { MatToolbar } from '@angular/material/toolbar'; import { MatSidenavContainer, MatSidenav, MatSidenavContent } from '@angular/material/sidenav'; +import { map } from 'rxjs'; @Component({ standalone: true, @@ -27,14 +26,12 @@ import { MatSidenavContainer, MatSidenav, MatSidenavContent } from '@angular/mat MatIconButton, MatIcon, MatAnchor, - RouterOutlet, - AsyncPipe + RouterOutlet ] }) export class MainNavComponent { - isHandset$: Observable = this.breakpointObserver.observe(Breakpoints.Handset).pipe( - map(result => result.matches), - shareReplay() + isHandset = toSignal( + this.breakpointObserver.observe(Breakpoints.Handset).pipe(map(result => result.matches)) ); constructor(private breakpointObserver: BreakpointObserver) {} diff --git a/apps/ngu-carousel-example/src/app/tile-2-images/tile-2-images.component.html b/apps/ngu-carousel-example/src/app/tile-2-images/tile-2-images.component.html index 98dcfe7..fc75485 100644 --- a/apps/ngu-carousel-example/src/app/tile-2-images/tile-2-images.component.html +++ b/apps/ngu-carousel-example/src/app/tile-2-images/tile-2-images.component.html @@ -1,5 +1,5 @@ - - @@ -9,13 +9,13 @@

{{ i + 1 }}

-
    - @for (i of myCarousel.pointNumbers; track i) { -
  • + @for (i of myCarousel.pointNumbers(); track i) { +
  • }
diff --git a/apps/ngu-carousel-example/src/app/tile-2-images/tile-2-images.component.ts b/apps/ngu-carousel-example/src/app/tile-2-images/tile-2-images.component.ts index 1e380d5..774cc4d 100644 --- a/apps/ngu-carousel-example/src/app/tile-2-images/tile-2-images.component.ts +++ b/apps/ngu-carousel-example/src/app/tile-2-images/tile-2-images.component.ts @@ -1,9 +1,6 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, afterNextRender, signal } from '@angular/core'; import { NguCarouselConfig } from '@ngu/carousel'; -import { Observable, interval } from 'rxjs'; -import { map, startWith, take } from 'rxjs/operators'; import { slider } from '../slide-animation'; -import { AsyncPipe } from '@angular/common'; import { NguTileComponent, NguCarouselPrevDirective, @@ -24,14 +21,13 @@ import { NguCarouselPrevDirective, NguCarouselDefDirective, NguTileComponent, - NguCarouselNextDirective, - AsyncPipe + NguCarouselNextDirective ] }) export class Tile2ImagesComponent { images = ['assets/bg.jpg', 'assets/car.png']; - public carouselTileItems$: Observable; + public carouselItems = signal([]); public carouselTileConfig: NguCarouselConfig = { grid: { xs: 1, sm: 1, md: 1, lg: 5, all: 0 }, speed: 250, @@ -43,21 +39,21 @@ export class Tile2ImagesComponent { interval: { timing: 1500 }, animation: 'lazy' }; - tempData: any[]; constructor() { - this.tempData = []; - this.carouselTileItems$ = interval(500).pipe( - startWith(-1), - take(2), - map(() => { - const data = (this.tempData = [ - ...this.tempData, - this.images[Math.floor(Math.random() * this.images.length)] - ]); + this.addItem(); + afterNextRender(() => { + const id = setInterval(() => { + this.addItem(); + if (this.carouselItems().length >= 2) { + clearInterval(id); + } + }, 500); + }); + } - return data; - }) - ); + addItem() { + const i = Math.floor(Math.random() * this.images.length); + this.carouselItems.update(items => [...items, this.images[i]]); } } diff --git a/apps/ngu-carousel-example/src/app/tile/tile.component.html b/apps/ngu-carousel-example/src/app/tile/tile.component.html index 46acd2a..d6f8e4c 100644 --- a/apps/ngu-carousel-example/src/app/tile/tile.component.html +++ b/apps/ngu-carousel-example/src/app/tile/tile.component.html @@ -1,7 +1,7 @@

Tile

- - @@ -11,13 +11,13 @@

{{ i + 1 }}

-
    - @for (i of myCarousel.pointNumbers; track i) { -
  • + @for (i of myCarousel.pointNumbers(); track i) { +
  • }
diff --git a/apps/ngu-carousel-example/src/app/tile/tile.component.ts b/apps/ngu-carousel-example/src/app/tile/tile.component.ts index 52b513e..baea8ab 100644 --- a/apps/ngu-carousel-example/src/app/tile/tile.component.ts +++ b/apps/ngu-carousel-example/src/app/tile/tile.component.ts @@ -1,9 +1,6 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, afterNextRender, signal } from '@angular/core'; import { NguCarouselConfig } from '@ngu/carousel'; -import { interval, Observable } from 'rxjs'; -import { map, startWith, take } from 'rxjs/operators'; import { slider } from '../slide-animation'; -import { AsyncPipe } from '@angular/common'; import { NguTileComponent, NguCarouselPrevDirective, @@ -26,14 +23,13 @@ import { NguCarouselDefDirective, NguTileComponent, NguCarouselNextDirective, - NguCarouselPointDirective, - AsyncPipe + NguCarouselPointDirective ] }) export class TileComponent { images = ['assets/bg.jpg', 'assets/car.png', 'assets/canberra.jpg', 'assets/holi.jpg']; - public carouselTileItems$: Observable; + public carouselItems = signal([]); public carouselTileConfig: NguCarouselConfig = { grid: { xs: 1, sm: 1, md: 1, lg: 5, all: 0 }, speed: 250, @@ -45,22 +41,21 @@ export class TileComponent { interval: { timing: 1500 }, animation: 'lazy' }; - tempData: any[]; constructor() { - this.tempData = []; - - this.carouselTileItems$ = interval(500).pipe( - startWith(-1), - take(30), - map(() => { - const data = (this.tempData = [ - ...this.tempData, - this.images[Math.floor(Math.random() * this.images.length)] - ]); + this.addItem(); + afterNextRender(() => { + const id = setInterval(() => { + this.addItem(); + if (this.carouselItems().length >= 30) { + clearInterval(id); + } + }, 500); + }); + } - return data; - }) - ); + private addItem() { + const i = Math.floor(Math.random() * this.images.length); + this.carouselItems.update(items => [...items, this.images[i]]); } } diff --git a/apps/ngu-carousel-example/src/app/wrapped/wrapped-carousel/wrapped-carousel.component.html b/apps/ngu-carousel-example/src/app/wrapped/wrapped-carousel/wrapped-carousel.component.html index 7f8e823..128c9ef 100644 --- a/apps/ngu-carousel-example/src/app/wrapped/wrapped-carousel/wrapped-carousel.component.html +++ b/apps/ngu-carousel-example/src/app/wrapped/wrapped-carousel/wrapped-carousel.component.html @@ -1,8 +1,8 @@ -@if (carouselTileItems.length) { +@if (items().length) {

Wrapped Carousel

- - @@ -11,21 +11,20 @@

Wrapped Carousel

- } - @else { + } @else {

Odd: {{ i + 1 }}

} -
    - @for (i of myCarousel.pointNumbers; track i) { -
  • + @for (i of myCarousel.pointNumbers(); track i) { +
  • }
diff --git a/apps/ngu-carousel-example/src/app/wrapped/wrapped-carousel/wrapped-carousel.component.ts b/apps/ngu-carousel-example/src/app/wrapped/wrapped-carousel/wrapped-carousel.component.ts index abe1fba..4f02040 100644 --- a/apps/ngu-carousel-example/src/app/wrapped/wrapped-carousel/wrapped-carousel.component.ts +++ b/apps/ngu-carousel-example/src/app/wrapped/wrapped-carousel/wrapped-carousel.component.ts @@ -1,12 +1,4 @@ -import { - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, - OnChanges, - SimpleChanges -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; import { NguCarouselConfig } from '@ngu/carousel'; import { slider } from '../../slide-animation'; import { NgTemplateOutlet } from '@angular/common'; @@ -34,8 +26,8 @@ import { NguCarouselNextDirective ] }) -export class WrappedCarouselComponent implements OnChanges, AfterViewInit { - @Input() carouselTileItems: any[]; +export class WrappedCarouselComponent { + items = input([]); public config: NguCarouselConfig = { grid: { xs: 1, sm: 2, md: 3, lg: 4, xl: 4, all: 0 }, gridBreakpoints: { sm: 480, md: 768, lg: 1024, xl: 1280 }, @@ -47,24 +39,19 @@ export class WrappedCarouselComponent implements OnChanges, AfterViewInit { touch: true, easing: 'cubic-bezier(0, 0, 0.2, 1)' }; - @Input() grid: { xs?: number; sm?: number; md?: number; lg?: number; xl?: number; all?: number } = - { xs: 1, sm: 2, md: 3, lg: 4, xl: 4, all: 0 }; - constructor(private cd: ChangeDetectorRef) {} - ngOnChanges(changes: SimpleChanges) { - if (!!changes?.grid) { - this.config.grid = changes?.grid?.currentValue || { - xs: 1, - sm: 2, - md: 3, - lg: 4, - xl: 4, - all: 0 - }; + default = { + xs: 1, + sm: 2, + md: 3, + lg: 4, + xl: 4, + all: 0 + }; + + grid = input(this.default, { + transform: (value: Partial) => { + return value || this.default; } - this.cd.detectChanges(); - } - ngAfterViewInit(): void { - this.cd.detectChanges(); - } + }); } diff --git a/apps/ngu-carousel-example/src/app/wrapped/wrapped.component.ts b/apps/ngu-carousel-example/src/app/wrapped/wrapped.component.ts index bf7ac14..17ac9df 100644 --- a/apps/ngu-carousel-example/src/app/wrapped/wrapped.component.ts +++ b/apps/ngu-carousel-example/src/app/wrapped/wrapped.component.ts @@ -1,31 +1,23 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core'; -import { BehaviorSubject, Observable, delay, of } from 'rxjs'; +import { ChangeDetectionStrategy, Component, Signal, signal } from '@angular/core'; import data from '../../assets/mock/images.json'; -import { AsyncPipe } from '@angular/common'; import { WrappedCarouselComponent } from './wrapped-carousel/wrapped-carousel.component'; @Component({ standalone: true, selector: 'app-wrapped', - template: ``, + template: ``, changeDetection: ChangeDetectionStrategy.OnPush, - imports: [WrappedCarouselComponent, AsyncPipe] + imports: [WrappedCarouselComponent] }) export class WrappedComponent { public grid: { xs: 1; sm: 1; md: 1; lg: 5; all: 0 }; - data: BehaviorSubject = new BehaviorSubject([]); - data$: Observable; - constructor(private cd: ChangeDetectorRef) { - this.data$ = this.data.asObservable(); - of(data) - .pipe(delay(1000)) - .subscribe(data => { - console.log(data.images); - this.data.next(data.images); - cd.detectChanges(); - }); + data = signal([]); + items = this.data.asReadonly(); + + constructor() { + setTimeout(() => { + console.log(data.images); + this.data.set(data.images); + }, 1000); } } diff --git a/apps/ngu-carousel-example/src/main.server.ts b/apps/ngu-carousel-example/src/main.server.ts index cdb649e..91cd232 100644 --- a/apps/ngu-carousel-example/src/main.server.ts +++ b/apps/ngu-carousel-example/src/main.server.ts @@ -1,2 +1,7 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { AppComponent } from './app/app.component'; +import { config } from './app/app-server.config'; -export { AppServerModule } from './app/app.server.module'; +const bootstrap = () => bootstrapApplication(AppComponent, config); + +export default bootstrap; diff --git a/apps/ngu-carousel-example/tsconfig.app.json b/apps/ngu-carousel-example/tsconfig.app.json index cb155f2..609201b 100644 --- a/apps/ngu-carousel-example/tsconfig.app.json +++ b/apps/ngu-carousel-example/tsconfig.app.json @@ -7,7 +7,7 @@ "esModuleInterop": true, "allowSyntheticDefaultImports": true }, - "files": ["src/main.ts"], + "files": ["src/main.ts", "src/main.server.ts", "server.ts"], "include": ["src/**/*.d.ts"], "exclude": ["karma.conf.js", "src/**/*.test.ts", "src/**/*.spec.ts"] } diff --git a/apps/ngu-carousel-example/tsconfig.server.json b/apps/ngu-carousel-example/tsconfig.server.json deleted file mode 100644 index 7dbe2fd..0000000 --- a/apps/ngu-carousel-example/tsconfig.server.json +++ /dev/null @@ -1,11 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "../../out-tsc/server", - "types": ["node"], - "resolveJsonModule": true, - "allowSyntheticDefaultImports": true - }, - "files": ["src/main.server.ts", "server.ts"] -} diff --git a/libs/ngu/carousel/.eslintrc.json b/libs/ngu/carousel/.eslintrc.json index aca22da..25c4664 100644 --- a/libs/ngu/carousel/.eslintrc.json +++ b/libs/ngu/carousel/.eslintrc.json @@ -28,7 +28,10 @@ "prefix": "ngu", "style": "kebab-case" } - ] + ], + "@angular-eslint/directive-class-suffix": "off", + "@angular-eslint/component-class-suffix": "off", + "@angular-eslint/no-host-metadata-property": "off" } }, { diff --git a/libs/ngu/carousel/README.md b/libs/ngu/carousel/README.md index 2cf7cbf..b369b1e 100644 --- a/libs/ngu/carousel/README.md +++ b/libs/ngu/carousel/README.md @@ -12,7 +12,8 @@ Demo available [Here](https://ngu-carousel.netlify.app/) | Angular Version | ngu-carousel Version | | ------------------------ | -------------------------------------------- | -| Angular >= 17 | `npm i --save @ngu/carousel@carousel@latest` | +| Angular >= 18 | `npm i --save @ngu/carousel@latest` | +| Angular >= 17 | `npm i --save @ngu/carousel@9.0.0` | | Angular >= 16 standalone | `npm i --save @ngu/carousel@8.0.0` | | Angular >= 16 | `npm i --save @ngu/carousel@7.2.0` | | Angular >= 15 | `npm i --save @ngu/carousel@7.0.0` | @@ -95,19 +96,19 @@ import { NguCarouselConfig } from '@ngu/carousel';

{{j}}

- - + +
    -
- - + +
    -
diff --git a/libs/ngu/carousel/src/lib/ngu-carousel.directive.ts b/libs/ngu/carousel/src/lib/ngu-carousel.directive.ts index 5cf9780..7dc35ee 100644 --- a/libs/ngu/carousel/src/lib/ngu-carousel.directive.ts +++ b/libs/ngu/carousel/src/lib/ngu-carousel.directive.ts @@ -1,4 +1,4 @@ -import { Directive, TemplateRef, ViewContainerRef } from '@angular/core'; +import { Directive, TemplateRef, ViewContainerRef, inject } from '@angular/core'; @Directive({ selector: '[NguCarouselItem]', @@ -10,22 +10,13 @@ export class NguCarouselItemDirective {} selector: '[NguCarouselNext]', standalone: true }) -export class NguCarouselNextDirective { - // @HostBinding('disabled') disabled: boolean; - // @HostBinding('style.display') display = 'block'; - // @HostListener('click') - // onClick() { - // } -} +export class NguCarouselNextDirective {} @Directive({ selector: '[NguCarouselPrev]', standalone: true }) -export class NguCarouselPrevDirective { - // @HostBinding('disabled') disabled: boolean; - // @HostBinding('style.display') display = 'block'; -} +export class NguCarouselPrevDirective {} @Directive({ selector: '[NguCarouselPoint]', @@ -39,9 +30,8 @@ export class NguCarouselPointDirective {} standalone: true }) export class NguCarouselDefDirective { + template = inject(TemplateRef); when?: (index: number, nodeData: T) => boolean; - - constructor(public template: TemplateRef) {} } @Directive({ @@ -49,7 +39,6 @@ export class NguCarouselDefDirective { selector: '[nguCarouselOutlet]', standalone: true }) -// eslint-disable-next-line @angular-eslint/directive-class-suffix export class NguCarouselOutlet { - constructor(public viewContainer: ViewContainerRef) {} + viewContainer = inject(ViewContainerRef); } diff --git a/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.html b/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.html index 7aebf1d..db23bfd 100644 --- a/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.html +++ b/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.html @@ -1,8 +1,10 @@
-
-
- +
+
+
+ +
diff --git a/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.scss b/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.scss index d99c581..6f9dc72 100644 --- a/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.scss +++ b/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.scss @@ -19,6 +19,10 @@ } // } } +.ngu-container { + overflow: hidden; +} + .nguvertical { flex-direction: column; } diff --git a/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.ts b/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.ts index d47ff55..7359f5e 100644 --- a/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.ts +++ b/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.ts @@ -1,16 +1,7 @@ import { - AfterContentInit, - AfterViewInit, ChangeDetectionStrategy, - ChangeDetectorRef, Component, - ContentChild, - ContentChildren, - DoCheck, ElementRef, - EventEmitter, - Inject, - Input, IterableChangeRecord, IterableChanges, IterableDiffer, @@ -18,13 +9,23 @@ import { NgIterable, NgZone, OnDestroy, - Output, - QueryList, Renderer2, TrackByFunction, - ViewChild + computed, + contentChild, + contentChildren, + effect, + inject, + input, + output, + viewChild, + signal, + untracked, + ChangeDetectorRef, + afterNextRender, + AfterRenderPhase } from '@angular/core'; -import { EMPTY, Subject, fromEvent, interval, merge, timer } from 'rxjs'; +import { EMPTY, Subject, Subscription, fromEvent, interval, merge, timer } from 'rxjs'; import { debounceTime, filter, map, startWith, switchMap, takeUntil } from 'rxjs/operators'; import { IS_BROWSER } from '../symbols'; @@ -33,7 +34,7 @@ import { NguCarouselNextDirective, NguCarouselOutlet, NguCarouselPrevDirective -} from './../ngu-carousel.directive'; +} from '../ngu-carousel.directive'; import { Breakpoints, NguCarouselConfig, @@ -63,67 +64,42 @@ const NG_DEV_MODE = typeof ngDevMode === 'undefined' || ngDevMode; imports: [NguCarouselOutlet], standalone: true }) -// eslint-disable-next-line @angular-eslint/component-class-suffix export class NguCarousel = NgIterable> extends NguCarouselStore - implements AfterContentInit, AfterViewInit, OnDestroy, DoCheck + implements OnDestroy { + private _host = inject>(ElementRef); + private _renderer = inject(Renderer2); + private _differs = inject(IterableDiffers); + private _isBrowser = inject(IS_BROWSER); + private _ngZone = inject(NgZone); + private _nguWindowScrollListener = inject(NguWindowScrollListener); + private _nguCarouselHammerManager = inject(NguCarouselHammerManager); + private _cdr = inject(ChangeDetectorRef); /** Public property that may be accessed outside of the component. */ - activePoint = 0; - - /** Public property that may be accessed outside of the component. */ - pointNumbers: number[] = []; + readonly activePoint = signal(0); + readonly pointNumbers = signal([]); - @Input() inputs: NguCarouselConfig; - @Output() carouselLoad = new EventEmitter(); - // eslint-disable-next-line @angular-eslint/no-output-on-prefix - @Output() onMove = new EventEmitter>(); + inputs = input.required(); + carouselLoad = output(); + onMove = output(); - private _arrayChanges: IterableChanges | null = null; + private _defDirectives = contentChildren(NguCarouselDefDirective); - @Input() - get dataSource(): NguCarouselDataSource { - return this._dataSource; - } - set dataSource(data: NguCarouselDataSource) { - if (data) { - this._switchDataSource(data); - } - } - private _dataSource: NguCarouselDataSource = null; + private _nodeOutlet = viewChild.required(NguCarouselOutlet); - @ContentChildren(NguCarouselDefDirective) - private _defDirectives: QueryList>; + nextButton = contentChild(NguCarouselNextDirective, { read: ElementRef }); + prevButton = contentChild(NguCarouselPrevDirective, { read: ElementRef }); - @ViewChild(NguCarouselOutlet, { static: true }) - private _nodeOutlet: NguCarouselOutlet; + carouselMain1 = viewChild.required('ngucarousel', { read: ElementRef }); + _nguItemsContainer = viewChild.required('nguItemsContainer', { read: ElementRef }); + _touchContainer = viewChild.required('touchContainer', { read: ElementRef }); - /** - * The setter is used to catch the button if the button is wrapped with `ngIf`. - * https://github.com/uiuniversal/ngu-carousel/issues/91 - */ - @ContentChild(NguCarouselNextDirective, { read: ElementRef, static: false }) - set nextButton(nextButton: ElementRef | undefined) { - this._nextButton$.next(nextButton?.nativeElement); - } - - /** - * The setter is used to catch the button if the button is wrapped with `ngIf`. - * https://github.com/uiuniversal/ngu-carousel/issues/91 - */ - @ContentChild(NguCarouselPrevDirective, { read: ElementRef, static: false }) - set prevButton(prevButton: ElementRef | undefined) { - this._prevButton$.next(prevButton?.nativeElement); - } - - @ViewChild('ngucarousel', { read: ElementRef, static: true }) - private carouselMain1: ElementRef; - - @ViewChild('nguItemsContainer', { read: ElementRef, static: true }) - private _nguItemsContainer: ElementRef; + private _arrayChanges: IterableChanges | null = null; - @ViewChild('touchContainer', { read: ElementRef, static: true }) - private _touchContainer: ElementRef; + dataSource = input.required({ + transform: (v: NguCarouselDataSource) => v || ([] as never) + }); private _intervalController$ = new Subject(); @@ -143,81 +119,108 @@ export class NguCarousel = NgIterable> private _destroy$ = new Subject(); - private ngu_dirty: boolean = true; - - private _markedForCheck: boolean = false; - /** * Tracking function that will be used to check the differences in data changes. Used similarly * to `ngFor` `trackBy` function. Optimize Items operations by identifying a Items based on its data * relative to the function to know if a Items should be added/removed/moved. * Accepts a function that takes two parameters, `index` and `item`. */ - @Input() - get trackBy(): TrackByFunction { - return this._trackByFn; - } - set trackBy(fn: TrackByFunction) { + trackBy = input>(); + _trackByFn = computed(() => { + const fn = this.trackBy(); if (NG_DEV_MODE && fn != null && typeof fn !== 'function' && console?.warn) { console.warn(`trackBy must be a function, but received ${JSON.stringify(fn)}.`); } - this._trackByFn = fn; - } - private _trackByFn: TrackByFunction; - - /** Subjects used to notify whenever buttons are removed or rendered so we can re-add listeners. */ - private readonly _prevButton$ = new Subject(); - private readonly _nextButton$ = new Subject(); - - constructor( - private _host: ElementRef, - private _renderer: Renderer2, - private _differs: IterableDiffers, - @Inject(IS_BROWSER) private _isBrowser: boolean, - private _cdr: ChangeDetectorRef, - private _ngZone: NgZone, - private _nguWindowScrollListener: NguWindowScrollListener, - private _nguCarouselHammerManager: NguCarouselHammerManager - ) { + return fn || ((index: number, item: T) => item); + }); + + constructor() { super(); - this._setupButtonListeners(); - } + this._dataDiffer = this._differs.find([]).create(this._trackByFn())!; - ngDoCheck() { - this._checkChanges(); - } + afterNextRender( + () => { + this._inputValidation(); - private _checkChanges() { - if (this.ngu_dirty) { - this.ngu_dirty = false; - const dataStream = this._dataSource; - if (!this._arrayChanges && !!dataStream) { - this._dataDiffer = this._differs - .find(dataStream) - .create((index: number, item: any) => (this.trackBy ? this.trackBy(index, item) : item))!; - } - } - if (this._dataDiffer) { - this._arrayChanges = - this._markedForCheck && this._arrayChanges - ? this._arrayChanges - : this._dataDiffer.diff(this._dataSource)!; - if (this._arrayChanges) { - this.renderNodeChanges(Array.from(this._dataSource!)); - } - } + this._carouselCssNode = this._createStyleElem(); + + if (this._isBrowser) { + this._carouselInterval(); + if (!this.vertical.enabled && this.inputs()?.touch) { + this._setupHammer(); + } + this._setupWindowResizeListener(); + this._onWindowScrolling(); + } + }, + { phase: AfterRenderPhase.EarlyRead } + ); + + effect( + () => { + const _ = this._defDirectives(); + const data = this.dataSource(); + untracked(() => this._checkChanges(data)); + }, + { allowSignalWrites: true } + ); + + let preSub: Subscription; + effect(() => { + preSub?.unsubscribe(); + const prevButton = this.prevButton(); + untracked(() => { + if (prevButton) { + preSub = fromEvent(prevButton.nativeElement, 'click') + .pipe(takeUntil(this._destroy$)) + .subscribe(() => this._carouselScrollOne(0)); + } + }); + }); + let nextSub: Subscription; + effect(() => { + nextSub?.unsubscribe(); + const nextButton = this.nextButton(); + untracked(() => { + if (nextButton) { + nextSub = fromEvent(nextButton.nativeElement, 'click') + .pipe(takeUntil(this._destroy$)) + .subscribe(() => this._carouselScrollOne(1)); + } + }); + }); } - private _switchDataSource(dataSource: any): any { - this._dataSource = dataSource; - this.ngu_dirty = true; + private _checkChanges(data: NguCarouselDataSource) { + // if (this.ngu_dirty) { + // this.ngu_dirty = false; + // const dataStream = this.dataSource; + // if (!this._arrayChanges && !!dataStream) { + // this._dataDiffer = this._differs + // .find(dataStream) + // .create((index: number, item: any) => this._trackByFn()(index, item))!; + // } + // } + // if (this._dataDiffer) { + // this._arrayChanges = + // this._markedForCheck && this._arrayChanges + // ? this._arrayChanges + // : this._dataDiffer.diff(this.dataSource)!; + // if (this._arrayChanges) { + // this.renderNodeChanges(Array.from(this.dataSource())); + // } + // } + this._arrayChanges = this._dataDiffer.diff(data); + if (this._arrayChanges) { + this.renderNodeChanges(Array.from(data as any)); + } } private renderNodeChanges(data: any[]) { if (!this._arrayChanges) return; - this.isLast = this._pointIndex === this.currentSlide; - const viewContainer = this._nodeOutlet.viewContainer; - this._markedForCheck = false; + this.isLast.set(this._pointIndex === this.currentSlide); + const viewContainer = this._nodeOutlet().viewContainer; + // this._markedForCheck = false; this._arrayChanges.forEachOperation( ( item: IterableChangeRecord, @@ -251,7 +254,7 @@ export class NguCarousel = NgIterable> * e.g. first/last/even/odd. */ private _updateItemIndexContext() { - const viewContainer = this._nodeOutlet.viewContainer; + const viewContainer = this._nodeOutlet().viewContainer; for (let renderIndex = 0, count = viewContainer.length; renderIndex < count; renderIndex++) { const viewRef = viewContainer.get(renderIndex) as any; const context = viewRef.context as any; @@ -265,66 +268,39 @@ export class NguCarousel = NgIterable> } private _getNodeDef(data: any, i: number): NguCarouselDefDirective | undefined { - if (this._defDirectives?.length === 1) { - return this._defDirectives.first; + if (this._defDirectives()?.length === 1) { + return this._defDirectives()[0]; } - const nodeDef: NguCarouselDefDirective | undefined = (this._defDirectives || []).find( + const nodeDef: NguCarouselDefDirective | undefined = (this._defDirectives() || []).find( def => !!def.when?.(i, data) ); return nodeDef; } - ngAfterViewInit() { - this._inputValidation(); - - this._carouselCssNode = this._createStyleElem(); - - if (this._isBrowser) { - this._carouselInterval(); - if (!this.vertical.enabled && this.inputs.touch) { - this._setupHammer(); - } - this._setupWindowResizeListener(); - this._onWindowScrolling(); - } - } - - ngAfterContentInit() { - this._cdr.markForCheck(); - this._defDirectives.changes.subscribe(() => { - this._markedForCheck = true; - this._checkChanges(); - }); - this._defDirectives.notifyOnChanges(); - } - private _inputValidation() { - this.inputs.gridBreakpoints = this.inputs.gridBreakpoints - ? this.inputs.gridBreakpoints - : new Breakpoints(); - if (this.inputs.grid.xl === undefined) { - this.inputs.grid.xl = this.inputs.grid.lg; + const inputs = this.inputs(); + inputs.gridBreakpoints = inputs.gridBreakpoints ? inputs.gridBreakpoints : new Breakpoints(); + if (inputs.grid.xl === undefined) { + inputs.grid.xl = inputs.grid.lg; } - this.type = this.inputs.grid.all !== 0 ? 'fixed' : 'responsive'; - this.loop = this.inputs.loop || false; - this.inputs.easing = this.inputs.easing || 'cubic-bezier(0, 0, 0.2, 1)'; - this.touch.active = this.inputs.touch || false; - this.RTL = this.inputs.RTL ? true : false; - this.interval = this.inputs.interval || undefined; - this.velocity = typeof this.inputs.velocity === 'number' ? this.inputs.velocity : this.velocity; - - if (this.inputs.vertical && this.inputs.vertical.enabled) { - this.vertical.enabled = this.inputs.vertical.enabled; - this.vertical.height = this.inputs.vertical.height; + this.type = inputs.grid.all !== 0 ? 'fixed' : 'responsive'; + this.loop = inputs.loop || false; + inputs.easing = inputs.easing || 'cubic-bezier(0, 0, 0.2, 1)'; + this.touch.active = inputs.touch || false; + this.RTL = inputs.RTL ? true : false; + this.interval = inputs.interval || undefined; + this.velocity = typeof inputs.velocity === 'number' ? inputs.velocity : this.velocity; + + if (inputs.vertical && inputs.vertical.enabled) { + this.vertical.enabled = inputs.vertical.enabled; + this.vertical.height = inputs.vertical.height; } this._directionSymbol = this.RTL ? '' : '-'; this.point = - this.inputs.point && typeof this.inputs.point.visible !== 'undefined' - ? this.inputs.point.visible - : true; + inputs.point && typeof inputs.point.visible !== 'undefined' ? inputs.point.visible : true; this._carouselSize(); } @@ -338,17 +314,17 @@ export class NguCarousel = NgIterable> private _setupHammer(): void { // Note: doesn't need to unsubscribe because streams are piped with `takeUntil` already. this._nguCarouselHammerManager - .createHammer(this._touchContainer.nativeElement) + .createHammer(this._touchContainer().nativeElement) .subscribe(hammer => { this._hammer = hammer; hammer.get('pan').set({ direction: Hammer.DIRECTION_HORIZONTAL }); this._nguCarouselHammerManager.on(hammer, 'panstart').subscribe(() => { - this.carouselWidth = this._nguItemsContainer.nativeElement.offsetWidth; + this.carouselWidth = this._nguItemsContainer().nativeElement.offsetWidth; this.touchTransform = this.transform[this.deviceType!]!; this.dexVal = 0; - this._setStyle(this._nguItemsContainer.nativeElement, 'transition', ''); + this._setStyle(this._nguItemsContainer().nativeElement, 'transition', ''); }); if (this.vertical.enabled) { @@ -382,11 +358,11 @@ export class NguCarousel = NgIterable> } else { this.dexVal = 0; this._setStyle( - this._nguItemsContainer.nativeElement, + this._nguItemsContainer().nativeElement, 'transition', 'transform 324ms cubic-bezier(0, 0, 0.2, 1)' ); - this._setStyle(this._nguItemsContainer.nativeElement, 'transform', ''); + this._setStyle(this._nguItemsContainer().nativeElement, 'transform', ''); } }); @@ -431,7 +407,7 @@ export class NguCarousel = NgIterable> } const type = this.type === 'responsive' ? '%' : 'px'; this._setStyle( - this._nguItemsContainer.nativeElement, + this._nguItemsContainer().nativeElement, 'transform', this.vertical.enabled ? `translate3d(0, ${this._directionSymbol}${this.touchTransform}${type}, 0)` @@ -457,34 +433,35 @@ export class NguCarousel = NgIterable> /** store data based on width of the screen for the carousel */ private _storeCarouselData(): void { - const breakpoints = this.inputs.gridBreakpoints; + const inputs = this.inputs(); + const breakpoints = this.inputs().gridBreakpoints; this.deviceWidth = this._isBrowser ? window.innerWidth : breakpoints?.xl!; - this.carouselWidth = this.carouselMain1.nativeElement.offsetWidth; + this.carouselWidth = this.carouselMain1().nativeElement.offsetWidth; if (this.type === 'responsive') { this.deviceType = this.deviceWidth >= breakpoints?.xl! ? 'xl' : this.deviceWidth >= breakpoints?.lg! - ? 'lg' - : this.deviceWidth >= breakpoints?.md! - ? 'md' - : this.deviceWidth >= breakpoints?.sm! - ? 'sm' - : 'xs'; - - this.items = this.inputs.grid[this.deviceType]!; + ? 'lg' + : this.deviceWidth >= breakpoints?.md! + ? 'md' + : this.deviceWidth >= breakpoints?.sm! + ? 'sm' + : 'xs'; + + this.items = inputs.grid[this.deviceType]!; this.itemWidth = this.carouselWidth / this.items; } else { - this.items = Math.trunc(this.carouselWidth / this.inputs.grid.all); - this.itemWidth = this.inputs.grid.all; + this.items = Math.trunc(this.carouselWidth / inputs.grid.all); + this.itemWidth = inputs.grid.all; this.deviceType = 'all'; } - this.slideItems = +(this.inputs.slide! < this.items ? this.inputs.slide! : this.items); - this.load = this.inputs.load! >= this.slideItems ? this.inputs.load! : this.slideItems; - this.speed = this.inputs.speed && this.inputs.speed > -1 ? this.inputs.speed : 400; + this.slideItems = +(inputs.slide! < this.items ? inputs.slide! : this.items); + this.load = inputs.load! >= this.slideItems ? inputs.load! : this.slideItems; + this.speed = inputs.speed && inputs.speed > -1 ? inputs.speed : 400; this._carouselPoint(); } @@ -498,16 +475,16 @@ export class NguCarousel = NgIterable> /** Init carousel point */ private _carouselPoint(): void { - const Nos = Array.from(this._dataSource!).length - (this.items - this.slideItems); + const Nos = Array.from(this.dataSource()).length - (this.items - this.slideItems); this._pointIndex = Math.ceil(Nos / this.slideItems); const pointers: number[] = []; - if (this._pointIndex > 1 || !this.inputs.point?.hideOnSingleSlide) { + if (this._pointIndex > 1 || !this.inputs().point?.hideOnSingleSlide) { for (let i = 0; i < this._pointIndex; i++) { pointers.push(i); } } - this.pointNumbers = pointers; + this.pointNumbers.set(pointers); this._carouselPointActiver(); if (this._pointIndex <= 1) { @@ -524,15 +501,14 @@ export class NguCarousel = NgIterable> /** change the active point in carousel */ private _carouselPointActiver(): void { const i = Math.ceil(this.currentSlide / this.slideItems); - this.activePoint = i; - this._cdr.markForCheck(); + this.activePoint.set(i); } /** this function is used to scoll the carousel when point is clicked */ public moveTo(slide: number, withoutAnimation?: boolean) { // slide = slide - 1; withoutAnimation && (this._withAnimation = false); - if (this.activePoint !== slide && slide < this._pointIndex) { + if (this.activePoint() !== slide && slide < this._pointIndex) { let slideremains; const btns = this.currentSlide < slide ? 1 : 0; @@ -543,7 +519,7 @@ export class NguCarousel = NgIterable> break; case this._pointIndex - 1: this._btnBoolean(0, 1); - slideremains = Array.from(this._dataSource!).length - this.items; + slideremains = Array.from(this.dataSource()).length - this.items; break; default: this._btnBoolean(0, 0); @@ -555,36 +531,37 @@ export class NguCarousel = NgIterable> /** set the style of the carousel based the inputs data */ private _carouselSize(): void { + const inputs = this.inputs(); this.token = this._generateID(); let dism = ''; - this._styleid = `.${this.token} > .ngucarousel > .ngu-touch-container > .ngucarousel-items`; + this._styleid = `.${this.token} > .ngucarousel > .ngu-container > .ngu-touch-container > .ngucarousel-items`; - if (this.inputs.custom === 'banner') { + if (inputs.custom === 'banner') { this._renderer.addClass(this._host.nativeElement, 'banner'); } - if (this.inputs.animation === 'lazy') { + if (inputs.animation === 'lazy') { dism += `${this._styleid} > .item {transition: transform .6s ease;}`; } - const breakpoints = this.inputs.gridBreakpoints; + const breakpoints = inputs.gridBreakpoints; let itemStyle = ''; if (this.vertical.enabled) { const itemWidthXS = `${this._styleid} > .item {height: ${ - this.vertical.height / +this.inputs.grid.xs + this.vertical.height / +inputs.grid.xs }px}`; const itemWidthSM = `${this._styleid} > .item {height: ${ - this.vertical.height / +this.inputs.grid.sm + this.vertical.height / +inputs.grid.sm }px}`; const itemWidthMD = `${this._styleid} > .item {height: ${ - this.vertical.height / +this.inputs.grid.md + this.vertical.height / +inputs.grid.md }px}`; const itemWidthLG = `${this._styleid} > .item {height: ${ - this.vertical.height / +this.inputs.grid.lg + this.vertical.height / +inputs.grid.lg }px}`; const itemWidthXL = `${this._styleid} > .item {height: ${ - this.vertical.height / +this.inputs.grid.xl! + this.vertical.height / +inputs.grid.xl! }px}`; itemStyle = `@media (max-width:${breakpoints?.sm! - 1}px){${itemWidthXS}} @@ -594,26 +571,26 @@ export class NguCarousel = NgIterable> @media (min-width:${breakpoints?.xl}px){${itemWidthXL}}`; } else if (this.type === 'responsive') { const itemWidthXS = - this.inputs.type === 'mobile' - ? `${this._styleid} .item {flex: 0 0 ${95 / +this.inputs.grid.xs}%; width: ${ - 95 / +this.inputs.grid.xs + inputs.type === 'mobile' + ? `${this._styleid} .item {flex: 0 0 ${95 / +inputs.grid.xs}%; width: ${ + 95 / +inputs.grid.xs }%;}` - : `${this._styleid} .item {flex: 0 0 ${100 / +this.inputs.grid.xs}%; width: ${ - 100 / +this.inputs.grid.xs + : `${this._styleid} .item {flex: 0 0 ${100 / +inputs.grid.xs}%; width: ${ + 100 / +inputs.grid.xs }%;}`; - const itemWidthSM = `${this._styleid} > .item {flex: 0 0 ${ - 100 / +this.inputs.grid.sm - }%; width: ${100 / +this.inputs.grid.sm}%}`; - const itemWidthMD = `${this._styleid} > .item {flex: 0 0 ${ - 100 / +this.inputs.grid.md - }%; width: ${100 / +this.inputs.grid.md}%}`; - const itemWidthLG = `${this._styleid} > .item {flex: 0 0 ${ - 100 / +this.inputs.grid.lg - }%; width: ${100 / +this.inputs.grid.lg}%}`; - const itemWidthXL = `${this._styleid} > .item {flex: 0 0 ${ - 100 / +this.inputs.grid.xl! - }%; width: ${100 / +this.inputs.grid.xl!}%}`; + const itemWidthSM = `${this._styleid} > .item {flex: 0 0 ${100 / +inputs.grid.sm}%; width: ${ + 100 / +inputs.grid.sm + }%}`; + const itemWidthMD = `${this._styleid} > .item {flex: 0 0 ${100 / +inputs.grid.md}%; width: ${ + 100 / +inputs.grid.md + }%}`; + const itemWidthLG = `${this._styleid} > .item {flex: 0 0 ${100 / +inputs.grid.lg}%; width: ${ + 100 / +inputs.grid.lg + }%}`; + const itemWidthXL = `${this._styleid} > .item {flex: 0 0 ${100 / +inputs.grid.xl!}%; width: ${ + 100 / +inputs.grid.xl! + }%}`; itemStyle = `@media (max-width:${breakpoints?.sm! - 1}px){${itemWidthXS}} @media (min-width:${breakpoints?.sm}px){${itemWidthSM}} @@ -621,14 +598,14 @@ export class NguCarousel = NgIterable> @media (min-width:${breakpoints?.lg}px){${itemWidthLG}} @media (min-width:${breakpoints?.xl}px){${itemWidthXL}}`; } else { - itemStyle = `${this._styleid} .item {flex: 0 0 ${this.inputs.grid.all}px; width: ${this.inputs.grid.all}px;}`; + itemStyle = `${this._styleid} .item {flex: 0 0 ${inputs.grid.all}px; width: ${inputs.grid.all}px;}`; } this._renderer.addClass(this._host.nativeElement, this.token); if (this.vertical.enabled) { - this._renderer.addClass(this._nguItemsContainer.nativeElement, 'nguvertical'); + this._renderer.addClass(this._nguItemsContainer().nativeElement, 'nguvertical'); this._renderer.setStyle( - this.carouselMain1.nativeElement, + this.carouselMain1().nativeElement, 'height', `${this.vertical.height}px` ); @@ -647,16 +624,16 @@ export class NguCarousel = NgIterable> let currentSlide = 0; let touchMove = Math.ceil(this.dexVal / this.itemWidth); touchMove = isFinite(touchMove) ? touchMove : 0; - this._setStyle(this._nguItemsContainer.nativeElement, 'transform', ''); + this._setStyle(this._nguItemsContainer().nativeElement, 'transform', ''); if (this._pointIndex === 1) { return; - } else if (Btn === 0 && ((!this.loop && !this.isFirst) || this.loop)) { + } else if (Btn === 0 && ((!this.loop && !this.isFirst()) || this.loop)) { const currentSlideD = this.currentSlide - this.slideItems; const MoveSlide = currentSlideD + this.slideItems; this._btnBoolean(0, 1); if (this.currentSlide === 0) { - currentSlide = Array.from(this._dataSource!).length - this.items; + currentSlide = Array.from(this.dataSource()).length - this.items; itemSpeed = 400; this._btnBoolean(0, 1); } else if (this.slideItems >= MoveSlide) { @@ -672,14 +649,14 @@ export class NguCarousel = NgIterable> } } this._carouselScrollTwo(Btn, currentSlide, itemSpeed); - } else if (Btn === 1 && ((!this.loop && !this.isLast) || this.loop)) { + } else if (Btn === 1 && ((!this.loop && !this.isLast()) || this.loop)) { if ( - Array.from(this._dataSource!).length <= this.currentSlide + this.items + this.slideItems && - !this.isLast + Array.from(this.dataSource()).length <= this.currentSlide + this.items + this.slideItems && + !this.isLast() ) { - currentSlide = Array.from(this._dataSource!).length - this.items; + currentSlide = Array.from(this.dataSource()).length - this.items; this._btnBoolean(0, 1); - } else if (this.isLast) { + } else if (this.isLast()) { currentSlide = 0; itemSpeed = 400; this._btnBoolean(1, 0); @@ -707,11 +684,11 @@ export class NguCarousel = NgIterable> } if (this._withAnimation) { this._setStyle( - this._nguItemsContainer.nativeElement, + this._nguItemsContainer().nativeElement, 'transition', - `transform ${itemSpeed}ms ${this.inputs.easing}` + `transform ${itemSpeed}ms ${this.inputs().easing}` ); - this.inputs.animation && + this.inputs().animation && this._carouselAnimator( Btn, currentSlide + 1, @@ -720,10 +697,10 @@ export class NguCarousel = NgIterable> Math.abs(this.currentSlide - currentSlide) ); } else { - this._setStyle(this._nguItemsContainer.nativeElement, 'transition', ``); + this._setStyle(this._nguItemsContainer().nativeElement, 'transition', ``); } - this.itemLength = Array.from(this._dataSource!).length; + this.itemLength = Array.from(this.dataSource()).length; this._transformStyle(currentSlide); this.currentSlide = currentSlide; this.onMove.emit(this); @@ -734,8 +711,8 @@ export class NguCarousel = NgIterable> /** boolean function for making isFirst and isLast */ private _btnBoolean(first: number, last: number) { - this.isFirst = !!first; - this.isLast = !!last; + this.isFirst.set(!!first); + this.isLast.set(!!last); } private _transformString(grid: keyof Transfrom, slide: number): string { @@ -743,10 +720,10 @@ export class NguCarousel = NgIterable> collect += `${this._styleid} { transform: translate3d(`; if (this.vertical.enabled) { - this.transform[grid] = (this.vertical.height / this.inputs.grid[grid]!) * slide; + this.transform[grid] = (this.vertical.height / this.inputs().grid[grid]!) * slide; collect += `0, -${this.transform[grid]}px, 0`; } else { - this.transform[grid] = (100 / this.inputs.grid[grid]!) * slide; + this.transform[grid] = (100 / this.inputs().grid[grid]!) * slide; collect += `${this._directionSymbol}${this.transform[grid]}%, 0, 0`; } collect += `); }`; @@ -757,7 +734,7 @@ export class NguCarousel = NgIterable> private _transformStyle(slide: number): void { let slideCss = ''; if (this.type === 'responsive') { - const breakpoints = this.inputs.gridBreakpoints; + const breakpoints = this.inputs().gridBreakpoints; slideCss = `@media (max-width: ${breakpoints?.sm! - 1}px) {${this._transformString( 'xs', slide @@ -767,7 +744,7 @@ export class NguCarousel = NgIterable> @media (min-width: ${breakpoints?.lg}px) {${this._transformString('lg', slide)} } @media (min-width: ${breakpoints?.xl}px) {${this._transformString('xl', slide)} }`; } else { - this.transform.all = this.inputs.grid.all * slide; + this.transform.all = this.inputs().grid.all * slide; slideCss = `${this._styleid} { transform: translate3d(${this._directionSymbol}${this.transform.all}px, 0, 0);`; } this._carouselCssNode.textContent = slideCss; @@ -775,8 +752,8 @@ export class NguCarousel = NgIterable> /** this will trigger the carousel to load the items */ private _carouselLoadTrigger(): void { - if (typeof this.inputs.load === 'number') { - Array.from(this._dataSource!).length - this.load <= this.currentSlide + this.items && + if (typeof this.inputs().load === 'number') { + Array.from(this.dataSource()).length - this.load <= this.currentSlide + this.items && this.carouselLoad.emit(this.currentSlide); } } @@ -794,7 +771,7 @@ export class NguCarousel = NgIterable> /** handle the auto slide */ private _carouselInterval(): void { - const container = this.carouselMain1.nativeElement; + const container = this.carouselMain1().nativeElement; if (this.interval && this.loop) { this._nguWindowScrollListener .pipe( @@ -818,7 +795,7 @@ export class NguCarousel = NgIterable> const touchPlay$ = fromEvent(container, 'touchstart').pipe(mapToOne); const touchPause$ = fromEvent(container, 'touchend').pipe(mapToZero); - const interval$ = interval(this.inputs.interval?.timing!).pipe(mapToOne); + const interval$ = interval(this.inputs().interval?.timing!).pipe(mapToOne); const initialDelay = this.interval.initialDelay || 0; @@ -855,7 +832,7 @@ export class NguCarousel = NgIterable> speed: number, length: number ): void { - const viewContainer = this._nodeOutlet.viewContainer; + const viewContainer = this._nodeOutlet().viewContainer; let val = length < 5 ? length : 5; val = val === 1 ? 3 : val; @@ -878,7 +855,6 @@ export class NguCarousel = NgIterable> context.animate = { value: true, params: { distance: -val } }; } } - this._cdr.markForCheck(); timer(speed * 0.7) .pipe(takeUntil(this._destroy$)) @@ -886,7 +862,7 @@ export class NguCarousel = NgIterable> } private _removeAnimations(collectedIndexes: number[]) { - const viewContainer = this._nodeOutlet.viewContainer; + const viewContainer = this._nodeOutlet().viewContainer; collectedIndexes.forEach(i => { const viewRef = viewContainer.get(i) as any; const context = viewRef.context as any; @@ -911,23 +887,6 @@ export class NguCarousel = NgIterable> return styleItem; } - private _setupButtonListeners(): void { - this._prevButton$ - .pipe( - // Returning `EMPTY` will remove event listener once the button is removed from the DOM. - switchMap(prevButton => (prevButton ? fromEvent(prevButton, 'click') : EMPTY)), - takeUntil(this._destroy$) - ) - .subscribe(() => this._carouselScrollOne(0)); - - this._nextButton$ - .pipe( - switchMap(nextButton => (nextButton ? fromEvent(nextButton, 'click') : EMPTY)), - takeUntil(this._destroy$) - ) - .subscribe(() => this._carouselScrollOne(1)); - } - private _setupWindowResizeListener(): void { this._ngZone.runOutsideAngular(() => fromEvent(window, 'resize') @@ -937,12 +896,12 @@ export class NguCarousel = NgIterable> takeUntil(this._destroy$) ) .subscribe(() => { - this._setStyle(this._nguItemsContainer.nativeElement, 'transition', ``); + this._setStyle(this._nguItemsContainer().nativeElement, 'transition', ``); // Re-enter the Angular zone only after `resize` events have been dispatched // and the timer has run (in `debounceTime`). this._ngZone.run(() => { this._storeCarouselData(); - this._cdr.markForCheck(); + // this._cdr.markForCheck(); }); }) ); diff --git a/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.ts b/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.ts index 44a8d4a..cc7761d 100644 --- a/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.ts +++ b/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.ts @@ -1,3 +1,5 @@ +import { signal } from '@angular/core'; + export class NguCarouselStore { constructor( public touch = new Touch(), @@ -24,8 +26,8 @@ export class NguCarouselStore { public dexVal = 0, public touchTransform = 0, public isEnd = false, - public isFirst = true, - public isLast = false, + public readonly isFirst = signal(true), + public readonly isLast = signal(false), public RTL = false, public point = true, public velocity = 1 @@ -71,7 +73,13 @@ declare interface TransformInterface { // This is misspelled. Must be changed to `Transform`. export class Transfrom implements TransformInterface { public xl? = 0; - constructor(public xs = 0, public sm = 0, public md = 0, public lg = 0, public all = 0) {} + constructor( + public xs = 0, + public sm = 0, + public md = 0, + public lg = 0, + public all = 0 + ) {} } // Interface is declared to prevent property-minification @@ -91,7 +99,12 @@ declare interface BreakpointsInterface { * {sm: 576, md: 768, lg: 992, xl: 1200} */ export class Breakpoints implements BreakpointsInterface { - constructor(public sm = 768, public md = 992, public lg = 1200, public xl = 1200) {} + constructor( + public sm = 768, + public md = 992, + public lg = 1200, + public xl = 1200 + ) {} } export class NguCarouselConfig { diff --git a/libs/ngu/carousel/src/lib/ngu-carousel/ngu-window-scroll-listener.ts b/libs/ngu/carousel/src/lib/ngu-carousel/ngu-window-scroll-listener.ts index f6e84c8..ab381b1 100644 --- a/libs/ngu/carousel/src/lib/ngu-carousel/ngu-window-scroll-listener.ts +++ b/libs/ngu/carousel/src/lib/ngu-carousel/ngu-window-scroll-listener.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, NgZone, OnDestroy } from '@angular/core'; +import { Injectable, NgZone, OnDestroy, inject } from '@angular/core'; import { Subject, fromEvent } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -8,9 +8,11 @@ import { IS_BROWSER } from '../symbols'; export class NguWindowScrollListener extends Subject implements OnDestroy { private readonly _destroy$ = new Subject(); - constructor(@Inject(IS_BROWSER) isBrowser: boolean, ngZone: NgZone) { + constructor() { super(); + const isBrowser = inject(IS_BROWSER); + const ngZone = inject(NgZone); // Note: this service is shared between multiple `NguCarousel` components and each instance // doesn't add new events listener for the `window`. if (isBrowser) {