searchbox
This commit is contained in:
20
src/app/animations.ts
Normal file
20
src/app/animations.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import {animate, query, stagger, state, style, transition, trigger} from '@angular/animations';
|
||||||
|
|
||||||
|
export const fade = [
|
||||||
|
// the fade-in/fade-out animation.
|
||||||
|
trigger('fade', [
|
||||||
|
|
||||||
|
// the "in" style determines the "resting" state of the element when it is visible.
|
||||||
|
state('in', style({opacity: 1, display: 'block', transform: 'translateY(0px)'})),
|
||||||
|
|
||||||
|
// fade in when created. this could also be written as transition('void => *')
|
||||||
|
transition(':enter', [
|
||||||
|
style({opacity: 0, display: 'block', transform: 'translateY(-20px)'}),
|
||||||
|
animate(300)
|
||||||
|
]),
|
||||||
|
|
||||||
|
// fade out when destroyed. this could also be written as transition('void => *')
|
||||||
|
transition(':leave',
|
||||||
|
animate(300, style({opacity: 0, display: 'block', transform: 'translateY(-20px)'})))
|
||||||
|
])
|
||||||
|
];
|
||||||
@@ -16,6 +16,7 @@ import {AngularFirestoreModule} from '@angular/fire/firestore';
|
|||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
ServiceWorkerModule.register('ngsw-worker.js', {enabled: environment.production}),
|
ServiceWorkerModule.register('ngsw-worker.js', {enabled: environment.production}),
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ import {NgModule} from '@angular/core';
|
|||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {NavigationComponent} from './navigation/navigation.component';
|
import {NavigationComponent} from './navigation/navigation.component';
|
||||||
import {RouterModule} from '@angular/router';
|
import {RouterModule} from '@angular/router';
|
||||||
|
import {FilterComponent} from './navigation/filter/filter.component';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
NavigationComponent
|
NavigationComponent,
|
||||||
|
FilterComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<input placeholder="Suche" (input)="onInputChange($event.target.value)">
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
@import "../../../../styles/styles";
|
||||||
|
|
||||||
|
input {
|
||||||
|
font-size: 16px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
color: #888;
|
||||||
|
margin-right: 20px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-bottom: 2px solid @primary-color;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { FilterComponent } from './filter.component';
|
||||||
|
|
||||||
|
describe('FilterComponent', () => {
|
||||||
|
let component: FilterComponent;
|
||||||
|
let fixture: ComponentFixture<FilterComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ FilterComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(FilterComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {Router} from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-filter',
|
||||||
|
templateUrl: './filter.component.html',
|
||||||
|
styleUrls: ['./filter.component.less']
|
||||||
|
})
|
||||||
|
export class FilterComponent {
|
||||||
|
|
||||||
|
constructor(private router: Router) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public onInputChange(text: string): void {
|
||||||
|
const route = text
|
||||||
|
? this.router.createUrlTree(['songs'], {queryParams: {q: text}})
|
||||||
|
: this.router.createUrlTree(['songs']);
|
||||||
|
|
||||||
|
this.router.navigateByUrl(route);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
<nav class="head">
|
<nav class="head">
|
||||||
<a href="#" routerLink="/songs" routerLinkActive="active">Inhalt</a>
|
<div class="links">
|
||||||
|
<a href="#" routerLink="/songs" routerLinkActive="active">Inhalt</a></div>
|
||||||
|
<div class="actions">
|
||||||
|
<app-filter></app-filter>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ nav {
|
|||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -38,3 +39,9 @@ nav {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ describe('SongService', () => {
|
|||||||
|
|
||||||
it('should list songs', async(() => {
|
it('should list songs', async(() => {
|
||||||
const service: SongService = TestBed.get(SongService);
|
const service: SongService = TestBed.get(SongService);
|
||||||
service.list().subscribe(songs => {
|
service.list$().subscribe(songs => {
|
||||||
expect(songs).toEqual(<any>[
|
expect(songs).toEqual(<any>[
|
||||||
{title: 'title1'}
|
{title: 'title1'}
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export class SongService {
|
|||||||
constructor(private songDataService: SongDataService) {
|
constructor(private songDataService: SongDataService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public list = (): Observable<Song[]> => this.songDataService.list();
|
public list$ = (): Observable<Song[]> => this.songDataService.list();
|
||||||
public read = (songId: string): Observable<Song | undefined> => this.songDataService.read(songId);
|
public read = (songId: string): Observable<Song | undefined> => this.songDataService.read(songId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: @primary-color;
|
background: @primary-color;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<app-card [padding]="false">
|
<app-card [padding]="false" *ngIf="songs$ | async as songs" [@fade]>
|
||||||
<app-list-item *ngFor="let song of songs | async" [song]="song" [routerLink]="song.id"></app-list-item>
|
<app-list-item *ngFor="let song of songs" [song]="song" [routerLink]="song.id"></app-list-item>
|
||||||
</app-card>
|
</app-card>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ describe('SongListComponent', () => {
|
|||||||
|
|
||||||
it('should read songs from SongService', fakeAsync(() => {
|
it('should read songs from SongService', fakeAsync(() => {
|
||||||
tick();
|
tick();
|
||||||
expect(component.songs).toEqual(<any>[
|
expect(component.songs$).toEqual(<any>[
|
||||||
{title: 'title1'}
|
{title: 'title1'}
|
||||||
]);
|
]);
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,24 +1,50 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {SongService} from '../services/song.service';
|
import {SongService} from '../services/song.service';
|
||||||
import {Song} from '../models/song';
|
import {Song} from '../models/song';
|
||||||
import {map} from 'rxjs/operators';
|
import {debounceTime, map} from 'rxjs/operators';
|
||||||
import {Observable} from 'rxjs';
|
import {combineLatest, Observable} from 'rxjs';
|
||||||
|
import {fade} from '../../animations';
|
||||||
|
import {ActivatedRoute} from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-songs',
|
selector: 'app-songs',
|
||||||
templateUrl: './song-list.component.html',
|
templateUrl: './song-list.component.html',
|
||||||
styleUrls: ['./song-list.component.less']
|
styleUrls: ['./song-list.component.less'],
|
||||||
|
animations: [fade]
|
||||||
})
|
})
|
||||||
export class SongListComponent implements OnInit {
|
export class SongListComponent implements OnInit {
|
||||||
public songs: Observable<Song[]>;
|
public songs$: Observable<Song[]>;
|
||||||
|
|
||||||
constructor(private songService: SongService) {
|
constructor(private songService: SongService, private activatedRoute: ActivatedRoute) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.songs = this.songService.list().pipe(map(songs =>
|
const filter$ = this.activatedRoute.queryParams.pipe(
|
||||||
songs.sort((a, b) => a.number - b.number)
|
debounceTime(300),
|
||||||
));
|
map(_ => _.q)
|
||||||
|
);
|
||||||
|
|
||||||
|
const songs$ = this.songService.list$().pipe(
|
||||||
|
map(songs => songs.sort((a, b) => a.number - b.number)),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.songs$ = combineLatest([filter$, songs$]).pipe(
|
||||||
|
map(_ => _[1].filter(song => this.filter(song, _[0])))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private filter(song: Song, filterValue: string): boolean {
|
||||||
|
if (!filterValue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const textMatch = song.text && this.normalize(song.text).indexOf(this.normalize(filterValue)) !== -1;
|
||||||
|
const titleMatch = song.title && this.normalize(song.title).indexOf(this.normalize(filterValue)) !== -1;
|
||||||
|
|
||||||
|
return textMatch || titleMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalize( input: string): string {
|
||||||
|
return input.toLowerCase().replace(/\s/g, '');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@primary-color: #5285ff;
|
@primary-color: #4286f4;
|
||||||
|
|
||||||
@navigation-background: #fffffff1;
|
@navigation-background: #fffffff1;
|
||||||
@navigation-link-color: #555;
|
@navigation-link-color: #555;
|
||||||
|
|||||||
Reference in New Issue
Block a user