feat: angular component
This commit is contained in:
parent
71571f8e3c
commit
423cd21da6
37 changed files with 6712 additions and 57 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -37,3 +37,5 @@ testem.log
|
|||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
.angular
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# Add files here to ignore them from prettier formatting
|
||||
/dist
|
||||
/coverage
|
||||
/coverage
|
||||
.angular
|
||||
|
|
|
|||
72
README.md
72
README.md
|
|
@ -1,57 +1,55 @@
|
|||
# Payspace
|
||||
|
||||
<a alt="Nx logo" href="https://nx.dev" target="_blank" rel="noreferrer"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45"></a>
|
||||
|
||||
✨ **This workspace has been generated by [Nx, a Smart, fast and extensible build system.](https://nx.dev)** ✨
|
||||
|
||||
## Generate code
|
||||
|
||||
If you happen to use Nx plugins, you can leverage code generators that might come with it.
|
||||
|
||||
Run `nx list` to get a list of available plugins and whether they have generators. Then run `nx list <plugin-name>` to see what generators are available.
|
||||
|
||||
Learn more about [Nx generators on the docs](https://nx.dev/plugin-features/use-code-generators).
|
||||
Applications was built using nx monorepo -> https://nx.dev/
|
||||
|
||||
## Running tasks
|
||||
|
||||
To execute tasks with Nx use the following syntax:
|
||||
To run Part 1: HTML/CSS use the following syntax:
|
||||
|
||||
```
|
||||
nx <target> <project> <...options>
|
||||
```
|
||||
`
|
||||
npm run part1
|
||||
`
|
||||
|
||||
You can also run multiple targets:
|
||||
To run Part 2: JavaScript use the following syntax:
|
||||
|
||||
```
|
||||
nx run-many -t <target1> <target2>
|
||||
```
|
||||
`
|
||||
npm run part2
|
||||
`
|
||||
|
||||
..or add `-p` to filter specific projects
|
||||
To run Part 3: Angular Component use the following syntax:
|
||||
|
||||
```
|
||||
nx run-many -t <target1> <target2> -p <proj1> <proj2>
|
||||
```
|
||||
`
|
||||
npm run part3
|
||||
`
|
||||
|
||||
To run Part 4: Problem-Solving use the following syntax:
|
||||
|
||||
`
|
||||
npm run part4
|
||||
`
|
||||
|
||||
To run Part 5: Code Review use the following syntax:
|
||||
|
||||
`
|
||||
npm run part5
|
||||
`
|
||||
|
||||
Targets can be defined in the `package.json` or `projects.json`. Learn more [in the docs](https://nx.dev/core-features/run-tasks).
|
||||
|
||||
## Want better Editor Integration?
|
||||
|
||||
Have a look at the [Nx Console extensions](https://nx.dev/nx-console). It provides autocomplete support, a UI for exploring and running tasks & generators, and more! Available for VSCode, IntelliJ and comes with a LSP for Vim users.
|
||||
|
||||
## Ready to deploy?
|
||||
|
||||
Just run `nx build demoapp` to build the application. The build artifacts will be stored in the `dist/` directory, ready to be deployed.
|
||||
`npm run part1:build`
|
||||
|
||||
## Set up CI!
|
||||
`npm run part2:build`
|
||||
|
||||
Nx comes with local caching already built-in (check your `nx.json`). On CI you might want to go a step further.
|
||||
`npm run part3:build`
|
||||
|
||||
- [Set up remote caching](https://nx.dev/core-features/share-your-cache)
|
||||
- [Set up task distribution across multiple machines](https://nx.dev/core-features/distribute-task-execution)
|
||||
- [Learn more how to setup CI](https://nx.dev/recipes/ci)
|
||||
`npm run part4:build`
|
||||
|
||||
## Connect with us!
|
||||
`npm run part5:build`
|
||||
|
||||
- [Join the community](https://nx.dev/community)
|
||||
- [Subscribe to the Nx Youtube Channel](https://www.youtube.com/@nxdevtools)
|
||||
- [Follow us on Twitter](https://twitter.com/nxdevtools)
|
||||
The build artifacts will be stored in the `dist/` directory, ready to be deployed.
|
||||
|
||||
## Author
|
||||
|
||||
Brian Pooe
|
||||
|
|
|
|||
13
nx.json
13
nx.json
|
|
@ -62,6 +62,19 @@
|
|||
"linter": "eslint",
|
||||
"unitTestRunner": "jest",
|
||||
"e2eTestRunner": "none"
|
||||
},
|
||||
"@nx/angular:application": {
|
||||
"style": "scss",
|
||||
"linter": "eslint",
|
||||
"unitTestRunner": "jest",
|
||||
"e2eTestRunner": "none"
|
||||
},
|
||||
"@nx/angular:library": {
|
||||
"linter": "eslint",
|
||||
"unitTestRunner": "jest"
|
||||
},
|
||||
"@nx/angular:component": {
|
||||
"style": "scss"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6081
package-lock.json
generated
6081
package-lock.json
generated
File diff suppressed because it is too large
Load diff
36
package.json
36
package.json
|
|
@ -5,12 +5,28 @@
|
|||
"scripts": {
|
||||
"part1": "nx run part-1-html-css:serve",
|
||||
"part2": "nx run part-2-javascript:serve",
|
||||
"part3": "nx run part-3-angular:serve",
|
||||
"part4": "nx run part-4-problem-solving:serve",
|
||||
"part5": "nx run part-5-code-review:serve"
|
||||
"part5": "nx run part-5-code-review:serve",
|
||||
"part1:build": "nx build part-1-html-css",
|
||||
"part2:build": "nx build part-2-javascript",
|
||||
"part3:build": "nx build part-3-angular",
|
||||
"part4:build": "nx build part-4-problem-solving",
|
||||
"part5:build": "nx build part-5-code-review"
|
||||
},
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~16.2.0",
|
||||
"@angular-devkit/core": "~16.2.0",
|
||||
"@angular-devkit/schematics": "~16.2.0",
|
||||
"@angular-eslint/eslint-plugin": "~16.0.0",
|
||||
"@angular-eslint/eslint-plugin-template": "~16.0.0",
|
||||
"@angular-eslint/template-parser": "~16.0.0",
|
||||
"@angular/cli": "~16.2.0",
|
||||
"@angular/compiler-cli": "~16.2.0",
|
||||
"@angular/language-service": "~16.2.0",
|
||||
"@nrwl/web": "^16.10.0",
|
||||
"@nx/angular": "^16.10.0",
|
||||
"@nx/eslint-plugin": "16.10.0",
|
||||
"@nx/jest": "16.10.0",
|
||||
"@nx/js": "16.10.0",
|
||||
|
|
@ -18,6 +34,7 @@
|
|||
"@nx/web": "16.10.0",
|
||||
"@nx/webpack": "^16.10.0",
|
||||
"@nx/workspace": "16.10.0",
|
||||
"@schematics/angular": "~16.2.0",
|
||||
"@swc/cli": "~0.1.62",
|
||||
"@swc/core": "~1.3.85",
|
||||
"@swc/jest": "0.2.20",
|
||||
|
|
@ -25,20 +42,35 @@
|
|||
"@types/node": "18.7.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
||||
"@typescript-eslint/parser": "^5.60.1",
|
||||
"autoprefixer": "^10.4.0",
|
||||
"eslint": "~8.46.0",
|
||||
"eslint-config-prettier": "8.1.0",
|
||||
"jest": "^29.4.1",
|
||||
"jest-environment-jsdom": "^29.4.1",
|
||||
"jest-preset-angular": "~13.1.0",
|
||||
"jsonc-eslint-parser": "^2.1.0",
|
||||
"nx": "16.10.0",
|
||||
"postcss": "^8.4.5",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-tailwindcss": "^0.5.5",
|
||||
"swc-loader": "0.1.15",
|
||||
"tailwindcss": "^3.0.2",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-node": "10.9.1",
|
||||
"typescript": "~5.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "~16.2.0",
|
||||
"@angular/common": "~16.2.0",
|
||||
"@angular/compiler": "~16.2.0",
|
||||
"@angular/core": "~16.2.0",
|
||||
"@angular/forms": "~16.2.0",
|
||||
"@angular/platform-browser": "~16.2.0",
|
||||
"@angular/platform-browser-dynamic": "~16.2.0",
|
||||
"@angular/router": "~16.2.0",
|
||||
"@swc/helpers": "~0.5.2",
|
||||
"tslib": "^2.3.0"
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.13.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,29 +5,29 @@ import { filterBooks } from "./book.util";
|
|||
export class AppElement extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.innerHTML = `
|
||||
<div class="container mx-auto sm:px-6 lg:px-8">
|
||||
<div class="container pt-4 mx-auto sm:px-6 lg:px-8">
|
||||
<!-- Search -->
|
||||
<label for="search" class="block text-sm font-medium leading-6 text-gray-900">Quick search</label>
|
||||
<div class="relative mt-2 flex items-center">
|
||||
<input type="text" name="search" id="search" class="block w-full rounded-md border-0 py-1.5 pr-14 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" data-search>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-sm font-medium text-gray-500 py-6">Books</h2>
|
||||
<ul role="list" class="mt-3 grid grid-cols-1 gap-5 sm:grid-cols-2 sm:gap-6 lg:grid-cols-4" data-book-cards-container></ul>
|
||||
<h2 class="text-sm font-medium text-gray-500 py-6">Books</h2>
|
||||
<ul role="list" class="mt-3 grid grid-cols-1 gap-5 sm:grid-cols-2 sm:gap-6 lg:grid-cols-4" data-book-cards-container></ul>
|
||||
|
||||
<template data-book-card-template>
|
||||
<li class="col-span-1 flex rounded-md shadow-sm">
|
||||
<div class="flex w-16 flex-shrink-0 items-center justify-center bg-[#9BD83A] rounded-l-md text-sm font-medium text-gray-900 hover:text-gray-600" data-initials></div>
|
||||
<div class="flex flex-1 items-center justify-between truncate rounded-r-md border-b border-r border-t border-gray-200 bg-white hover:text-gray-600">
|
||||
<div class="flex-1 truncate px-4 py-2 text-sm">
|
||||
<a href="#" class="font-medium text-gray-900 hover:text-gray-600" data-title></a><br>
|
||||
<a href="#" class="font-medium text-gray-900 hover:text-gray-600" data-author></a>
|
||||
<p class="text-gray-500" data-year></p>
|
||||
<template data-book-card-template>
|
||||
<li class="col-span-1 flex rounded-md shadow-sm">
|
||||
<div class="flex w-16 flex-shrink-0 items-center justify-center bg-[#9BD83A] rounded-l-md text-sm font-medium text-gray-900 hover:text-gray-600" data-initials></div>
|
||||
<div class="flex flex-1 items-center justify-between truncate rounded-r-md border-b border-r border-t border-gray-200 bg-white hover:text-gray-600">
|
||||
<div class="flex-1 truncate px-4 py-2 text-sm">
|
||||
<a href="#" class="font-medium text-gray-900 hover:text-gray-600" data-title></a><br>
|
||||
<a href="#" class="font-medium text-gray-900 hover:text-gray-600" data-author></a>
|
||||
<p class="text-gray-500" data-year></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
</li>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
46
part-3-angular/.eslintrc.json
Normal file
46
part-3-angular/.eslintrc.json
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"extends": [
|
||||
"../.eslintrc.json"
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"!**/*"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.ts"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:@nx/angular",
|
||||
"plugin:@angular-eslint/template/process-inline-templates"
|
||||
],
|
||||
"rules": {
|
||||
"@angular-eslint/directive-selector": [
|
||||
"error",
|
||||
{
|
||||
"type": "attribute",
|
||||
"prefix": "payspace",
|
||||
"style": "camelCase"
|
||||
}
|
||||
],
|
||||
"@angular-eslint/component-selector": [
|
||||
"error",
|
||||
{
|
||||
"type": "element",
|
||||
"prefix": "payspace",
|
||||
"style": "kebab-case"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.html"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:@nx/angular-template"
|
||||
],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
22
part-3-angular/jest.config.ts
Normal file
22
part-3-angular/jest.config.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/* eslint-disable */
|
||||
export default {
|
||||
displayName: 'part-3-angular',
|
||||
preset: '../jest.preset.js',
|
||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||
coverageDirectory: '../coverage/part-3-angular',
|
||||
transform: {
|
||||
'^.+\\.(ts|mjs|js|html)$': [
|
||||
'jest-preset-angular',
|
||||
{
|
||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
},
|
||||
],
|
||||
},
|
||||
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
||||
snapshotSerializers: [
|
||||
'jest-preset-angular/build/serializers/no-ng-attributes',
|
||||
'jest-preset-angular/build/serializers/ng-snapshot',
|
||||
'jest-preset-angular/build/serializers/html-comment',
|
||||
]
|
||||
};
|
||||
104
part-3-angular/project.json
Normal file
104
part-3-angular/project.json
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"name": "part-3-angular",
|
||||
"$schema": "../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"prefix": "payspace",
|
||||
"sourceRoot": "part-3-angular/src",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@angular-devkit/build-angular:browser",
|
||||
"outputs": [
|
||||
"{options.outputPath}"
|
||||
],
|
||||
"options": {
|
||||
"outputPath": "dist/part-3-angular",
|
||||
"index": "part-3-angular/src/index.html",
|
||||
"main": "part-3-angular/src/main.ts",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"tsConfig": "part-3-angular/tsconfig.app.json",
|
||||
"assets": [
|
||||
"part-3-angular/src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"part-3-angular/src/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "1mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"executor": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "part-3-angular:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "part-3-angular:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"executor": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "part-3-angular:build"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"outputs": [
|
||||
"{options.outputFile}"
|
||||
],
|
||||
"options": {
|
||||
"lintFilePatterns": [
|
||||
"part-3-angular/**/*.ts",
|
||||
"part-3-angular/**/*.html"
|
||||
]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": [
|
||||
"{workspaceRoot}/coverage/{projectRoot}"
|
||||
],
|
||||
"options": {
|
||||
"jestConfig": "part-3-angular/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
part-3-angular/src/app/app.component.html
Normal file
7
part-3-angular/src/app/app.component.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<div class="container pt-4 mx-auto sm:px-6 lg:px-8">
|
||||
<payspace-search (searchText)="searchText($event)"></payspace-search>
|
||||
<h2 class="text-sm font-medium text-gray-500 py-6">Books</h2>
|
||||
<ul role="list" class="mt-3 grid grid-cols-1 gap-5 sm:grid-cols-2 sm:gap-6 lg:grid-cols-4">
|
||||
<payspace-book *ngFor="let book of books$ | async" [book]="book" />
|
||||
</ul>
|
||||
</div>
|
||||
0
part-3-angular/src/app/app.component.scss
Normal file
0
part-3-angular/src/app/app.component.scss
Normal file
24
part-3-angular/src/app/app.component.spec.ts
Normal file
24
part-3-angular/src/app/app.component.spec.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AppComponent, NxWelcomeComponent],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('h1')?.textContent).toContain('Welcome part-3-angular');
|
||||
});
|
||||
|
||||
it(`should have as title 'part-3-angular'`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('part-3-angular');
|
||||
});
|
||||
});
|
||||
29
part-3-angular/src/app/app.component.ts
Normal file
29
part-3-angular/src/app/app.component.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { SearchComponent } from "./ui/search/search.component";
|
||||
import { BookComponent } from "./ui/book/book.component";
|
||||
import { ApiService } from "./services/api.service";
|
||||
import { Observable } from "rxjs";
|
||||
import { Book } from "./models";
|
||||
import { CommonModule } from "@angular/common";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [SearchComponent, BookComponent, CommonModule],
|
||||
selector: "payspace-root",
|
||||
templateUrl: "./app.component.html",
|
||||
styleUrls: ["./app.component.scss"]
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
books$: Observable<Book[] | null> = this.apiService.books$;
|
||||
|
||||
constructor(private readonly apiService: ApiService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.apiService.loadBooks();
|
||||
}
|
||||
|
||||
searchText(searchTerm: string = ""): void {
|
||||
this.apiService.loadBooks(searchTerm, false);
|
||||
}
|
||||
}
|
||||
6
part-3-angular/src/app/app.config.ts
Normal file
6
part-3-angular/src/app/app.config.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { ApplicationConfig } from "@angular/core";
|
||||
import { provideHttpClient } from "@angular/common/http";
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [provideHttpClient()]
|
||||
};
|
||||
6
part-3-angular/src/app/models/book.interface.ts
Normal file
6
part-3-angular/src/app/models/book.interface.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export interface Book {
|
||||
author: string;
|
||||
initials: string;
|
||||
title: string;
|
||||
year: number;
|
||||
}
|
||||
1
part-3-angular/src/app/models/index.ts
Normal file
1
part-3-angular/src/app/models/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./book.interface";
|
||||
43
part-3-angular/src/app/services/api.service.ts
Normal file
43
part-3-angular/src/app/services/api.service.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { Injectable } from "@angular/core";
|
||||
import { HttpClient, HttpErrorResponse } from "@angular/common/http";
|
||||
import { Book } from "../models";
|
||||
import { BehaviorSubject, catchError, Observable, take, throwError } from "rxjs";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root"
|
||||
})
|
||||
export class ApiService {
|
||||
private _books: BehaviorSubject<Book[]> = new BehaviorSubject<Book[]>([]);
|
||||
books$: Observable<Book[]> = this._books.asObservable();
|
||||
|
||||
private books: Book[] = [];
|
||||
|
||||
getAllBooks$ = this.httpClient.get<Book[]>("/assets/books.json")
|
||||
.pipe(
|
||||
take(1),
|
||||
catchError((error: HttpErrorResponse) => {
|
||||
console.error("Something went wrong:", error);
|
||||
return throwError(() => new Error("Something went wrong."));
|
||||
})
|
||||
);
|
||||
|
||||
constructor(private readonly httpClient: HttpClient) {
|
||||
}
|
||||
|
||||
loadBooks = (searchTerm: string = "", preload = true): void => {
|
||||
if (preload) {
|
||||
this.getAllBooks$.subscribe((books: Book[]) => {
|
||||
this._books.next(books);
|
||||
this.books = books;
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const filteredBooks: Book[] = this.books.filter((book: Book) =>
|
||||
book.author.toLowerCase().includes(searchTerm) ||
|
||||
book.title.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
this._books.next(filteredBooks);
|
||||
};
|
||||
}
|
||||
14
part-3-angular/src/app/ui/book/book.component.html
Normal file
14
part-3-angular/src/app/ui/book/book.component.html
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<li class="col-span-1 flex rounded-md shadow-sm">
|
||||
<div
|
||||
class="flex w-16 flex-shrink-0 items-center justify-center bg-[#9BD83A] rounded-l-md text-sm font-medium text-gray-900 hover:text-gray-600">
|
||||
{{book?.initials}}
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-1 items-center justify-between truncate rounded-r-md border-b border-r border-t border-gray-200 bg-white hover:text-gray-600">
|
||||
<div class="flex-1 truncate px-4 py-2 text-sm">
|
||||
<a href="#" class="font-medium text-gray-900 hover:text-gray-600">{{book?.title}}</a><br>
|
||||
<a href="#" class="font-medium text-gray-900 hover:text-gray-600">{{book?.author}}</a>
|
||||
<p class="text-gray-500">{{book?.year}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
1
part-3-angular/src/app/ui/book/book.component.scss
Normal file
1
part-3-angular/src/app/ui/book/book.component.scss
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
22
part-3-angular/src/app/ui/book/book.component.spec.ts
Normal file
22
part-3-angular/src/app/ui/book/book.component.spec.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { BookComponent } from './book.component';
|
||||
|
||||
describe('BookComponent', () => {
|
||||
let component: BookComponent;
|
||||
let fixture: ComponentFixture<BookComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ BookComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(BookComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
14
part-3-angular/src/app/ui/book/book.component.ts
Normal file
14
part-3-angular/src/app/ui/book/book.component.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { Component, Input } from "@angular/core";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Book } from "../../models";
|
||||
|
||||
@Component({
|
||||
selector: "payspace-book",
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: "./book.component.html",
|
||||
styleUrls: ["./book.component.scss"]
|
||||
})
|
||||
export class BookComponent {
|
||||
@Input() book: Book | null | undefined;
|
||||
}
|
||||
5
part-3-angular/src/app/ui/search/search.component.html
Normal file
5
part-3-angular/src/app/ui/search/search.component.html
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<label for="search" class="block text-sm font-medium leading-6 text-gray-900">Quick search</label>
|
||||
<div class="relative mt-2 flex items-center">
|
||||
<input [formControl]="search" type="text" name="search" id="search"
|
||||
class="block w-full rounded-md border-0 py-1.5 pr-14 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6">
|
||||
</div>
|
||||
1
part-3-angular/src/app/ui/search/search.component.scss
Normal file
1
part-3-angular/src/app/ui/search/search.component.scss
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
22
part-3-angular/src/app/ui/search/search.component.spec.ts
Normal file
22
part-3-angular/src/app/ui/search/search.component.spec.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SearchComponent } from './search.component';
|
||||
|
||||
describe('SearchComponent', () => {
|
||||
let component: SearchComponent;
|
||||
let fixture: ComponentFixture<SearchComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ SearchComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SearchComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
20
part-3-angular/src/app/ui/search/search.component.ts
Normal file
20
part-3-angular/src/app/ui/search/search.component.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { Component, Output } from "@angular/core";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { FormControl, ReactiveFormsModule } from "@angular/forms";
|
||||
import { distinctUntilChanged, filter } from "rxjs";
|
||||
|
||||
@Component({
|
||||
selector: "payspace-search",
|
||||
standalone: true,
|
||||
imports: [CommonModule, ReactiveFormsModule],
|
||||
templateUrl: "./search.component.html",
|
||||
styleUrls: ["./search.component.scss"]
|
||||
})
|
||||
export class SearchComponent {
|
||||
search = new FormControl("");
|
||||
|
||||
@Output() searchText = this.search.valueChanges.pipe(
|
||||
filter(Boolean),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
}
|
||||
0
part-3-angular/src/assets/.gitkeep
Normal file
0
part-3-angular/src/assets/.gitkeep
Normal file
26
part-3-angular/src/assets/books.json
Normal file
26
part-3-angular/src/assets/books.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
[
|
||||
{
|
||||
"author": "F. Scott Fitzgerald",
|
||||
"initials": "FS",
|
||||
"title": "The Great Gatsby",
|
||||
"year": 1925
|
||||
},
|
||||
{
|
||||
"author": "Harper Lee",
|
||||
"initials": "HL",
|
||||
"title": "To Kill a Mockingbird",
|
||||
"year": 1960
|
||||
},
|
||||
{
|
||||
"author": "Stephenie Meyer",
|
||||
"initials": "SM",
|
||||
"title": "Twilight",
|
||||
"year": 2005
|
||||
},
|
||||
{
|
||||
"author": "Stephen King",
|
||||
"initials": "S",
|
||||
"title": "If It Bleeds",
|
||||
"year": 2020
|
||||
}
|
||||
]
|
||||
17
part-3-angular/src/index.html
Normal file
17
part-3-angular/src/index.html
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>part-3-angular</title>
|
||||
<base href="/" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="https://www.payspace.com/wp-content/uploads/2018/03/PS-FAVICON-NEW-2017.png" sizes="32x32" />
|
||||
<link rel="icon" href="https://www.payspace.com/wp-content/uploads/2018/03/PS-FAVICON-NEW-2017.png" sizes="192x192" />
|
||||
<link rel="apple-touch-icon" href="https://www.payspace.com/wp-content/uploads/2018/03/PS-FAVICON-NEW-2017.png" />
|
||||
<meta name="msapplication-TileImage"
|
||||
content="https://www.payspace.com/wp-content/uploads/2018/03/PS-FAVICON-NEW-2017.png" />
|
||||
</head>
|
||||
<body class="h-full">
|
||||
<payspace-root></payspace-root>
|
||||
</body>
|
||||
</html>
|
||||
5
part-3-angular/src/main.ts
Normal file
5
part-3-angular/src/main.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { appConfig } from './app/app.config';
|
||||
import { AppComponent } from './app/app.component';
|
||||
|
||||
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));
|
||||
5
part-3-angular/src/styles.scss
Normal file
5
part-3-angular/src/styles.scss
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
8
part-3-angular/src/test-setup.ts
Normal file
8
part-3-angular/src/test-setup.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// @ts-expect-error https://thymikee.github.io/jest-preset-angular/docs/getting-started/test-environment
|
||||
globalThis.ngJest = {
|
||||
testEnvironmentOptions: {
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
},
|
||||
};
|
||||
import 'jest-preset-angular/setup-jest';
|
||||
14
part-3-angular/tailwind.config.js
Normal file
14
part-3-angular/tailwind.config.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
|
||||
const { join } = require('path');
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
|
||||
...createGlobPatternsForDependencies(__dirname),
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
10
part-3-angular/tsconfig.app.json
Normal file
10
part-3-angular/tsconfig.app.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../dist/out-tsc",
|
||||
"types": []
|
||||
},
|
||||
"files": ["src/main.ts"],
|
||||
"include": ["src/**/*.d.ts"],
|
||||
"exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
|
||||
}
|
||||
12
part-3-angular/tsconfig.editor.json
Normal file
12
part-3-angular/tsconfig.editor.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
32
part-3-angular/tsconfig.json
Normal file
32
part-3-angular/tsconfig.json
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"useDefineForClassFields": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.editor.json"
|
||||
}
|
||||
],
|
||||
"extends": "../tsconfig.base.json",
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
16
part-3-angular/tsconfig.spec.json
Normal file
16
part-3-angular/tsconfig.spec.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"target": "es2016",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"files": ["src/test-setup.ts"],
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue