Skip to content

Commit 10749c5

Browse files
authored
feat(client): add carousel with autumn camp banners (banners stored o… (#319)
* feat(client): add carousel with autumn camp banners (banners stored on client side) KAP-243 * feat: add link to autumn camp banners
1 parent d832e1f commit 10749c5

File tree

13 files changed

+183
-10
lines changed

13 files changed

+183
-10
lines changed

apps/blog/src/app/app.config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@ import {
99
provideExperimentalZonelessChangeDetection,
1010
} from '@angular/core';
1111
import { provideClientHydration } from '@angular/platform-browser';
12+
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
1213
import {
1314
IsActiveMatchOptions,
1415
provideRouter,
1516
Router,
1617
withComponentInputBinding,
17-
withDisabledInitialNavigation,
1818
withInMemoryScrolling,
1919
withRouterConfig,
2020
withViewTransitions,
2121
} from '@angular/router';
22+
import { provideFastSVG } from '@push-based/ngx-fast-svg';
2223

2324
import { provideI18n } from '@angular-love/blog/i18n/data-access';
2425
import { blogShellRoutes } from '@angular-love/blog/shell/feature';
25-
import { provideFastSVG } from '@push-based/ngx-fast-svg';
2626

2727
import { environment } from '../environments/environment';
2828

@@ -70,6 +70,7 @@ export const appConfig: ApplicationConfig = {
7070
provideFastSVG({
7171
url: (name: string) => `assets/icons/${name}.svg`,
7272
}),
73+
provideAnimationsAsync(),
7374
environment.providers,
7475
],
7576
};
331 KB
Loading
331 KB
Loading
324 KB
Loading

libs/blog/ad-banner/ui/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './lib/ad-banner/ad-banner.component';
22
export * from './lib/ad-image-banner/ad-image-banner.component';
33
export * from './lib/ad-image-banner/ad-image-banner-data.interface';
44
export * from './lib/instances/al-indepth-banner.component';
5+
export * from './lib/instances/autumn-camp/al-autumn-camp-banner.component';
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export interface AdImageBanner {
22
url: string;
33
alt: string;
4-
slug: string;
4+
action: { type: 'slug'; slug: string } | { type: 'url'; url: string };
55
}

libs/blog/ad-banner/ui/src/lib/ad-image-banner/ad-image-banner.component.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,16 @@ export class AdImageBannerComponent {
1919
private readonly _localizeService = inject(AlLocalizeService);
2020

2121
navigateFromBanner(): void {
22-
this._router.navigate(
23-
this._localizeService.localizePath(['/', this.banner()!.slug]),
24-
);
22+
const banner = this.banner();
23+
switch (banner.action.type) {
24+
case 'slug':
25+
this._router.navigate(
26+
this._localizeService.localizePath(['/', banner.action.slug]),
27+
);
28+
break;
29+
case 'url':
30+
window.location.href = banner.action.url;
31+
break;
32+
}
2533
}
2634
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { animate, AnimationBuilder, style } from '@angular/animations';
2+
import {
3+
afterNextRender,
4+
DestroyRef,
5+
Directive,
6+
effect,
7+
ElementRef,
8+
inject,
9+
input,
10+
TemplateRef,
11+
ViewContainerRef,
12+
} from '@angular/core';
13+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
14+
import { debounceTime, interval, tap } from 'rxjs';
15+
16+
/**
17+
* This directive creates an infinite slider from a collection of items (e.g. banners).
18+
* @example
19+
* ```html
20+
* <div class="overflow-hidden">
21+
* <div class="flex">
22+
* <some-banner-component
23+
* *alInfiniteSlider="let banner of banners()"
24+
* class="flex-shrink-0 flex-grow-0 basis-full"
25+
* [banner]="banner"
26+
* />
27+
* </div>
28+
* </div>
29+
* ```
30+
**/
31+
@Directive({
32+
selector: '[alInfiniteSlider]',
33+
standalone: true,
34+
})
35+
export class AlInfiniteSliderDirective {
36+
readonly alInfiniteSliderOf = input.required<unknown[]>();
37+
readonly msPerSlide = input<number>(7000);
38+
readonly msPerAnimation = input<number>(1000);
39+
40+
private readonly _templateRef = inject(TemplateRef);
41+
private readonly _viewContainerRef = inject(ViewContainerRef);
42+
private readonly _builder = inject(AnimationBuilder);
43+
private readonly _element = inject(ElementRef);
44+
private readonly _destroyRef = inject(DestroyRef);
45+
46+
constructor() {
47+
this._initView();
48+
this._startSlider();
49+
}
50+
51+
private _initView() {
52+
effect(() => {
53+
this.alInfiniteSliderOf()?.forEach((item, index) => {
54+
// Create a new embedded view for each item in the collection
55+
this._viewContainerRef.createEmbeddedView(this._templateRef, {
56+
$implicit: item, // Pass the current item as the context
57+
index: index, // Pass the current index as part of the context
58+
});
59+
});
60+
});
61+
}
62+
63+
private _startSlider() {
64+
afterNextRender(() => {
65+
const animationPlayer = this._builder
66+
.build([
67+
style({ transform: `translateX(0%)` }),
68+
animate(
69+
`${this.msPerAnimation()}ms ease-in-out`,
70+
style({ transform: `translateX(-100%)` }),
71+
),
72+
])
73+
.create(this._element.nativeElement.parentElement);
74+
75+
interval(this.msPerSlide())
76+
.pipe(
77+
tap(() => animationPlayer.play()),
78+
debounceTime(this.msPerSlide() / 2),
79+
tap(() => {
80+
// rearrange the slides so 1 | 2 | 3 becomes 2 | 3 | 1
81+
this._moveFirstSlideAtTheEnd();
82+
// reset the animation to compensate rearranging the slides
83+
animationPlayer.reset();
84+
}),
85+
takeUntilDestroyed(this._destroyRef),
86+
)
87+
.subscribe();
88+
});
89+
}
90+
91+
private _moveFirstSlideAtTheEnd() {
92+
// Detach the first view and append it to the end
93+
this._viewContainerRef.move(
94+
this._viewContainerRef.get(0)!,
95+
this._viewContainerRef.length - 1,
96+
);
97+
}
98+
}
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
22

3+
import { AdImageBanner } from '../ad-image-banner/ad-image-banner-data.interface';
34
import { AdImageBannerComponent } from '../ad-image-banner/ad-image-banner.component';
45

56
@Component({
@@ -8,13 +9,16 @@ import { AdImageBannerComponent } from '../ad-image-banner/ad-image-banner.compo
89
imports: [AdImageBannerComponent],
910
changeDetection: ChangeDetectionStrategy.OnPush,
1011
template: `
11-
<al-ad-image-banner [banner]="banner()!" />
12+
<al-ad-image-banner [banner]="banner()" />
1213
`,
1314
})
1415
export class AlIndepthBannerComponent {
15-
protected readonly banner = signal({
16+
protected readonly banner = signal<AdImageBanner>({
1617
url: 'assets/AL-AID-banner.png',
1718
alt: 'Banner - Two blogs join forces',
18-
slug: 'angular-love-joins-forces-with-angular-in-depth-to-bring-you-the-best-angular-on-the-web',
19+
action: {
20+
type: 'slug',
21+
slug: 'angular-love-joins-forces-with-angular-in-depth-to-bring-you-the-best-angular-on-the-web',
22+
},
1923
});
2024
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
2+
3+
import { AdImageBanner } from '../../ad-image-banner/ad-image-banner-data.interface';
4+
import { AdImageBannerComponent } from '../../ad-image-banner/ad-image-banner.component';
5+
import { AlInfiniteSliderDirective } from '../../infinite-slider-directive/al-infinite-slider.directive';
6+
7+
import { AUTUMN_CAMP_BANNERS } from './autumn-camp-banners.const';
8+
9+
@Component({
10+
selector: 'al-autumn-camp-banner',
11+
standalone: true,
12+
imports: [AdImageBannerComponent, AlInfiniteSliderDirective],
13+
changeDetection: ChangeDetectionStrategy.OnPush,
14+
template: `
15+
<div class="overflow-hidden">
16+
<div class="flex">
17+
<al-ad-image-banner
18+
*alInfiniteSlider="let banner of banners()"
19+
class="flex-shrink-0 flex-grow-0 basis-full"
20+
[banner]="banner"
21+
></al-ad-image-banner>
22+
</div>
23+
</div>
24+
`,
25+
})
26+
export class AlAutumnCampBannerComponent {
27+
protected readonly banners = signal<AdImageBanner[]>(AUTUMN_CAMP_BANNERS);
28+
}

0 commit comments

Comments
 (0)