Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 15 additions & 17 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,22 @@ import { UserListComponent } from './user-list/user-list.component';

import { ErrorMetadataService } from './services/error-metadata.service';
import { HighlightTextPipe } from './pipes/highlight-text.pipe';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { UserListInterceptorService } from './mocks/user-list-interceptor.service';
import { FilterLocationidPipe } from './pipes/filter-locationid.pipe';

@NgModule({
declarations: [
AppComponent,
UserListComponent,
HighlightTextPipe
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule
],
providers: [
// { provide: ErrorHandler, useClass: ErrorMetadataService },
{ provide: HTTP_INTERCEPTORS, useClass: UserListInterceptorService, multi: true }
],
bootstrap: [AppComponent]
declarations: [
AppComponent,
UserListComponent,
HighlightTextPipe,
FilterLocationidPipe
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [
{ provide: ErrorHandler, useClass: ErrorMetadataService }
],
bootstrap: [AppComponent]
})
export class AppModule { }
32 changes: 16 additions & 16 deletions src/app/mocks/user-list-interceptor.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { UserListInterceptorService } from './user-list-interceptor.service';

describe('UserListInterceptorService', () => {
let service: UserListInterceptorService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(UserListInterceptorService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
import { TestBed } from '@angular/core/testing';
import { UserListInterceptorService } from './user-list-interceptor.service';
describe('UserListInterceptorService', () => {
let service: UserListInterceptorService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(UserListInterceptorService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
91 changes: 50 additions & 41 deletions src/app/mocks/user-list-interceptor.service.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,50 @@
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable({
providedIn: 'root'
})
export class UserListInterceptorService implements HttpInterceptor {
private readonly API_URL = '/mock/api/filter';
private readonly STORAGE_KEY = 'MOCK_API_FILTER';

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<string>> {

if (request.url === this.API_URL && request.method === 'GET') {
return this.getFilter();
}

if (request.url === this.API_URL && request.method === 'PUT') {
return this.setFilter(request.body);
}

return next.handle(request);
}

private getFilter(): Observable<HttpResponse<string>> {
return new Observable(observer => {
observer.next(new HttpResponse<string>({
status: 200,
body: window.localStorage.getItem(this.STORAGE_KEY)
}));

observer.complete();
});
}

private setFilter(filter: string): Observable<HttpResponse<string>> {
window.localStorage.setItem(this.STORAGE_KEY, filter);
return this.getFilter();
}

}
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
HttpResponse,
} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable({
providedIn: 'root',
})
export class UserListInterceptorService implements HttpInterceptor {
private readonly API_URL = '/mock/api/filter';
private readonly STORAGE_KEY = 'MOCK_API_FILTER';

intercept(
request: HttpRequest<any>,
next: HttpHandler,
): Observable<HttpEvent<string>> {
if (request.url === this.API_URL && request.method === 'GET') {
return this.getFilter();
}

if (request.url === this.API_URL && request.method === 'PUT') {
return this.setFilter(request.body);
}

return next.handle(request);
}

private getFilter(): Observable<HttpResponse<string>> {
return new Observable((observer) => {
observer.next(
new HttpResponse<string>({
status: 200,
body: window.localStorage.getItem(this.STORAGE_KEY),
}),
);

observer.complete();
});
}

private setFilter(filter: string): Observable<HttpResponse<string>> {
window.localStorage.setItem(this.STORAGE_KEY, filter);
return this.getFilter();
}
}
8 changes: 8 additions & 0 deletions src/app/pipes/filter-locationid.pipe.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { FilterLocationidPipe } from './filter-locationid.pipe';

describe('FilterLocationidPipe', () => {
it('create an instance', () => {
const pipe = new FilterLocationidPipe();
expect(pipe).toBeTruthy();
});
});
15 changes: 15 additions & 0 deletions src/app/pipes/filter-locationid.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Pipe, PipeTransform } from '@angular/core';
import { User } from '../user/user';

@Pipe({
name: 'filterLocationid',
})
export class FilterLocationidPipe implements PipeTransform {
transform(users: User[] | null, ...ids: number[]): User[] | null {
if (users === null || ids.length === 0) {
return users;
}

return users.filter((user) => ids.some((id) => user.locationId === id));
}
}
23 changes: 10 additions & 13 deletions src/app/pipes/highlight-text.pipe.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
name: 'highlightText'
name: 'highlightText',
})
export class HighlightTextPipe implements PipeTransform {
transform(value: string, filter: string): string {
if (filter.length === 0) return value;

transform(value: string, filter: string): string {
if (filter.length === 0) {
return value;
}
const search = new RegExp(filter, 'ig'); //i => ignore case, g => global
return value.replace(search, (match) => {
return `<span class="highlight-text">${match}</span>`;
});
}
}

const search = new RegExp(filter, 'ig');

return value.replace(search, (match) => {
return `<span class="highlight-text">${match}</span>`;
});
}

}
//cross site scripting, angular security
22 changes: 19 additions & 3 deletions src/app/user-list/user-list.component.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
<div class="input-group">
<input type="text" class="form-control" placeholder="Find Users by Name" #filter (keyup)="update(filter.value)">
<input
type="text"
class="form-control"
placeholder="Find Users by Name"
#filter
(keyup)="update(filter.value)"
/>

<span class="input-group-btn">
<button class="btn btn-default" type="button" (click)="update(''); filter.value = ''">Clear</button>
<button
class="btn btn-default"
type="button"
(click)="update(''); filter.value = ''"
>
Clear
</button>
</span>
</div>

<ul class="user-list">
<li *ngFor="let user of (users | async)" class="user-list-item" [innerHtml]="user.name | highlightText : filter.value"></li>
<li
*ngFor="let user of users | filterLocationid: 3 : 4 : 5"
class="user-list-item"
[innerHtml]="user.name | highlightText: filter.value"
></li>
</ul>
27 changes: 13 additions & 14 deletions src/app/user-list/user-list.component.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,33 @@
import { Component, OnInit } from '@angular/core';
import { User } from '../user/user';
import { UserListService } from './user-list.service';
import { WebStorageService } from '../services/web-storage.service';

import { WebStorageService } from '../services/web-storage.service';

@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.css']
selector: 'app-user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.css'],
})
export class UserListComponent implements OnInit {
public users: Promise<User[]> | null = null;

constructor(
private userListService: UserListService,
private webStorageService: WebStorageService
) { }
private webStorage: WebStorageService,
) {}

public async ngOnInit(): Promise<void> {
this.webStorageService.getRemote().subscribe(filtered => {
this.users = (filtered === null) ? this.userListService.getAll() : this.userListService.filter(filtered);
}, error => {
console.error('ngOnInit Error', error);
});
const filtered = this.webStorage.get('USERS');
this.users =
filtered === null
? await this.userListService.getAll()
: JSON.parse(filtered);
}

public async update(text: string): Promise<void> {
this.webStorageService.setRemote(text).subscribe(filtered => {
this.users = (filtered === null) ? this.userListService.getAll() : this.userListService.filter(filtered);
});
this.users = await this.userListService.filter(text);
this.webStorage.set('USERS', JSON.stringify(this.users));
}

}
2 changes: 1 addition & 1 deletion src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ body {

.highlight-text {
background: yellow;
}
}