1
1
import {
2
+ AfterContentChecked ,
3
+ AfterContentInit ,
2
4
Component ,
3
- Directive ,
4
- TemplateRef ,
5
5
ContentChildren ,
6
- QueryList ,
6
+ Directive ,
7
+ EventEmitter ,
8
+ Inject ,
7
9
Input ,
8
- AfterContentChecked ,
9
- OnInit ,
10
+ NgZone ,
10
11
OnChanges ,
11
12
OnDestroy ,
12
13
Output ,
13
- EventEmitter
14
+ PLATFORM_ID ,
15
+ QueryList ,
16
+ TemplateRef
14
17
} from '@angular/core' ;
18
+ import { isPlatformBrowser } from '@angular/common' ;
19
+
15
20
import { NgbCarouselConfig } from './carousel-config' ;
16
21
22
+ import { Subject , timer } from 'rxjs' ;
23
+ import { filter , map , switchMap , takeUntil } from 'rxjs/operators' ;
24
+
17
25
let nextId = 0 ;
18
26
19
27
/**
@@ -39,35 +47,37 @@ export class NgbSlide {
39
47
'class' : 'carousel slide' ,
40
48
'[style.display]' : '"block"' ,
41
49
'tabIndex' : '0' ,
42
- '(mouseenter)' : 'onMouseEnter ()' ,
43
- '(mouseleave)' : 'onMouseLeave ()' ,
44
- '(keydown.arrowLeft)' : 'keyPrev ()' ,
45
- '(keydown.arrowRight)' : 'keyNext ()'
50
+ '(mouseenter)' : 'pauseOnHover && pause ()' ,
51
+ '(mouseleave)' : 'pauseOnHover && cycle ()' ,
52
+ '(keydown.arrowLeft)' : 'keyboard && prev ()' ,
53
+ '(keydown.arrowRight)' : 'keyboard && next ()'
46
54
} ,
47
55
template : `
48
56
<ol class="carousel-indicators" *ngIf="showNavigationIndicators">
49
57
<li *ngFor="let slide of slides" [id]="slide.id" [class.active]="slide.id === activeId"
50
- (click)="cycleToSelected (slide.id, getSlideEventDirection(activeId, slide.id) )"></li>
58
+ (click)="select (slide.id); pauseOnHover && pause( )"></li>
51
59
</ol>
52
60
<div class="carousel-inner">
53
61
<div *ngFor="let slide of slides" class="carousel-item" [class.active]="slide.id === activeId">
54
62
<ng-template [ngTemplateOutlet]="slide.tplRef"></ng-template>
55
63
</div>
56
64
</div>
57
- <a class="carousel-control-prev" role="button" (click)="cycleToPrev ()" *ngIf="showNavigationArrows">
65
+ <a class="carousel-control-prev" role="button" (click)="prev ()" *ngIf="showNavigationArrows">
58
66
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
59
67
<span class="sr-only" i18n="@@ngb.carousel.previous">Previous</span>
60
68
</a>
61
- <a class="carousel-control-next" role="button" (click)="cycleToNext ()" *ngIf="showNavigationArrows">
69
+ <a class="carousel-control-next" role="button" (click)="next ()" *ngIf="showNavigationArrows">
62
70
<span class="carousel-control-next-icon" aria-hidden="true"></span>
63
71
<span class="sr-only" i18n="@@ngb.carousel.next">Next</span>
64
72
</a>
65
- `
73
+ `
66
74
} )
67
75
export class NgbCarousel implements AfterContentChecked ,
68
- OnDestroy , OnInit , OnChanges {
76
+ AfterContentInit , OnChanges , OnDestroy {
69
77
@ContentChildren ( NgbSlide ) slides : QueryList < NgbSlide > ;
70
- private _slideChangeInterval ;
78
+
79
+ private _start$ = new Subject < void > ( ) ;
80
+ private _stop$ = new Subject < void > ( ) ;
71
81
72
82
/**
73
83
* The active slide id.
@@ -114,7 +124,7 @@ export class NgbCarousel implements AfterContentChecked,
114
124
*/
115
125
@Output ( ) slide = new EventEmitter < NgbSlideEvent > ( ) ;
116
126
117
- constructor ( config : NgbCarouselConfig ) {
127
+ constructor ( config : NgbCarouselConfig , @ Inject ( PLATFORM_ID ) private _platformId , private _ngZone : NgZone ) {
118
128
this . interval = config . interval ;
119
129
this . wrap = config . wrap ;
120
130
this . keyboard = config . keyboard ;
@@ -123,117 +133,77 @@ export class NgbCarousel implements AfterContentChecked,
123
133
this . showNavigationIndicators = config . showNavigationIndicators ;
124
134
}
125
135
136
+ ngAfterContentInit ( ) {
137
+ // setInterval() doesn't play well with SSR and protractor,
138
+ // so we should run it in the browser and outside Angular
139
+ if ( isPlatformBrowser ( this . _platformId ) ) {
140
+ this . _ngZone . runOutsideAngular ( ( ) => {
141
+ this . _start$
142
+ . pipe (
143
+ map ( ( ) => this . interval ) , filter ( interval => interval > 0 ) ,
144
+ switchMap ( interval => timer ( interval ) . pipe ( takeUntil ( this . _stop$ ) ) ) )
145
+ . subscribe ( ( ) => this . _ngZone . run ( ( ) => this . next ( ) ) ) ;
146
+
147
+ this . _start$ . next ( ) ;
148
+ } ) ;
149
+ }
150
+ }
151
+
126
152
ngAfterContentChecked ( ) {
127
153
let activeSlide = this . _getSlideById ( this . activeId ) ;
128
154
this . activeId = activeSlide ? activeSlide . id : ( this . slides . length ? this . slides . first . id : null ) ;
129
155
}
130
156
131
- ngOnInit ( ) { this . _startTimer ( ) ; }
157
+ ngOnDestroy ( ) { this . _stop$ . next ( ) ; }
132
158
133
159
ngOnChanges ( changes ) {
134
160
if ( 'interval' in changes && ! changes [ 'interval' ] . isFirstChange ( ) ) {
135
- this . _restartTimer ( ) ;
161
+ this . _start$ . next ( ) ;
136
162
}
137
163
}
138
164
139
- ngOnDestroy ( ) { clearInterval ( this . _slideChangeInterval ) ; }
140
-
141
165
/**
142
166
* Navigate to a slide with the specified identifier.
143
167
*/
144
- select ( slideId : string ) {
145
- this . cycleToSelected ( slideId , this . getSlideEventDirection ( this . activeId , slideId ) ) ;
146
- this . _restartTimer ( ) ;
147
- }
168
+ select ( slideId : string ) { this . _cycleToSelected ( slideId , this . _getSlideEventDirection ( this . activeId , slideId ) ) ; }
148
169
149
170
/**
150
171
* Navigate to the next slide.
151
172
*/
152
- prev ( ) {
153
- this . cycleToPrev ( ) ;
154
- this . _restartTimer ( ) ;
155
- }
173
+ prev ( ) { this . _cycleToSelected ( this . _getPrevSlide ( this . activeId ) , NgbSlideEventDirection . RIGHT ) ; }
156
174
157
175
/**
158
176
* Navigate to the next slide.
159
177
*/
160
- next ( ) {
161
- this . cycleToNext ( ) ;
162
- this . _restartTimer ( ) ;
163
- }
178
+ next ( ) { this . _cycleToSelected ( this . _getNextSlide ( this . activeId ) , NgbSlideEventDirection . LEFT ) ; }
164
179
165
180
/**
166
181
* Stops the carousel from cycling through items.
167
182
*/
168
- pause ( ) { this . _stopTimer ( ) ; }
183
+ pause ( ) { this . _stop$ . next ( ) ; }
169
184
170
185
/**
171
186
* Restarts cycling through the carousel slides from left to right.
172
187
*/
173
- cycle ( ) { this . _startTimer ( ) ; }
174
-
175
- cycleToNext ( ) { this . cycleToSelected ( this . _getNextSlide ( this . activeId ) , NgbSlideEventDirection . LEFT ) ; }
188
+ cycle ( ) { this . _start$ . next ( ) ; }
176
189
177
- cycleToPrev ( ) { this . cycleToSelected ( this . _getPrevSlide ( this . activeId ) , NgbSlideEventDirection . RIGHT ) ; }
178
-
179
- cycleToSelected ( slideIdx : string , direction : NgbSlideEventDirection ) {
190
+ private _cycleToSelected ( slideIdx : string , direction : NgbSlideEventDirection ) {
180
191
let selectedSlide = this . _getSlideById ( slideIdx ) ;
181
- if ( selectedSlide ) {
182
- if ( selectedSlide . id !== this . activeId ) {
183
- this . slide . emit ( { prev : this . activeId , current : selectedSlide . id , direction : direction } ) ;
184
- }
192
+ if ( selectedSlide && selectedSlide . id !== this . activeId ) {
193
+ this . slide . emit ( { prev : this . activeId , current : selectedSlide . id , direction : direction } ) ;
194
+ this . _start$ . next ( ) ;
185
195
this . activeId = selectedSlide . id ;
186
196
}
187
197
}
188
198
189
- getSlideEventDirection ( currentActiveSlideId : string , nextActiveSlideId : string ) : NgbSlideEventDirection {
199
+ private _getSlideEventDirection ( currentActiveSlideId : string , nextActiveSlideId : string ) : NgbSlideEventDirection {
190
200
const currentActiveSlideIdx = this . _getSlideIdxById ( currentActiveSlideId ) ;
191
201
const nextActiveSlideIdx = this . _getSlideIdxById ( nextActiveSlideId ) ;
192
202
193
203
return currentActiveSlideIdx > nextActiveSlideIdx ? NgbSlideEventDirection . RIGHT : NgbSlideEventDirection . LEFT ;
194
204
}
195
205
196
- keyPrev ( ) {
197
- if ( this . keyboard ) {
198
- this . prev ( ) ;
199
- }
200
- }
201
-
202
- keyNext ( ) {
203
- if ( this . keyboard ) {
204
- this . next ( ) ;
205
- }
206
- }
207
-
208
- onMouseEnter ( ) {
209
- if ( this . pauseOnHover ) {
210
- this . pause ( ) ;
211
- }
212
- }
213
-
214
- onMouseLeave ( ) {
215
- if ( this . pauseOnHover ) {
216
- this . cycle ( ) ;
217
- }
218
- }
219
-
220
- private _restartTimer ( ) {
221
- this . _stopTimer ( ) ;
222
- this . _startTimer ( ) ;
223
- }
224
-
225
- private _startTimer ( ) {
226
- if ( this . interval > 0 ) {
227
- this . _slideChangeInterval = setInterval ( ( ) => { this . cycleToNext ( ) ; } , this . interval ) ;
228
- }
229
- }
230
-
231
- private _stopTimer ( ) { clearInterval ( this . _slideChangeInterval ) ; }
232
-
233
- private _getSlideById ( slideId : string ) : NgbSlide {
234
- let slideWithId : NgbSlide [ ] = this . slides . filter ( slide => slide . id === slideId ) ;
235
- return slideWithId . length ? slideWithId [ 0 ] : null ;
236
- }
206
+ private _getSlideById ( slideId : string ) : NgbSlide { return this . slides . find ( slide => slide . id === slideId ) ; }
237
207
238
208
private _getSlideIdxById ( slideId : string ) : number {
239
209
return this . slides . toArray ( ) . indexOf ( this . _getSlideById ( slideId ) ) ;
@@ -259,8 +229,8 @@ export class NgbCarousel implements AfterContentChecked,
259
229
}
260
230
261
231
/**
262
- * The payload of the slide event fired when the slide transition is completed
263
- */
232
+ * The payload of the slide event fired when the slide transition is completed
233
+ */
264
234
export interface NgbSlideEvent {
265
235
/**
266
236
* Previous slide id
0 commit comments