import { Component } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: './app.component.html'
})
export class AppComponent {
myText = 'A String from the Component';
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { TickerDirective } from './ticker.directive';
@NgModule({
imports: [
BrowserModule,
],
declarations: [
AppComponent,
TickerDirective
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
#ghost{
display: inline-block;
height: 0;
position: absolute;
}
.tickerContainer{
overflow-x: hidden;
overflow-y: scroll;
white-space: nowrap;
}
.myStyles{
background: #eee;
color: blue;
border: 1px solid #ddd;
max-width: 200px;
cursor: pointer;
}
<!DOCTYPE html>
<html>
<head>
<script>document.write('<base href="' + document.location + '" />');</script>
<title>Angular Tour of Heroes</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<!-- Polyfills for older browsers -->
<script src="https://unpkg.com/core-js/client/shim.min.js"></script>
<script src="https://unpkg.com/zone.js@0.6.25?main=browser"></script>
<script src="https://unpkg.com/reflect-metadata@0.1.8"></script>
<script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>
<script src="https://cdn.rawgit.com/angular/angular.io/b3c65a9/public/docs/_examples/_boilerplate/systemjs.config.web.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<my-app>Loading...</my-app>
</body>
</html>
{
"compilerOptions": {
"target": "es5",
"module": "es2015",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es2015", "dom"],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"typeRoots": [
"../../node_modules/@types/"
]
},
"files": [
"app/app.module.ts",
"app/main-aot.ts"
],
"angularCompilerOptions": {
"genDir": "aot",
"skipMetadataEmit" : true
}
}
import { Directive, ElementRef, HostListener, Input, Renderer, OnInit } from '@angular/core';
@Directive({ selector: '[ticker]' })
export class TickerDirective implements OnInit {
margin: number; // margin of the text nodes which decrements to tick to the left
interval: any; // used to kill the setTimout
firstNode: any; // the node which displays first and without mouseover
view: any[]; // an array of nodes attached to the main node to provide a seemless scroll
textWidth: number;
idle: boolean;
@Input('speed') speed: number; // milliseconds between ticks
@Input('padding-right') paddingRight: number;
@Input('size') size: number;
@Input('trigger') trigger: string;
@Input('text') text: string;
constructor(private el: ElementRef, private r: Renderer) { }
@HostListener('mouseenter') onMouseEnter(): void {
if ( this.trigger === 'onMouseEnter') {
this.initTicker();
}
}
@HostListener('click') onClick(): void {
if ( this.trigger === 'onClick') {
if ( this.idle ) {
this.initTicker();
} else {
this.reset();
}
this.idle = !this.idle;
}
}
initTicker(): void {
if (this.tickerNeeded()) {
this.margin = 0;
this.view = [
this.createTickerNode( '<T>', this.text ),
this.createTickerNode( '<T>', this.text )
];
this.r.attachViewAfter( this.firstNode, this.view );
this.moveLeft();
}
}
@HostListener('mouseleave') onMouseLeave(): void {
if (this.tickerNeeded() && this.trigger === 'onMouseEnter') {
this.reset();
}
}
reset(): void {
clearInterval( this.interval );
this.r.detachView( this.view );
this.r.setElementStyle( this.firstNode, 'margin-left', '0' );
}
ngOnInit(): void {
this.setIgnoredAtts();
this.textWidth = this.getTextWidth();
this.firstNode = this.createTickerNode( this.firstNode, this.text );
if ( this.trigger === 'auto' && this.tickerNeeded()) {
this.initTicker();
}
}
setIgnoredAtts(): void {
if ( !this.paddingRight ) { this.paddingRight = 16; }
if ( !this.speed ) { this.speed = 25; }
if ( !this.trigger ) { this.trigger = 'onMouseEnter'; }
if ( !this.size ) { this.size = 16; }
if ( !this.text ) { this.text = 'You need to add the [text] attribute to the "ticker" directive'; }
this.idle = true;
}
createTickerNode( self: any , text: string ): any {
self = this.r.createElement( this.el.nativeElement, 'span' );
this.r.setElementStyle( self, 'padding-right', this.paddingRight + 'px');
this.r.setElementStyle( self, 'font-size', this.size + 'px');
// this.r.setText( self, text ); // not working, oddly
self.innerHTML = this.text; // quick fix
return self;
}
moveLeft(): void {
let resetMargin = ( this.textWidth + this.paddingRight ) * -2 ;
this.interval = setInterval(() => {
this.r.setElementStyle( this.firstNode, 'margin-left', this.margin-- + 'px' );
if (this.margin < resetMargin) { this.margin = 0; }
}, this.speed);
}
getTextWidth(): number {
let t = this.r.createElement( document.getElementById('ghost'), 'div' );
// this.r.setText( t, this.text ); // not working, oddly
t.innerHTML = this.text; // quick fix
this.r.setElementStyle( t, 'font-size', this.size + 'px');
let w = t.offsetWidth;
t.innerHTML = '';
return w;
}
tickerNeeded(): boolean {
return this.textWidth > this.el.nativeElement.parentElement.offsetWidth - 2;
}
}
<h1>Angular2 Ticker Directive</h1>
<h3>Starts on Mouse Enter (defaults only)</h3>
<div class="tickerContainer myStyles">
<div ticker></div>
</div>
<hr>
<h3>Starts on Click</h3>
<div class="tickerContainer myStyles">
<div ticker [trigger]="'onClick'" [text]="myText" [speed]="15" [padding-right]="0" [size]="40"></div>
</div>
<hr>
<h3>Auto Start</h3>
<div class="tickerContainer myStyles">
<div ticker [trigger]="'auto'" [text]="'A statically-typed, long string'" [speed]="50" [padding-right]="40" [size]="20"></div>
</div>
<div id="ghost"></div>