// Import the core angular services. import { Directive } from "@angular/core"; // ----------------------------------------------------------------------------------- // // ----------------------------------------------------------------------------------- // // CAUTION: Animation names / key-frames cannot be scoped to a component. As such, each // animation name must be globally-unique to the rendered page. We're going to use an // incrementing key to make sure all simple-slider animations are uniquely named. var incrementingID = 0; interface SlideshowConfig { count: number ; pause: number; transition: number; } @Directive({ selector: "[simpleSlider]", inputs: [ "config: simpleSlider" ], host: { "[attr.data-simple-slider-id]": "id", "[class.simple-slider-directive]": "true" } }) export class SimpleSliderDirective { public config!: SlideshowConfig; public id: string; private styleElement: HTMLStyleElement | null; // I initialize the child component. constructor() { this.id = `simple-slider-${ ++incrementingID }`; this.styleElement = null; } // --- // PUBLIC METHODS. // --- // I get called once when the component is being unmounted. public ngOnDestroy() : void { // CAUTION: The ngOnInit() is NOT ALWAYS CALLED prior to the host being // destroyed. As such, we have to check to see if our style-element was // ever created. if ( this.styleElement ) { document.head.removeChild( this.styleElement ); } } // I get called once after the inputs have been bound for the first time. public ngOnInit() : void { // Technically, the inputs can be bound more than once. However, for the sake of // simplicity, we're going to check the inputs just once and then use them to // generate static key-frames for our animation. var count = this.config.count; var pause = this.config.pause; // In milliseconds. var transition = this.config.transition; // In milliseconds. // The key-frames within an animation are defined as percentages, not times. As // such, we have to calculate the total time so that we can then figure out what // percentage of the total time each slide and transition will account for. var total = ( ( count * pause ) + ( count * transition ) ); var percentPause = ( pause / total * 100 ); var percentTransition = ( transition / total * 100 ); // Now that we have the timings translated into percentages, we have to build-up // the key-frame definitions. var keyframes: string[] = []; for ( var i = 0 ; i < count ; i++ ) { // For each key-frame, the FROM and TO will represent the starting and ending // percentage of each slide. Then, the difference between each subsequent // key-frame percentage will account for the transition timing between slides. var from = ( ( percentPause * i ) + ( percentTransition * i ) ); var to = ( from + percentPause ); // Each key-frame will move the contents left by 100% of the container width // (since each slide is assumed to take up 100% of the container dimensions). var offset = ( i * -100 ); keyframes.push( ` ${ from }% , ${ to }% { transform: translateX( ${ offset }% ) ; } ` ); } // Create a Style Tag with the dynamic key-frames as the tag-content. We're going // to target the current element based on the CLASS NAME and the DATA attribute // that has been configured in the Host() bindings. this.styleElement = document.createElement( "style" ); this.styleElement.type = "text/css"; this.styleElement.textContent = ` @keyframes ${ this.id }-keyframes { ${ keyframes.join( "" ) } } .simple-slider-directive[ data-simple-slider-id = '${ this.id }' ] { animation-duration: ${ total }ms ; animation-iteration-count: infinite ; animation-name: ${ this.id }-keyframes ; } `; document.head.appendChild( this.styleElement ); } }