diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 0000000..4f9ac26 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,16 @@ +# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries + +# For the full list of supported browsers by the Angular framework, please see: +# https://angular.io/guide/browser-support + +# You can see what browsers were selected by your queries by running: +# npx browserslist + +last 1 Chrome version +last 1 Firefox version +last 2 Edge major versions +last 2 Safari major versions +last 2 iOS major versions +Firefox ESR diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..59d9a3a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1624286 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out +nng +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings + +# System files +.DS_Store +Thumbs.db diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..77b3745 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 + "recommendations": ["angular.ng-template"] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..740e35a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "ng serve", + "type": "pwa-chrome", + "request": "launch", + "preLaunchTask": "npm: start", + "url": "http://localhost:4200/" + }, + { + "name": "ng test", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: test", + "url": "http://localhost:9876/debug.html" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..a298b5b --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "start", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + }, + { + "type": "npm", + "script": "test", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + } + ] +} diff --git a/README.md b/README.md index 3eaa156..2fb9bed 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,27 @@ -# at-certification-stock +# AtCertificationStock -[Edit on StackBlitz ⚡️](https://stackblitz.com/edit/angular-ivy-2c4mp6) \ No newline at end of file +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.2.8. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. diff --git a/angular.json b/angular.json index 7a03df9..648098f 100644 --- a/angular.json +++ b/angular.json @@ -3,65 +3,80 @@ "version": 1, "newProjectRoot": "projects", "projects": { - "demo": { + "at-certification-stock": { + "projectType": "application", + "schematics": {}, "root": "", "sourceRoot": "src", - "projectType": "application", "prefix": "app", - "schematics": {}, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { - "outputPath": "dist/demo", + "outputPath": "dist/at-certification-stock", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", - "tsConfig": "src/tsconfig.app.json", + "tsConfig": "tsconfig.app.json", "assets": [ "src/favicon.ico", "src/assets" ], "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.css" ], "scripts": [] }, "configurations": { "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" } ], - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "extractCss": true, - "namedChunks": false, - "aot": true, - "extractLicenses": true, - "vendorChunk": false, - "buildOptimizer": true + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true } - } + }, + "defaultConfiguration": "production" }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", - "options": { - "browserTarget": "demo:build" - }, "configurations": { "production": { - "browserTarget": "demo:build:production" + "browserTarget": "at-certification-stock:build:production" + }, + "development": { + "browserTarget": "at-certification-stock:build:development" } - } + }, + "defaultConfiguration": "development" }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { - "browserTarget": "demo:build" + "browserTarget": "at-certification-stock:build" } }, "test": { @@ -69,32 +84,20 @@ "options": { "main": "src/test.ts", "polyfills": "src/polyfills.ts", - "tsConfig": "src/tsconfig.spec.json", - "karmaConfig": "src/karma.conf.js", - "styles": [ - "styles.css" - ], - "scripts": [], + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", "assets": [ "src/favicon.ico", "src/assets" - ] - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "src/tsconfig.app.json", - "src/tsconfig.spec.json" ], - "exclude": [ - "**/node_modules/**" - ] + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.css" + ], + "scripts": [] } } } } - }, - "defaultProject": "demo" -} \ No newline at end of file + } +} diff --git a/doc/Angular Level 2 certification mini-project - Stock tracker.pdf b/doc/Angular Level 2 certification mini-project - Stock tracker.pdf new file mode 100644 index 0000000..701c48f Binary files /dev/null and b/doc/Angular Level 2 certification mini-project - Stock tracker.pdf differ diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..d4bb949 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,44 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + jasmine: { + // you can add configuration options for Jasmine here + // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html + // for example, you can disable the random execution with `random: false` + // or set a specific seed with `seed: 4321` + }, + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + jasmineHtmlReporter: { + suppressAll: true // removes the duplicated traces + }, + coverageReporter: { + dir: require('path').join(__dirname, './coverage/at-certification-stock'), + subdir: '.', + reporters: [ + { type: 'html' }, + { type: 'text-summary' } + ] + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false, + restartOnFileChange: true + }); +}; diff --git a/package.json b/package.json index cd3e1f0..bbdbc45 100644 --- a/package.json +++ b/package.json @@ -1,39 +1,40 @@ { - "name": "angular", + "name": "at-certification-stock", "version": "0.0.0", - "private": true, "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", - "test": "ng test", - "lint": "ng lint", - "e2e": "ng e2e" + "watch": "ng build --watch --configuration development", + "test": "ng test" }, + "private": true, "dependencies": { - "@angular/animations": "^14.0.0", - "@angular/common": "^14.0.0", - "@angular/compiler": "^14.0.0", - "@angular/core": "^14.0.0", - "@angular/forms": "^14.0.0", - "@angular/platform-browser": "^14.0.0", - "@angular/platform-browser-dynamic": "^14.0.0", - "@angular/router": "^14.0.0", + "@angular/animations": "^14.2.0", + "@angular/cdk": "^14.2.6", + "@angular/common": "^14.2.0", + "@angular/compiler": "^14.2.0", + "@angular/core": "^14.2.0", + "@angular/forms": "^14.2.0", + "@angular/material": "^14.2.6", + "@angular/platform-browser": "^14.2.0", + "@angular/platform-browser-dynamic": "^14.2.0", + "@angular/router": "^14.2.0", "rxjs": "~7.5.0", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, "devDependencies": { - "@angular-devkit/build-angular": "^14.0.0", - "@angular/cli": "~14.0.0", - "@angular/compiler-cli": "^14.0.0", + "@angular-devkit/build-angular": "^14.2.8", + "@angular/cli": "~14.2.8", + "@angular/compiler-cli": "^14.2.0", "@types/jasmine": "~4.0.0", - "jasmine-core": "~4.1.0", - "karma": "~6.3.0", + "jasmine-core": "~4.3.0", + "karma": "~6.4.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", - "karma-jasmine": "~5.0.0", - "karma-jasmine-html-reporter": "~1.7.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.0.0", "typescript": "~4.7.2" } } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts new file mode 100644 index 0000000..14066e1 --- /dev/null +++ b/src/app/app-routing.module.ts @@ -0,0 +1,17 @@ +import {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; +import {SentimentComponent} from "./pages/sentiment/sentiment.component"; +import {LandingPageComponent} from "./pages/landing-page/landing-page.component"; + +const routes: Routes = [ + + {path: 'sentiment/:symbol', component: SentimentComponent}, + {path: '**', component: LandingPageComponent} +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule { +} diff --git a/src/app/app.component.css b/src/app/app.component.css index b7ef084..e69de29 100644 --- a/src/app/app.component.css +++ b/src/app/app.component.css @@ -1,3 +0,0 @@ -p { - font-family: Lato; -} \ No newline at end of file diff --git a/src/app/app.component.html b/src/app/app.component.html index 76ba315..0680b43 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,4 +1 @@ - -

- Start editing to see some magic happen :) -

\ No newline at end of file + diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts new file mode 100644 index 0000000..94e6372 --- /dev/null +++ b/src/app/app.component.spec.ts @@ -0,0 +1,35 @@ +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + RouterTestingModule + ], + declarations: [ + AppComponent + ], + }).compileComponents(); + }); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'at-certification-stock'`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app.title).toEqual('at-certification-stock'); + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.nativeElement as HTMLElement; + expect(compiled.querySelector('.content span')?.textContent).toContain('at-certification-stock app is running!'); + }); +}); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index f9ed12b..a424f85 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,10 +1,10 @@ -import { Component, VERSION } from '@angular/core'; +import { Component } from '@angular/core'; @Component({ - selector: 'my-app', + selector: 'app-root', templateUrl: './app.component.html', - styleUrls: [ './app.component.css' ] + styleUrls: ['./app.component.css'] }) -export class AppComponent { - name = 'Angular ' + VERSION.major; +export class AppComponent { + title = 'at-certification-stock'; } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8342b65..bf87211 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,13 +1,48 @@ -import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { FormsModule } from '@angular/forms'; +import {NgModule} from '@angular/core'; +import {BrowserModule} from '@angular/platform-browser'; -import { AppComponent } from './app.component'; -import { HelloComponent } from './hello.component'; +import {AppRoutingModule} from './app-routing.module'; +import {AppComponent} from './app.component'; +import {HttpClientModule} from "@angular/common/http"; +import {SentimentComponent} from './pages/sentiment/sentiment.component'; +import {LandingPageComponent} from './pages/landing-page/landing-page.component'; +import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import {MatCardModule} from "@angular/material/card"; +import {MatFormFieldModule} from "@angular/material/form-field"; +import {MatButtonModule} from "@angular/material/button"; +import {MatInputModule} from "@angular/material/input"; +import {MatDividerModule} from "@angular/material/divider"; +import {FormsModule} from "@angular/forms"; +import {MatProgressSpinnerModule} from "@angular/material/progress-spinner"; +import {MatSnackBarModule} from "@angular/material/snack-bar"; +import {MatGridListModule} from "@angular/material/grid-list"; +import { StockCardComponent } from './components/stock-card/stock-card.component'; +import {MatIconModule} from "@angular/material/icon"; @NgModule({ - imports: [ BrowserModule, FormsModule ], - declarations: [ AppComponent, HelloComponent ], - bootstrap: [ AppComponent ] + declarations: [ + AppComponent, + SentimentComponent, + LandingPageComponent, + StockCardComponent + ], + imports: [ + FormsModule, + BrowserModule, + AppRoutingModule, + HttpClientModule, + BrowserAnimationsModule, + MatCardModule, + MatFormFieldModule, + MatButtonModule, + MatInputModule, + MatDividerModule, + MatSnackBarModule, + MatGridListModule, + MatIconModule + ], + providers: [], + bootstrap: [AppComponent] }) -export class AppModule { } +export class AppModule { +} diff --git a/src/app/components/stock-card/stock-card.component.css b/src/app/components/stock-card/stock-card.component.css new file mode 100644 index 0000000..f3cd274 --- /dev/null +++ b/src/app/components/stock-card/stock-card.component.css @@ -0,0 +1,16 @@ +.mat-card { + margin: 1rem; +} + +.mat-card-title .mat-icon { + cursor: pointer; + float: right; +} + +.mat-grid-tile .mat-icon { + font-size: 4rem; + height: 4rem; + width: 4rem; +} + + diff --git a/src/app/components/stock-card/stock-card.component.html b/src/app/components/stock-card/stock-card.component.html new file mode 100644 index 0000000..2c9c8fa --- /dev/null +++ b/src/app/components/stock-card/stock-card.component.html @@ -0,0 +1,27 @@ + + {{company.description}} ({{company.symbol}}) + + + + + + + + + + + + + +

Change today: {{quote.dp / 100 | percent: '1.1-1'}}

+

Opening price: {{quote.o | currency: 'USD'}}

+

Current price: {{quote.c | currency: 'USD'}}

+

High price: {{quote.h | currency: 'USD'}}

+
+
+
+ + + + +
diff --git a/src/app/components/stock-card/stock-card.component.spec.ts b/src/app/components/stock-card/stock-card.component.spec.ts new file mode 100644 index 0000000..4d38b55 --- /dev/null +++ b/src/app/components/stock-card/stock-card.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StockCardComponent } from './stock-card.component'; + +describe('StockCardComponent', () => { + let component: StockCardComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ StockCardComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(StockCardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/stock-card/stock-card.component.ts b/src/app/components/stock-card/stock-card.component.ts new file mode 100644 index 0000000..de2b376 --- /dev/null +++ b/src/app/components/stock-card/stock-card.component.ts @@ -0,0 +1,40 @@ +import {Component, Input, OnDestroy, OnInit} from '@angular/core'; +import {FinnhubService} from "../../services/finnhub.service"; +import {Company, QuoteResponse} from "../../model/company-data"; +import {Subject, Subscription} from "rxjs"; +import {StorageService} from "../../services/storage.service"; + +@Component({ + selector: 'app-stock-card', + templateUrl: './stock-card.component.html', + styleUrls: ['./stock-card.component.css'] +}) +export class StockCardComponent implements OnInit, OnDestroy { + + @Input() + company = {} as Company; + + private _quote$ = new Subject(); + private subs: Subscription[] = []; + + constructor(public finnhubService: FinnhubService, + public storageService: StorageService) { + } + + ngOnInit(): void { + this.subs.push(this.finnhubService.getQuote(this.company.symbol).subscribe(value => this._quote$.next(value))); + } + + getQuote(): Subject { + return this._quote$; + } + + ngOnDestroy(): void { + this.subs.forEach(s => s.unsubscribe()); + this._quote$.complete(); + } + + remove() { + this.storageService.remove(this.company.symbol); + } +} diff --git a/src/app/hello.component.ts b/src/app/hello.component.ts deleted file mode 100644 index bbc9aa9..0000000 --- a/src/app/hello.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component, Input } from '@angular/core'; - -@Component({ - selector: 'hello', - template: `

Hello {{name}}!

`, - styles: [`h1 { font-family: Lato; }`] -}) -export class HelloComponent { - @Input() name: string; -} diff --git a/src/app/model/company-data.ts b/src/app/model/company-data.ts new file mode 100644 index 0000000..37b7e19 --- /dev/null +++ b/src/app/model/company-data.ts @@ -0,0 +1,21 @@ +export interface Company { + description: string; + displaySymbol: string; + symbol: string; + type: string; +} + +export interface SearchResponse { + count: number; + result: Array; +} + +export interface QuoteResponse { + c: number; + d: number; + dp: number; + h: number; + l: number; + o: number; + pc: number; +} diff --git a/src/app/pages/landing-page/landing-page.component.css b/src/app/pages/landing-page/landing-page.component.css new file mode 100644 index 0000000..97e66a7 --- /dev/null +++ b/src/app/pages/landing-page/landing-page.component.css @@ -0,0 +1,7 @@ +.mat-card { + margin: 1rem; +} + +.mat-divider { + margin: 0 1rem; +} diff --git a/src/app/pages/landing-page/landing-page.component.html b/src/app/pages/landing-page/landing-page.component.html new file mode 100644 index 0000000..600b8a1 --- /dev/null +++ b/src/app/pages/landing-page/landing-page.component.html @@ -0,0 +1,26 @@ + + +

+ Enter the symbol of a stock to track (i.e. AAPL, TSLA, GOOGL) +

+ + + + + + +
+
+ + + + + + diff --git a/src/app/pages/landing-page/landing-page.component.spec.ts b/src/app/pages/landing-page/landing-page.component.spec.ts new file mode 100644 index 0000000..3f6e5a3 --- /dev/null +++ b/src/app/pages/landing-page/landing-page.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LandingPageComponent } from './landing-page.component'; + +describe('LandingPageComponent', () => { + let component: LandingPageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LandingPageComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(LandingPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/landing-page/landing-page.component.ts b/src/app/pages/landing-page/landing-page.component.ts new file mode 100644 index 0000000..09045b4 --- /dev/null +++ b/src/app/pages/landing-page/landing-page.component.ts @@ -0,0 +1,52 @@ +import {Component, OnInit} from '@angular/core'; +import {FinnhubService} from "../../services/finnhub.service"; +import {StorageService} from "../../services/storage.service"; +import {MatSnackBar} from "@angular/material/snack-bar"; + +@Component({ + selector: 'app-landing-page', + templateUrl: './landing-page.component.html', + styleUrls: ['./landing-page.component.css'] +}) +export class LandingPageComponent implements OnInit { + + symbol = ''; + disabled = false; + + constructor(public finnhubService: FinnhubService, + public storageService: StorageService, + private snackBar: MatSnackBar) { + } + + ngOnInit(): void { + + } + + + addSymbol() { + console.log('search for ' + this.symbol) + + // start loading... + this.disabled = true; + this.finnhubService.searchSymbol(this.symbol).subscribe(value => { + + if (value.count > 0) { + + let company = value.result.find(company => company.symbol == this.symbol); + if (company) { + this.storageService.add(company); + this.symbol = ''; + } else { + this.snackBar.open('No unique result', 'close', {duration: 10000}); + } + + this.disabled = false + } else { + this.snackBar.open('No result', 'close', {duration: 10000}); + this.disabled = false + } + }, (error) => { + this.snackBar.open('Communication error', 'close', {duration: 10000}); + }); + } +} diff --git a/src/app/pages/sentiment/sentiment.component.css b/src/app/pages/sentiment/sentiment.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/sentiment/sentiment.component.html b/src/app/pages/sentiment/sentiment.component.html new file mode 100644 index 0000000..c305baa --- /dev/null +++ b/src/app/pages/sentiment/sentiment.component.html @@ -0,0 +1 @@ +

sentiment works!

diff --git a/src/app/pages/sentiment/sentiment.component.spec.ts b/src/app/pages/sentiment/sentiment.component.spec.ts new file mode 100644 index 0000000..bf32a62 --- /dev/null +++ b/src/app/pages/sentiment/sentiment.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SentimentComponent } from './sentiment.component'; + +describe('SentimentComponent', () => { + let component: SentimentComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SentimentComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SentimentComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/sentiment/sentiment.component.ts b/src/app/pages/sentiment/sentiment.component.ts new file mode 100644 index 0000000..37b4d78 --- /dev/null +++ b/src/app/pages/sentiment/sentiment.component.ts @@ -0,0 +1,16 @@ +import { Component, OnInit } from '@angular/core'; +import {FinnhubService} from "../../services/finnhub.service"; + +@Component({ + selector: 'app-sentiment', + templateUrl: './sentiment.component.html', + styleUrls: ['./sentiment.component.css'] +}) +export class SentimentComponent implements OnInit { + + constructor(private finnhubService: FinnhubService) { } + + ngOnInit(): void { + } + +} diff --git a/src/app/services/finnhub.service.spec.ts b/src/app/services/finnhub.service.spec.ts new file mode 100644 index 0000000..d82e213 --- /dev/null +++ b/src/app/services/finnhub.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { FinnhubService } from './finnhub.service'; + +describe('FinnhubService', () => { + let service: FinnhubService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(FinnhubService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/finnhub.service.ts b/src/app/services/finnhub.service.ts new file mode 100644 index 0000000..7041218 --- /dev/null +++ b/src/app/services/finnhub.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core'; +import {HttpClient} from "@angular/common/http"; +import {Observable} from "rxjs"; +import {QuoteResponse, SearchResponse} from "../model/company-data"; + +@Injectable({ + providedIn: 'root' +}) +export class FinnhubService { + + private readonly token = { 'token': 'bu4f8kn48v6uehqi3cqg' }; + private readonly api = 'https://finnhub.io/api/v1'; + private readonly apiQuote = this.api + '/quote'; + private readonly apiSearch = this.api + '/search'; + + + + constructor(private httpClient: HttpClient) { } + + searchSymbol(q: string): Observable { + return this.httpClient.get(this.apiSearch, {params: { ...this.token, q }}); + } + + getQuote(symbol: string) { + return this.httpClient.get(this.apiQuote, {params: { ...this.token, symbol }}); + } +} + + diff --git a/src/app/services/storage.service.spec.ts b/src/app/services/storage.service.spec.ts new file mode 100644 index 0000000..e7fe5b5 --- /dev/null +++ b/src/app/services/storage.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { StorageService } from './storage.service'; + +describe('StorageService', () => { + let service: StorageService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(StorageService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/storage.service.ts b/src/app/services/storage.service.ts new file mode 100644 index 0000000..84c6ec9 --- /dev/null +++ b/src/app/services/storage.service.ts @@ -0,0 +1,41 @@ +import {Injectable, OnDestroy} from '@angular/core'; +import {BehaviorSubject} from "rxjs"; +import {Company} from "../model/company-data"; + +@Injectable({ + providedIn: 'root' +}) +export class StorageService implements OnDestroy { + + private storage: Company[] = []; + + private _storage$ = new BehaviorSubject([]); + + constructor() { + } + + add(company: Company): void { + this.storage.push(company); + this._storage$.next(this.storage) + } + + remove(symbol: string): void { + console.log('Remove ' + symbol) + + let index = this.storage.findIndex(company => company.symbol == symbol); + if (index > -1) { + this.storage.splice(index, 1); + } + + console.log(this.storage) + this._storage$.next(this.storage) + } + + get(): BehaviorSubject { + return this._storage$; + } + + ngOnDestroy(): void { + this._storage$.complete(); + } +} diff --git a/src/assets/.gitkeep b/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts new file mode 100644 index 0000000..3612073 --- /dev/null +++ b/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +}; diff --git a/src/environments/environment.ts b/src/environments/environment.ts new file mode 100644 index 0000000..f56ff47 --- /dev/null +++ b/src/environments/environment.ts @@ -0,0 +1,16 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/plugins/zone-error'; // Included with Angular CLI. diff --git a/src/favicon.ico b/src/favicon.ico new file mode 100644 index 0000000..997406a Binary files /dev/null and b/src/favicon.ico differ diff --git a/src/index.html b/src/index.html index f69aaa1..f9c7fef 100644 --- a/src/index.html +++ b/src/index.html @@ -1 +1,16 @@ -loading \ No newline at end of file + + + + + AtCertificationStock + + + + + + + + + + + diff --git a/src/main.ts b/src/main.ts index 955a7fb..c7b673c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,16 +1,12 @@ -import './polyfills'; - import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; -platformBrowserDynamic().bootstrapModule(AppModule).then(ref => { - // Ensure Angular destroys itself on hot reloads. - if (window['ngRef']) { - window['ngRef'].destroy(); - } - window['ngRef'] = ref; +if (environment.production) { + enableProdMode(); +} - // Otherwise, log the boot error -}).catch(err => console.error(err)); \ No newline at end of file +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/src/polyfills.ts b/src/polyfills.ts index 9c15394..429bb9e 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -8,64 +8,46 @@ * file. * * The current setup is for so-called "evergreen" browsers; the last versions of browsers that - * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), - * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * automatically update themselves. This includes recent versions of Safari, Chrome (including + * Opera), Edge on the desktop, and iOS and Chrome on mobile. * - * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + * Learn more in https://angular.io/guide/browser-support */ /*************************************************************************************************** * BROWSER POLYFILLS */ -/** IE9, IE10 and IE11 requires all of the following polyfills. **/ -// import 'core-js/es6/symbol'; -// import 'core-js/es6/object'; -// import 'core-js/es6/function'; -// import 'core-js/es6/parse-int'; -// import 'core-js/es6/parse-float'; -// import 'core-js/es6/number'; -// import 'core-js/es6/math'; -// import 'core-js/es6/string'; -// import 'core-js/es6/date'; -// import 'core-js/es6/array'; -// import 'core-js/es6/regexp'; -// import 'core-js/es6/map'; -// import 'core-js/es6/set'; - -/** IE10 and IE11 requires the following for NgClass support on SVG elements */ -// import 'classlist.js'; // Run `npm install --save classlist.js`. - -/** IE10 and IE11 requires the following to support `@angular/animation`. */ -// import 'web-animations-js'; // Run `npm install --save web-animations-js`. - - -/** Evergreen browsers require these. **/ -// import 'core-js/es6/reflect'; -// import 'core-js/es7/reflect'; - - /** - * Web Animations `@angular/platform-browser/animations` - * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. - * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * */ -// import 'web-animations-js'; // Run `npm install --save web-animations-js`. - - /*************************************************************************************************** - * Zone JS is required by Angular itself. + * Zone JS is required by default for Angular itself. */ -import 'zone.js/dist/zone'; // Included with Angular CLI. +import 'zone.js'; // Included with Angular CLI. /*************************************************************************************************** * APPLICATION IMPORTS */ - -/** - * Date, currency, decimal and percent pipes. - * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 - */ -// import 'intl'; // Run `npm install --save intl`. \ No newline at end of file diff --git a/src/styles.css b/src/styles.css index 74f71b0..7e7239a 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1 +1,4 @@ -/* Add application styles & imports to this file! */ \ No newline at end of file +/* You can add global styles to this file, and also import other style files */ + +html, body { height: 100%; } +body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000..c04c876 --- /dev/null +++ b/src/test.ts @@ -0,0 +1,26 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: { + context(path: string, deep?: boolean, filter?: RegExp): { + (id: string): T; + keys(): string[]; + }; +}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting(), +); + +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().forEach(context); diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..82d91dc --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,15 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": [ + "src/main.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/tsconfig.json b/tsconfig.json index f3c4868..ff06eae 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,27 +1,32 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "compileOnSave": false, "compilerOptions": { "baseUrl": "./", "outDir": "./dist/out-tsc", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, "sourceMap": true, "declaration": false, "downlevelIteration": true, "experimentalDecorators": true, - "module": "esnext", "moduleResolution": "node", "importHelpers": true, - "target": "es2015", - "typeRoots": [ - "node_modules/@types" - ], + "target": "es2020", + "module": "es2020", "lib": [ - "es2018", + "es2020", "dom" ] }, "angularCompilerOptions": { - "enableIvy": true, - "fullTemplateTypeCheck": true, - "strictInjectionParameters": true + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true } -} \ No newline at end of file +} diff --git a/tsconfig.spec.json b/tsconfig.spec.json new file mode 100644 index 0000000..092345b --- /dev/null +++ b/tsconfig.spec.json @@ -0,0 +1,18 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "files": [ + "src/test.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +}