├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.e2e.json ├── package-lock.json ├── package.json ├── src ├── app │ ├── add-astronaut │ │ ├── add-astronaut.component.css │ │ ├── add-astronaut.component.html │ │ └── add-astronaut.component.ts │ ├── app.component.css │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── astronaut.service.ts │ └── types.ts ├── assets │ ├── .gitkeep │ └── astronauts.json ├── browserslist ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── karma.conf.js ├── main.ts ├── polyfills.ts ├── styles.css ├── test.ts ├── theme.scss ├── tsconfig.app.json ├── tsconfig.spec.json └── tslint.json ├── steps ├── step_1.md ├── step_2.md ├── step_3.md ├── step_4.md ├── step_5.md ├── step_6.md ├── step_7.md └── step_8.md ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # profiling files 12 | chrome-profiler-events.json 13 | speed-measure-plugin.json 14 | 15 | # IDEs and editors 16 | /.idea 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | *.sublime-workspace 23 | 24 | # IDE - VSCode 25 | .vscode/* 26 | !.vscode/settings.json 27 | !.vscode/tasks.json 28 | !.vscode/launch.json 29 | !.vscode/extensions.json 30 | .history/* 31 | 32 | # misc 33 | /.sass-cache 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | npm-debug.log 38 | yarn-error.log 39 | testem.log 40 | /typings 41 | 42 | # System Files 43 | .DS_Store 44 | Thumbs.db 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blast Off with Angular Material 2 | ### ng-conf 3 | 4 | In this workshop, we will use Angular Material to create a faceted search for the current astronauts at NASA. 5 | 6 | [Final demo](https://stackblitz.com/github/rnocc/blast-off-with-am/tree/final) 7 | 8 | [Slides](https://drive.google.com/file/d/1eTHOtAWx48WVdP0jzXNg4LkKDnPm8ufa/view) 9 | 10 | * [Step 1](./steps/step_1.md) 11 | * [Step 2](./steps/step_2.md) 12 | * [Step 3](./steps/step_3.md) 13 | * [Step 4](./steps/step_4.md) 14 | * [Step 5](./steps/step_5.md) 15 | * [Step 6](./steps/step_6.md) 16 | * [Step 7](./steps/step_7.md) 17 | * [Step 8](./steps/step_8.md) (bonus! run locally to view demo) 18 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "blast-off-with-am": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": {}, 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/blast-off-with-am", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "src/tsconfig.app.json", 21 | "assets": [ 22 | "src/favicon.ico", 23 | "src/assets" 24 | ], 25 | "styles": [ 26 | "src/theme.scss", 27 | "src/styles.css" 28 | ], 29 | "scripts": [], 30 | "es5BrowserSupport": true 31 | }, 32 | "configurations": { 33 | "production": { 34 | "fileReplacements": [ 35 | { 36 | "replace": "src/environments/environment.ts", 37 | "with": "src/environments/environment.prod.ts" 38 | } 39 | ], 40 | "optimization": true, 41 | "outputHashing": "all", 42 | "sourceMap": false, 43 | "extractCss": true, 44 | "namedChunks": false, 45 | "aot": true, 46 | "extractLicenses": true, 47 | "vendorChunk": false, 48 | "buildOptimizer": true, 49 | "budgets": [ 50 | { 51 | "type": "initial", 52 | "maximumWarning": "2mb", 53 | "maximumError": "5mb" 54 | } 55 | ] 56 | } 57 | } 58 | }, 59 | "serve": { 60 | "builder": "@angular-devkit/build-angular:dev-server", 61 | "options": { 62 | "browserTarget": "blast-off-with-am:build" 63 | }, 64 | "configurations": { 65 | "production": { 66 | "browserTarget": "blast-off-with-am:build:production" 67 | } 68 | } 69 | }, 70 | "extract-i18n": { 71 | "builder": "@angular-devkit/build-angular:extract-i18n", 72 | "options": { 73 | "browserTarget": "blast-off-with-am:build" 74 | } 75 | }, 76 | "test": { 77 | "builder": "@angular-devkit/build-angular:karma", 78 | "options": { 79 | "main": "src/test.ts", 80 | "polyfills": "src/polyfills.ts", 81 | "tsConfig": "src/tsconfig.spec.json", 82 | "karmaConfig": "src/karma.conf.js", 83 | "styles": [ 84 | "src/theme.scss", 85 | "src/styles.css" 86 | ], 87 | "scripts": [], 88 | "assets": [ 89 | "src/favicon.ico", 90 | "src/assets" 91 | ] 92 | } 93 | }, 94 | "lint": { 95 | "builder": "@angular-devkit/build-angular:tslint", 96 | "options": { 97 | "tsConfig": [ 98 | "src/tsconfig.app.json", 99 | "src/tsconfig.spec.json" 100 | ], 101 | "exclude": [ 102 | "**/node_modules/**" 103 | ] 104 | } 105 | } 106 | } 107 | }, 108 | "blast-off-with-am-e2e": { 109 | "root": "e2e/", 110 | "projectType": "application", 111 | "prefix": "", 112 | "architect": { 113 | "e2e": { 114 | "builder": "@angular-devkit/build-angular:protractor", 115 | "options": { 116 | "protractorConfig": "e2e/protractor.conf.js", 117 | "devServerTarget": "blast-off-with-am:serve" 118 | }, 119 | "configurations": { 120 | "production": { 121 | "devServerTarget": "blast-off-with-am:serve:production" 122 | } 123 | } 124 | }, 125 | "lint": { 126 | "builder": "@angular-devkit/build-angular:tslint", 127 | "options": { 128 | "tsConfig": "e2e/tsconfig.e2e.json", 129 | "exclude": [ 130 | "**/node_modules/**" 131 | ] 132 | } 133 | } 134 | } 135 | } 136 | }, 137 | "defaultProject": "blast-off-with-am" 138 | } 139 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to blast-off-with-am!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | })); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blast-off-with-am", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~7.2.0", 15 | "@angular/cdk": "~7.3.7", 16 | "@angular/common": "~7.2.0", 17 | "@angular/compiler": "~7.2.0", 18 | "@angular/core": "~7.2.0", 19 | "@angular/forms": "~7.2.0", 20 | "@angular/material": "~7.3.7", 21 | "@angular/platform-browser": "~7.2.0", 22 | "@angular/platform-browser-dynamic": "~7.2.0", 23 | "@angular/router": "~7.2.0", 24 | "core-js": "^2.5.4", 25 | "hammerjs": "^2.0.8", 26 | "lodash": "^4.17.11", 27 | "rxjs": "~6.3.3", 28 | "tslib": "^1.9.0", 29 | "zone.js": "~0.8.26" 30 | }, 31 | "devDependencies": { 32 | "@angular-devkit/build-angular": "~0.13.0", 33 | "@angular/cli": "~7.3.1", 34 | "@angular/compiler-cli": "~7.2.0", 35 | "@angular/language-service": "~7.2.0", 36 | "@types/jasmine": "~2.8.8", 37 | "@types/jasminewd2": "~2.0.3", 38 | "@types/lodash": "^4.14.123", 39 | "@types/node": "~8.9.4", 40 | "codelyzer": "~4.5.0", 41 | "jasmine-core": "~2.99.1", 42 | "jasmine-spec-reporter": "~4.2.1", 43 | "karma": "~3.1.1", 44 | "karma-chrome-launcher": "~2.2.0", 45 | "karma-coverage-istanbul-reporter": "~2.0.1", 46 | "karma-jasmine": "~1.1.2", 47 | "karma-jasmine-html-reporter": "^0.2.2", 48 | "protractor": "~5.4.0", 49 | "ts-node": "~7.0.0", 50 | "tslint": "~5.11.0", 51 | "typescript": "~3.2.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/app/add-astronaut/add-astronaut.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field { 2 | width: 100%; 3 | } 4 | 5 | mat-radio-button { 6 | margin-right: 1.25em; 7 | } 8 | 9 | mat-radio-button { 10 | padding-bottom: .75em; 11 | } 12 | 13 | mat-select { 14 | padding-bottom: 1.25em; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/add-astronaut/add-astronaut.component.html: -------------------------------------------------------------------------------- 1 |

Add astronaut

2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Active 15 | 16 | 17 | Inactive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Undergraduate major 27 | 32 | 33 | 34 | 35 |
36 | -------------------------------------------------------------------------------- /src/app/add-astronaut/add-astronaut.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { MatDialogRef } from '@angular/material/dialog'; 3 | import { Validators, FormGroup, FormBuilder } from '@angular/forms'; 4 | import { AstronautService } from '../astronaut.service'; 5 | import { Observable } from 'rxjs'; 6 | import { Option } from '../types'; 7 | import { map } from 'rxjs/operators'; 8 | 9 | @Component({ 10 | selector: 'app-add-astronaut', 11 | templateUrl: './add-astronaut.component.html', 12 | styleUrls: ['./add-astronaut.component.css'] 13 | }) 14 | export class AddAstronautComponent { 15 | astronaut: FormGroup; 16 | undergraduateMajors: Observable; 17 | 18 | constructor( 19 | private dialogRef: MatDialogRef, 20 | fb: FormBuilder, 21 | astronautService: AstronautService 22 | ) { 23 | this.astronaut = fb.group({ 24 | firstName: ['', Validators.required], 25 | lastName: ['', Validators.required], 26 | middleInitial: ['', Validators.maxLength(1)], 27 | active: [true], 28 | birthdate: ['', Validators.required], 29 | undergraduateMajor: ['', Validators.required] 30 | }); 31 | this.undergraduateMajors = astronautService.filters.pipe( 32 | map(filters => filters.find(f => f.category === 'undergraduateMajor')), 33 | map(filter => filter.options), 34 | ); 35 | } 36 | 37 | close(): void { 38 | this.dialogRef.close(); 39 | } 40 | 41 | saveAstronaut() { 42 | // Save to backend 43 | // Display new astronaut 44 | console.log(this.astronaut.value); 45 | this.close(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | height: 100vh; 3 | display: grid; 4 | grid-template: "toolbar" 5 | "page-container" 1fr; 6 | } 7 | 8 | h1 { 9 | grid-area: title; 10 | } 11 | 12 | mat-toolbar { 13 | grid-area: toolbar; 14 | display: grid; 15 | grid-template: "title menu" auto 16 | / 1fr 50px; 17 | } 18 | 19 | mat-sidenav-container { 20 | grid-area: page-container 21 | } 22 | 23 | mat-sidenav-content { 24 | padding: 16px; 25 | display: grid; 26 | grid-gap: 10px; 27 | grid-template-columns: repeat(auto-fill, 280px); 28 | } 29 | 30 | mat-card { 31 | width: 232px; 32 | } 33 | 34 | [mat-card-image] { 35 | height: 330px; 36 | } 37 | 38 | dl { 39 | display: grid; 40 | grid-template-columns: 2fr 3fr; 41 | } 42 | 43 | dd { 44 | margin-inline-start: 0; 45 | } 46 | 47 | mat-sidenav { 48 | width: 310px; 49 | } 50 | 51 | .search { 52 | padding: 16px; 53 | } 54 | 55 | .list-button { 56 | width: 100%; 57 | text-align: left; 58 | } 59 | 60 | mat-chip.mat-chip { 61 | margin: 0 16px 16px; 62 | } 63 | 64 | ::ng-deep .mat-chip-list-wrapper { 65 | margin: 0 !important; 66 | } 67 | 68 | .menu-button { 69 | grid-area: menu; 70 | } 71 | 72 | mat-menu { 73 | display: none 74 | } 75 | 76 | [mat-fab] { 77 | position: fixed; 78 | bottom: 50px; 79 | right: 50px; 80 | z-index: 10; 81 | } 82 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 |

Astronaut Directory

3 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

{{ filter.displayName }}

19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {{val}} 33 | cancel 34 | 35 | 36 | 37 | 38 |
39 | 40 |
41 |
42 | 43 | 44 | {{astronaut.name}} 45 | 46 | 47 |
48 |
Name
49 |
{{ astronaut.name }}
50 |
Space walks
51 |
{{ astronaut.spaceWalks }}
52 |
Undergraduate major
53 |
{{ astronaut.undergraduateMajor }}
54 |
55 |
56 |
57 |
58 |
59 | 60 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async(() => { 6 | TestBed.configureTestingModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | }).compileComponents(); 11 | })); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.debugElement.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'blast-off-with-am'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.debugElement.componentInstance; 22 | expect(app.title).toEqual('blast-off-with-am'); 23 | }); 24 | 25 | it('should render title in a h1 tag', () => { 26 | const fixture = TestBed.createComponent(AppComponent); 27 | fixture.detectChanges(); 28 | const compiled = fixture.debugElement.nativeElement; 29 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to blast-off-with-am!'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { AstronautService } from './astronaut.service'; 3 | import { Observable } from 'rxjs'; 4 | import { Astronaut, FilterState, Filter, Option } from './types'; 5 | import { MatDialog } from '@angular/material/dialog'; 6 | import { AddAstronautComponent } from './add-astronaut/add-astronaut.component'; 7 | 8 | @Component({ 9 | selector: 'app-root', 10 | templateUrl: './app.component.html', 11 | styleUrls: ['./app.component.css'] 12 | }) 13 | export class AppComponent { 14 | astronauts: Observable; 15 | filterState: FilterState; 16 | filters: Observable; 17 | 18 | constructor(astronautService: AstronautService, private dialog: MatDialog) { 19 | this.astronauts = astronautService.astronauts; 20 | this.filterState = astronautService.filterState; 21 | this.filters = astronautService.filters; 22 | } 23 | 24 | changeFilter(category: string, option: Option) { 25 | this.filterState[category] = option; 26 | } 27 | 28 | addAstronaut() { 29 | this.dialog.open(AddAstronautComponent, { 30 | width: '500px', 31 | ariaLabel: 'Add an astronaut' 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | import { MatToolbarModule } from '@angular/material/toolbar'; 5 | import { MatSidenavModule } from '@angular/material/sidenav'; 6 | import { MatCardModule } from '@angular/material/card'; 7 | import { MatFormFieldModule } from '@angular/material/form-field'; 8 | import { MatInputModule } from '@angular/material/input'; 9 | import { MatDividerModule } from '@angular/material/divider'; 10 | import { MatListModule } from '@angular/material/list'; 11 | import { MatButtonModule } from '@angular/material/button'; 12 | import { MatChipsModule } from '@angular/material/chips'; 13 | import { MatIconModule } from '@angular/material/icon'; 14 | import { MatMenuModule } from '@angular/material/menu'; 15 | import { MatDialogModule } from '@angular/material/dialog'; 16 | import { MatRadioModule } from '@angular/material/radio'; 17 | import { MatDatepickerModule } from '@angular/material/datepicker'; 18 | import { MatNativeDateModule } from '@angular/material'; 19 | import { MatSelectModule, } from '@angular/material/select'; 20 | import { ReactiveFormsModule } from '@angular/forms'; 21 | 22 | import { AppComponent } from './app.component'; 23 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 24 | import { AddAstronautComponent } from './add-astronaut/add-astronaut.component'; 25 | 26 | @NgModule({ 27 | declarations: [ 28 | AppComponent, 29 | AddAstronautComponent 30 | ], 31 | imports: [ 32 | BrowserModule, 33 | BrowserAnimationsModule, 34 | HttpClientModule, 35 | MatToolbarModule, 36 | MatSidenavModule, 37 | MatCardModule, 38 | MatFormFieldModule, 39 | MatInputModule, 40 | MatDividerModule, 41 | MatListModule, 42 | MatButtonModule, 43 | MatChipsModule, 44 | MatIconModule, 45 | MatMenuModule, 46 | MatDialogModule, 47 | MatRadioModule, 48 | MatDatepickerModule, 49 | MatNativeDateModule, 50 | MatSelectModule, 51 | ReactiveFormsModule, 52 | ], 53 | providers: [], 54 | bootstrap: [AppComponent], 55 | // Don't forget to list AddAstronautComponent as an entry component! 56 | // This is needed because it is added to the DOM programmatically-- 57 | // it doesn't appear in the template of any other component 58 | entryComponents: [AddAstronautComponent] 59 | }) 60 | export class AppModule { } 61 | -------------------------------------------------------------------------------- /src/app/astronaut.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { FilterState, Filter, Option, Astronaut } from './types'; 4 | import * as _ from 'lodash'; 5 | import { Observable } from 'rxjs'; 6 | import { tap, map, share } from 'rxjs/operators'; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class AstronautService { 12 | astronauts: Observable; 13 | filterState: FilterState = {}; 14 | filters: Observable; 15 | 16 | constructor(http: HttpClient) { 17 | this.astronauts = http.get('assets/astronauts.json').pipe( 18 | share() 19 | ); 20 | this.filters = this.astronauts.pipe( 21 | map(astronauts => this.createFilters(astronauts)) 22 | ); 23 | } 24 | 25 | private createFilters( astronauts: Astronaut[]) { 26 | return [{ 27 | category: 'spaceWalks', 28 | displayName: 'Space walks', 29 | options: this.extractFilterOptions('spaceWalks', astronauts) 30 | }, { 31 | category: 'undergraduateMajor', 32 | displayName: 'Undergraduate major', 33 | options: this.extractFilterOptions('undergraduateMajor', astronauts) 34 | }]; 35 | } 36 | 37 | private extractFilterOptions(category: string, astronauts: Astronaut[]): Option[] { 38 | this.filterState[category] = ''; 39 | return _.chain(astronauts) 40 | .groupBy(category) 41 | .keys() 42 | .sort() 43 | .value(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/app/types.ts: -------------------------------------------------------------------------------- 1 | export interface Astronaut { 2 | name: string; 3 | year: number; 4 | group: number; 5 | status: string; 6 | birthdate: string; 7 | birthPlace: string; 8 | gender: string; 9 | almaMater: string; 10 | undergraduateMajor: string; 11 | graduateMajor: string; 12 | militaryRank: string; 13 | militaryBranch: string; 14 | spaceFlights: number; 15 | spaceFlightHours: number; 16 | spaceWalks: number; 17 | spaceWalkHours: number; 18 | missions: string; 19 | deathDate: string; 20 | deathMission: string; 21 | photo: string; 22 | } 23 | 24 | export interface Filter { 25 | category: string; 26 | displayName: string; 27 | options: Option[]; 28 | } 29 | 30 | export type FilterState = Record; 31 | 32 | export type Option = string | number; 33 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnocc/blast-off-with-am/aed71570b9aea20ffe11653fce3e323f8a516dad/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/astronauts.json: -------------------------------------------------------------------------------- 1 | [{"name":"Joseph M. Acaba","year":2004,"group":19,"status":"Active","birthdate":"5/17/1967","birthPlace":"Inglewood, CA","gender":"Male","almaMater":"University of California-Santa Barbara; University of Arizona","undergraduateMajor":"Geology","graduateMajor":"Geology","militaryRank":"","militaryBranch":"","spaceFlights":2,"spaceFlightHours":3307,"spaceWalks":2,"spaceWalkHours":13,"missions":"STS-119 (Discovery), ISS-31/32 (Soyuz)","deathDate":"","deathMission":"","photo":"https://www.nasa.gov/images/content/58839main_ascan_acaba_8x10.jpg"},{"name":"Dominic A. Antonelli","year":2000,"group":18,"status":"Active","birthdate":"8/23/1967","birthPlace":"Detroit, MI","gender":"Male","almaMater":"MIT; University of Washington","undergraduateMajor":"Aeronautics & Astronautics","graduateMajor":"Aeronautics & Astronautics","militaryRank":"Commander","militaryBranch":"US Navy","spaceFlights":2,"spaceFlightHours":579,"spaceWalks":0,"spaceWalkHours":0,"missions":"STS-119 (Discovery), STS-132 (Atlantis)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/1/15/Antonelli.jpg/1280px-Antonelli.jpg"},{"name":"Richard R. Arnold II","year":2004,"group":19,"status":"Active","birthdate":"11/26/1963","birthPlace":"Cheverly, MD","gender":"Male","almaMater":"Frostburg State University; University of Maryland","undergraduateMajor":"Accounting","graduateMajor":"Environmental Science","militaryRank":"","militaryBranch":"","spaceFlights":1,"spaceFlightHours":307,"spaceWalks":2,"spaceWalkHours":12,"missions":"STS-119 (Discovery)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/a/aa/Richardarnoldv2.jpg/440px-Richardarnoldv2.jpg"},{"name":"Serena M. Aunon","year":2009,"group":20,"status":"Active","birthdate":"4/9/1976","birthPlace":"Indianapolis, IN","gender":"Female","almaMater":"George Washington University; University of Texas","undergraduateMajor":"Electrical Engineering","graduateMajor":"Medicine","militaryRank":"","militaryBranch":"","spaceFlights":0,"spaceFlightHours":0,"spaceWalks":0,"spaceWalkHours":0,"missions":"","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/3/30/Serena_M._Aunon%2C_NASA_astronaut_candidate.jpg/440px-Serena_M._Aunon%2C_NASA_astronaut_candidate.jpg"},{"name":"Michael R. Barratt","year":2000,"group":18,"status":"Active","birthdate":"4/16/1959","birthPlace":"Vancouver, WA","gender":"Male","almaMater":"University of Washington; Northwestern University; Wright State University","undergraduateMajor":"Zoology","graduateMajor":"Medicine; Aerospace Medicine","militaryRank":"","militaryBranch":"","spaceFlights":2,"spaceFlightHours":5075,"spaceWalks":1,"spaceWalkHours":5,"missions":"ISS-19/20 (Soyuz), STS-133 (Discovery)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/c/ce/Michael_R._Barratt_2010.jpg/440px-Michael_R._Barratt_2010.jpg"},{"name":"Robert L. Behnken","year":2000,"group":18,"status":"Active","birthdate":"7/28/1970","birthPlace":"Creve Couer, MO","gender":"Male","almaMater":"Washington University; California Institute of Technology","undergraduateMajor":"Physics & Mechanical Engineering","graduateMajor":"Mechanical Engineering","militaryRank":"Colonel","militaryBranch":"US Air Force","spaceFlights":2,"spaceFlightHours":708,"spaceWalks":6,"spaceWalkHours":37,"missions":"STS-123 (Endeavor), STS-130 (Endeavor)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/6/65/Robert_L._Behnken_in_2018.jpg/440px-Robert_L._Behnken_in_2018.jpg"},{"name":"Eric A. Boe","year":2000,"group":18,"status":"Active","birthdate":"10/1/1964","birthPlace":"Miami, FL","gender":"Male","almaMater":"US Air Force Academy; Georgia Institute of Technology","undergraduateMajor":"Aeronautical Engineering","graduateMajor":"Electrical Engineering","militaryRank":"Colonel","militaryBranch":"US Air Force","spaceFlights":2,"spaceFlightHours":687,"spaceWalks":0,"spaceWalkHours":0,"missions":"STS-126 (Endeavor), STS-133 (Discovery)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/d/dc/Eric_Boe_in_2018.jpg/440px-Eric_Boe_in_2018.jpg"},{"name":"Stephen G. Bowen","year":2000,"group":18,"status":"Active","birthdate":"2/13/1964","birthPlace":"Cohasset, MA","gender":"Male","almaMater":"US Naval Academy; MIT","undergraduateMajor":"Electrical Engineering","graduateMajor":"Ocean Engineering","militaryRank":"Captain","militaryBranch":"US Navy","spaceFlights":3,"spaceFlightHours":970,"spaceWalks":7,"spaceWalkHours":47,"missions":"STS-126 (Endeavor), STS-132 (Atlantis), STS-133 (Discovery)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c4/Stephenbowenv2.jpg/440px-Stephenbowenv2.jpg"},{"name":"Randolph J. Bresnik","year":2004,"group":19,"status":"Active","birthdate":"9/11/1967","birthPlace":"Fort Knox, KY","gender":"Male","almaMater":"The Citadel; University of Tennessee-Knoxville","undergraduateMajor":"Mathematics","graduateMajor":"Aviation Systems","militaryRank":"Colonel","militaryBranch":"US Marine Corps","spaceFlights":1,"spaceFlightHours":259,"spaceWalks":2,"spaceWalkHours":12,"missions":"STS-129 (Atlantis)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/45/Randolph_J._Bresnik.jpg/440px-Randolph_J._Bresnik.jpg"},{"name":"Daniel C. Burbank","year":1996,"group":16,"status":"Active","birthdate":"7/27/1961","birthPlace":"Machester, CT","gender":"Male","almaMater":"US Coast Guard Academy; Embry-Riddle Aeronautical University","undergraduateMajor":"Electrical Engineering","graduateMajor":"Aeronautical Science","militaryRank":"Captain","militaryBranch":"US Coast Guard (Retired)","spaceFlights":3,"spaceFlightHours":4512,"spaceWalks":1,"spaceWalkHours":7,"missions":"STS-106 (Atlantis), STS-115 (Atlantis), ISS-29/30 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/8/8d/Daniel_Burbank.jpg/440px-Daniel_Burbank.jpg"},{"name":"Tracy E. Caldwell (Dyson)","year":1998,"group":17,"status":"Active","birthdate":"8/14/1969","birthPlace":"Arcadia, CA","gender":"Female","almaMater":"California State University-Fullerton; University of California-Davis","undergraduateMajor":"Chemistry","graduateMajor":"Physical Chemistry","militaryRank":"","militaryBranch":"","spaceFlights":2,"spaceFlightHours":4531,"spaceWalks":3,"spaceWalkHours":23,"missions":"STS-118 (Endeavor), ISS-23/24 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/2/2e/Tracy_E_Caldwell_portrait.jpg/440px-Tracy_E_Caldwell_portrait.jpg"},{"name":"Christopher J. Cassidy","year":2004,"group":19,"status":"Active","birthdate":"1/4/1970","birthPlace":"Salem, MA","gender":"Male","almaMater":"US Naval Academy; MIT","undergraduateMajor":"Mathematics","graduateMajor":"Ocean Engineering","militaryRank":"Commander","militaryBranch":"US Navy","spaceFlights":1,"spaceFlightHours":4376,"spaceWalks":6,"spaceWalkHours":31,"missions":"STS-127 (Endeavor); ISS-35/36 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/2/2d/JohnCassidyv2.jpg/440px-JohnCassidyv2.jpg"},{"name":"Gregory E. Chamitoff","year":1998,"group":17,"status":"Active","birthdate":"8/6/1962","birthPlace":"Montreal, Canada","gender":"Male","almaMater":"California Polytechnic State University; California Institute of Technology; MIT","undergraduateMajor":"Electrical Engineering","graduateMajor":"Aeronautical Engineering; Aeronautics & Astronautics","militaryRank":"","militaryBranch":"","spaceFlights":2,"spaceFlightHours":4770,"spaceWalks":2,"spaceWalkHours":13,"missions":"STS-124/126 (Discovery/Endeavor), STS-134 (Endeavor)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/Gregorychamitoffv2.jpg/440px-Gregorychamitoffv2.jpg"},{"name":"Catherine G. Coleman","year":1992,"group":14,"status":"Active","birthdate":"12/14/1960","birthPlace":"Charleston, SC","gender":"Female","almaMater":"MIT; University of Massachusetts","undergraduateMajor":"Chemistry","graduateMajor":"Polymer Science & Engineering","militaryRank":"Colonel","militaryBranch":"US Air Force (Retired)","spaceFlights":3,"spaceFlightHours":4324,"spaceWalks":0,"spaceWalkHours":0,"missions":"STS-73 (Columbia), STS-93 (Columbia), ISS-26/27 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/b/be/Catherine_Coleman_2009.jpg/440px-Catherine_Coleman_2009.jpg"},{"name":"Alvin B. Drew Jr.","year":2000,"group":18,"status":"Active","birthdate":"11/5/1962","birthPlace":"Washington, DC","gender":"Male","almaMater":"US Air Force Academy; Embry-Riddle Aeronautical University","undergraduateMajor":"Physics & Astronautical Engineering","graduateMajor":"Aerospace Science; Political Science","militaryRank":"Colonel","militaryBranch":"US Air Force","spaceFlights":2,"spaceFlightHours":613,"spaceWalks":2,"spaceWalkHours":13,"missions":"STS-118 (Endeavor), STS-133 (Discovery)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/1/1f/Alvin_drew-2007.jpg/440px-Alvin_drew-2007.jpg"},{"name":"Jeanette J. Epps","year":2009,"group":20,"status":"Active","birthdate":"11/3/1970","birthPlace":"Syracuse, NY","gender":"Female","almaMater":"LeMoyne College; University of Maryland","undergraduateMajor":"Physics","graduateMajor":"Aerospace Engineering","militaryRank":"","militaryBranch":"","spaceFlights":0,"spaceFlightHours":0,"spaceWalks":0,"spaceWalkHours":0,"missions":"","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Jeanette_J._Epps.jpg/440px-Jeanette_J._Epps.jpg"},{"name":"Andrew J. Feustel","year":2000,"group":18,"status":"Active","birthdate":"8/25/1965","birthPlace":"Lancaster, PA","gender":"Male","almaMater":"Purdue University; Queen’s University-Canada","undergraduateMajor":"Solid Earth Sciences","graduateMajor":"Geophysics; Seismology","militaryRank":"","militaryBranch":"","spaceFlights":2,"spaceFlightHours":687,"spaceWalks":6,"spaceWalkHours":42,"missions":"STS-125 (Atlantis), STS-134 (Endeavor)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/1/1e/Andrewfeustelv2.jpg/440px-Andrewfeustelv2.jpg"},{"name":"E. Michael Fincke","year":1996,"group":16,"status":"Active","birthdate":"3/14/1967","birthPlace":"Pittsburgh, PA","gender":"Male","almaMater":"MIT; Stanford University; University of Houston-Clear Lake","undergraduateMajor":"Aeronautics & Astronautics","graduateMajor":"Aeronautics & Astronautics; Physical Sciences","militaryRank":"Colonel","militaryBranch":"US Air Force","spaceFlights":3,"spaceFlightHours":9159,"spaceWalks":9,"spaceWalkHours":48,"missions":"ISS-09 (Soyuz), ISS-18 (Soyuz), STS-134 (Endeavor)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/a/a3/Edward_Michael_Fincke.jpg/440px-Edward_Michael_Fincke.jpg"},{"name":"Jack D. Fischer","year":2009,"group":20,"status":"Active","birthdate":"1/23/1974","birthPlace":"Louisville, CO","gender":"Male","almaMater":"US Air Force Academy; MIT","undergraduateMajor":"Astronautical Engineering","graduateMajor":"Aeronautics & Astronautics","militaryRank":"","militaryBranch":"","spaceFlights":0,"spaceFlightHours":0,"spaceWalks":0,"spaceWalkHours":0,"missions":"","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/8/88/Jack_D._Fischer%2C_official_portrait.jpg/440px-Jack_D._Fischer%2C_official_portrait.jpg"},{"name":"C. Michael Foale","year":1987,"group":12,"status":"Active","birthdate":"1/6/1957","birthPlace":"Louth, England","gender":"Male","almaMater":"Cambridge University","undergraduateMajor":"Physics","graduateMajor":"Laboratory Astrophysics","militaryRank":"","militaryBranch":"","spaceFlights":6,"spaceFlightHours":8970,"spaceWalks":4,"spaceWalkHours":22,"missions":"STS-45 (Atlantis), STS-56 (Discovery), STS-63 (Discovery), STS-84/86 (Atlantis), STS-103 (Discovery), ISS-08 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/2/29/Michael_Foale_-_official_astronaut_portrait.jpg/440px-Michael_Foale_-_official_astronaut_portrait.jpg"},{"name":"Kevin A. Ford","year":2000,"group":18,"status":"Active","birthdate":"7/7/1960","birthPlace":"Portland, IN","gender":"Male","almaMater":"University of Notre Dame; Troy State University; University of Florida; Air Force Institute of Technology","undergraduateMajor":"Aerospace Engineering","graduateMajor":"International Relations; Aerospace Engineering; Astronautical Engineering","militaryRank":"Colonel","militaryBranch":"US Air Force (Retired)","spaceFlights":2,"spaceFlightHours":3781,"spaceWalks":0,"spaceWalkHours":0,"missions":"STS-128 (Discovery), ISS-33/34 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/f/f1/Kevin_A._Ford.jpg/440px-Kevin_A._Ford.jpg"},{"name":"Michael E. Fossum","year":1998,"group":17,"status":"Active","birthdate":"12/19/1957","birthPlace":"Sioux Falls, SD","gender":"Male","almaMater":"Texas A&M University; Air Force Institute of Technology; University of Houston-Clear Lake","undergraduateMajor":"Mechanical Engineering","graduateMajor":"Systems Engineering; Physical Science (Space Science)","militaryRank":"","militaryBranch":"US Air Force Reserves (Retired)","spaceFlights":3,"spaceFlightHours":4651,"spaceWalks":7,"spaceWalkHours":48,"missions":"STS-121 (Discovery), STS-124 (Discovery), ISS-28/29 (Soyuz)","deathDate":"","deathMission":"","photo":"https://www.nasa.gov/sites/default/files/thumbnails/image/9734419146_5a350ceef2_o.jpg"},{"name":"Michael S. Hopkins","year":2009,"group":20,"status":"Active","birthdate":"12/28/1968","birthPlace":"Lebanon, MO","gender":"Male","almaMater":"University of Illinois; Stanford University","undergraduateMajor":"Aerospace Engineering","graduateMajor":"Aerospace Engineering","militaryRank":"Colonel","militaryBranch":"US Air Force","spaceFlights":1,"spaceFlightHours":3990,"spaceWalks":2,"spaceWalkHours":13,"missions":"ISS-37/38 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/6/65/Michael_S._Hopkins_in_2018.jpg/440px-Michael_S._Hopkins_in_2018.jpg"},{"name":"Douglas G. Hurley","year":2000,"group":18,"status":"Active","birthdate":"10/21/1966","birthPlace":"Endicott, NY","gender":"Male","almaMater":"Tulane University","undergraduateMajor":"Civil Engineering","graduateMajor":"","militaryRank":"Colonel","militaryBranch":"US Marine Corps","spaceFlights":2,"spaceFlightHours":683,"spaceWalks":0,"spaceWalkHours":0,"missions":"STS-127 (Endeavor), STS-135 (Atlantis)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/2/2e/Douglas_Hurley_in_2018.jpg/440px-Douglas_Hurley_in_2018.jpg"},{"name":"Gregory H. Johnson","year":1998,"group":17,"status":"Active","birthdate":"5/12/1962","birthPlace":"London, England","gender":"Male","almaMater":"US Air Force Academy; Columbia University; University of Texas","undergraduateMajor":"Aeronautical Engineering","graduateMajor":"Flight Structures Engineering; Business Administration","militaryRank":"Colonel","militaryBranch":"US Air Force (Retired)","spaceFlights":2,"spaceFlightHours":755,"spaceWalks":0,"spaceWalkHours":0,"missions":"STS-123 (Endeavor), STS-134 (Endeavor)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/1/11/Gregory_H._Johnson.jpg/440px-Gregory_H._Johnson.jpg"},{"name":"Scott J. Kelly","year":1996,"group":16,"status":"Active","birthdate":"2/21/1964","birthPlace":"Orange, NJ","gender":"Male","almaMater":"State University of New York Maritime College; University of Tennessee-Knoxville","undergraduateMajor":"Electrical Engineering","graduateMajor":"Aviation Systems","militaryRank":"Captain","militaryBranch":"US Navy (Retired)","spaceFlights":4,"spaceFlightHours":12490,"spaceWalks":3,"spaceWalkHours":18,"missions":"STS-103 (Discovery), STS-118 (Endeavor), ISS-25/26 (Soyuz), ISS-43/44/45/46 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/1/15/Scott_J._Kelly.jpg/440px-Scott_J._Kelly.jpg"},{"name":"Robert Shane Kimbrough","year":2004,"group":19,"status":"Active","birthdate":"6/4/1967","birthPlace":"Killeen, TX","gender":"Male","almaMater":"US Military Academy; Georgia Institute of Technology","undergraduateMajor":"Aerospace Engineering","graduateMajor":"Operations Research","militaryRank":"Colonel","militaryBranch":"US Army","spaceFlights":3,"spaceFlightHours":3720,"spaceWalks":4,"spaceWalkHours":25,"missions":"STS-126 (Endeavor), ISS-49/50 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/a/a9/Shanekimbroughv2.jpg/440px-Shanekimbroughv2.jpg"},{"name":"Timothy L. Kopra","year":2000,"group":18,"status":"Active","birthdate":"4/9/1963","birthPlace":"Austin, TX","gender":"Male","almaMater":"US Military Academy; Georgia Institute of Technology; US Army War College","undergraduateMajor":"Computer Science","graduateMajor":"Aerospace Engineering; Strategic Studies","militaryRank":"Colonel","militaryBranch":"US Army (Retired)","spaceFlights":2,"spaceFlightHours":5857,"spaceWalks":3,"spaceWalkHours":13,"missions":"STS-127/128 (Endeavor/Discovery), ISS-46/47 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/0/02/TimotyKorpav2.jpg/440px-TimotyKorpav2.jpg"},{"name":"Kjell N. Lindgren","year":2009,"group":20,"status":"Active","birthdate":"1/23/1973","birthPlace":"Taipei, Taiwan","gender":"Male","almaMater":"US Air Force Academy; University of Colorado; Colorado State University; University of Minnesota; University of Texas Medical Branch-Galveston","undergraduateMajor":"Biology","graduateMajor":"Medicine; Cardiovascular Physiology; Health Informatics; Public Health","militaryRank":"","militaryBranch":"","spaceFlights":1,"spaceFlightHours":3400,"spaceWalks":2,"spaceWalkHours":15,"missions":"ISS-44/45 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/d/d0/Kjell_Lindgren_in_EMU.jpg/440px-Kjell_Lindgren_in_EMU.jpg"},{"name":"Thomas H. Marshburn","year":2004,"group":19,"status":"Active","birthdate":"8/29/1960","birthPlace":"Statesville, NC","gender":"Male","almaMater":"Davidson College; University of Virginia; Wake Forest University; University of Texas Medical Branch-Galveston","undergraduateMajor":"Physics","graduateMajor":"Engineering Physics; Medicine; Medical Science","militaryRank":"","militaryBranch":"","spaceFlights":2,"spaceFlightHours":3871,"spaceWalks":4,"spaceWalkHours":24,"missions":"STS-127 (Endeavor), ISS-34/35 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/1/1d/ThomasMarshburnv2.jpg/440px-ThomasMarshburnv2.jpg"},{"name":"Richard A. Mastracchio","year":1996,"group":16,"status":"Active","birthdate":"2/11/1960","birthPlace":"Waterbury, CT","gender":"Male","almaMater":"University of Connecticut; Rensselaer Polytechnic Institute; University of Houston-Clear Lake","undergraduateMajor":"Electrical Engineering","graduateMajor":"Electrical Engineering; Physical Sciences","militaryRank":"","militaryBranch":"","spaceFlights":4,"spaceFlightHours":5461,"spaceWalks":9,"spaceWalkHours":53,"missions":"STS-106 (Atlantis), STS-118 (Endeavor), STS-131 (Discovery), ISS-38/39 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/9/9e/Richard_Mastracchio_2013.jpg/440px-Richard_Mastracchio_2013.jpg"},{"name":"K. Megan McArthur","year":2000,"group":18,"status":"Active","birthdate":"8/30/1971","birthPlace":"Honolulu, HI","gender":"Female","almaMater":"University of California-Los Angeles; University of California-San Diego","undergraduateMajor":"Aerospace Engineering","graduateMajor":"Oceanography","militaryRank":"","militaryBranch":"","spaceFlights":1,"spaceFlightHours":309,"spaceWalks":0,"spaceWalkHours":0,"missions":"STS-125 (Atlantis)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Meganmcarthurv2.jpg/440px-Meganmcarthurv2.jpg"},{"name":"Dorothy M. Metcalf-Lindenberger","year":2004,"group":19,"status":"Active","birthdate":"5/2/1975","birthPlace":"Colorado Springs, CO","gender":"Female","almaMater":"Whitman College","undergraduateMajor":"Geology","graduateMajor":"","militaryRank":"","militaryBranch":"","spaceFlights":1,"spaceFlightHours":362,"spaceWalks":0,"spaceWalkHours":0,"missions":"STS-131 (Discovery)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Dorothy_Metcalf-Lindenburger_JSC2004-E-40090.jpg/440px-Dorothy_Metcalf-Lindenburger_JSC2004-E-40090.jpg"},{"name":"Karen L. Nyberg","year":2000,"group":18,"status":"Active","birthdate":"10/7/1969","birthPlace":"Parker’s Prairie, MN","gender":"Female","almaMater":"University of North Dakota; University of Texas","undergraduateMajor":"Mechanical Engineering","graduateMajor":"Mechanical Engineering","militaryRank":"","militaryBranch":"","spaceFlights":2,"spaceFlightHours":4320,"spaceWalks":0,"spaceWalkHours":0,"missions":"STS-124 (Discovery), ISS-36/37 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b2/Karen_nyberg_v2.jpg/440px-Karen_nyberg_v2.jpg"},{"name":"Donald R. Pettit","year":1996,"group":16,"status":"Active","birthdate":"4/20/1955","birthPlace":"Silverton, OR","gender":"Male","almaMater":"Oregon State University; University of Arizona","undergraduateMajor":"Chemical Engineering","graduateMajor":"Chemical Engineering","militaryRank":"","militaryBranch":"","spaceFlights":3,"spaceFlightHours":8872,"spaceWalks":2,"spaceWalkHours":13,"missions":"ISS-6 (Soyuz), STS-126 (Endeavor), ISS-30/31 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/e/e3/Donald_R._Pettit.jpg/440px-Donald_R._Pettit.jpg"},{"name":"Kathleen Rubins","year":2009,"group":20,"status":"Active","birthdate":"10/14/1978","birthPlace":"Farmington, CT","gender":"Female","almaMater":"University of California-San Diego; Stanford University","undergraduateMajor":"Molecular Biology","graduateMajor":"Cancer Biology","militaryRank":"","militaryBranch":"","spaceFlights":1,"spaceFlightHours":2762,"spaceWalks":2,"spaceWalkHours":13,"missions":"ISS-48/49 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Kathleen_Rubins_portrait.jpg/440px-Kathleen_Rubins_portrait.jpg"},{"name":"Nicole P. Stott","year":2000,"group":18,"status":"Active","birthdate":"11/19/1962","birthPlace":"Albany, NY","gender":"Female","almaMater":"Embry-Riddle Aeronautical University; University of Central Florida","undergraduateMajor":"Aeronautical Engineering","graduateMajor":"Engineering Management","militaryRank":"","militaryBranch":"","spaceFlights":2,"spaceFlightHours":2477,"spaceWalks":1,"spaceWalkHours":6,"missions":"STS-128/129 (Discovery/Atlantis), STS-133 (Discovery)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Nicole_Stott_v2.jpg/440px-Nicole_Stott_v2.jpg"},{"name":"Steven R. Swanson","year":1998,"group":17,"status":"Active","birthdate":"12/3/1960","birthPlace":"Syracuse, NY","gender":"Male","almaMater":"University of Colorado; Florida Atlantic University; Texas A&M University","undergraduateMajor":"Engineering Physics","graduateMajor":"Computer Systems; Computer Science","militaryRank":"","militaryBranch":"","spaceFlights":3,"spaceFlightHours":4700,"spaceWalks":5,"spaceWalkHours":28,"missions":"STS-117 (Atlantis), STS-119 (Discovery), ISS-39/40 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Steven_swanson_v2.jpg/440px-Steven_swanson_v2.jpg"},{"name":"Scott D. Tingle","year":2009,"group":20,"status":"Active","birthdate":"7/19/1965","birthPlace":"Attleboro, MA","gender":"Male","almaMater":"Southeastern Massachusetts University; Purdue University","undergraduateMajor":"Mechanical Engineering","graduateMajor":"Mechanical Engineering","militaryRank":"Commander","militaryBranch":"US Navy","spaceFlights":0,"spaceFlightHours":0,"spaceWalks":0,"spaceWalkHours":0,"missions":"","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/Scott_D._Tingle%2C_official_portrait.jpg/440px-Scott_D._Tingle%2C_official_portrait.jpg"},{"name":"Mark T. Vande Hei","year":2009,"group":20,"status":"Active","birthdate":"11/10/1966","birthPlace":"Falls Church, VA","gender":"Male","almaMater":"Saint John’s University; Stanford University","undergraduateMajor":"Physics","graduateMajor":"Applied Physics","militaryRank":"Colonel","militaryBranch":"US Army","spaceFlights":0,"spaceFlightHours":0,"spaceWalks":0,"spaceWalkHours":0,"missions":"","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Mark_Vande_Hei%2C_official_portrait.jpg/440px-Mark_Vande_Hei%2C_official_portrait.jpg"},{"name":"Terry W. Virts Jr.","year":2000,"group":18,"status":"Active","birthdate":"12/1/1967","birthPlace":"Baltimore, MD","gender":"Male","almaMater":"US Air Force Academy; Embry-Riddle Aeronautical University","undergraduateMajor":"Mathematics","graduateMajor":"Aeronautics","militaryRank":"Colonel","militaryBranch":"US Air Force","spaceFlights":2,"spaceFlightHours":5122,"spaceWalks":3,"spaceWalkHours":18,"missions":"STS-130 (Endeavor), ISS-42/43 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/7/76/Terry_Virts.jpg/440px-Terry_Virts.jpg"},{"name":"Rex J. Walheim","year":1996,"group":16,"status":"Active","birthdate":"10/10/1962","birthPlace":"Redwood, CA","gender":"Male","almaMater":"University of California-Berkeley; University of Houston","undergraduateMajor":"Mechanical Engineering","graduateMajor":"Industrial Engineering","militaryRank":"Colonel","militaryBranch":"US Air Force (Retired)","spaceFlights":3,"spaceFlightHours":872,"spaceWalks":5,"spaceWalkHours":36,"missions":"STS-110 (Atlantis), STS-122 (Atlantis), STS-135 (Atlantis)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Rex_J._Walheim.jpg/440px-Rex_J._Walheim.jpg"},{"name":"Shannon Walker","year":2004,"group":19,"status":"Active","birthdate":"6/4/1965","birthPlace":"Houston, TX","gender":"Female","almaMater":"Rice University","undergraduateMajor":"Space Physics","graduateMajor":"Space Physics","militaryRank":"","militaryBranch":"","spaceFlights":1,"spaceFlightHours":3919,"spaceWalks":0,"spaceWalkHours":0,"missions":"ISS-24/25 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/8/8d/ShannonWalker.jpg/440px-ShannonWalker.jpg"},{"name":"Douglas H. Wheelock","year":1998,"group":17,"status":"Active","birthdate":"5/5/1960","birthPlace":"Binghamton, NY","gender":"Male","almaMater":"US Military Academy; Georgia Institute of Technology","undergraduateMajor":"Applied Science & Engineering","graduateMajor":"Aerospace Engineering","militaryRank":"Colonel","militaryBranch":"US Army","spaceFlights":2,"spaceFlightHours":4281,"spaceWalks":6,"spaceWalkHours":43,"missions":"STS-120 (Discovery), ISS-24/25 (Soyuz)","deathDate":"","deathMission":"","photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/8/8e/Wheelock_Douglas.jpg/440px-Wheelock_Douglas.jpg"},{"name":"Peggy A. Whitson","year":1996,"group":16,"status":"Active","birthdate":"2/9/1960","birthPlace":"Mt. Ayr, IA","gender":"Female","almaMater":"Iowa Wesleyan College; Rice University","undergraduateMajor":"Chemistry & Biology","graduateMajor":"Biochemistry","militaryRank":"","militaryBranch":"","spaceFlights":3,"spaceFlightHours":11698,"spaceWalks":7,"spaceWalkHours":46,"missions":"STS-111/113 (Endeavor), ISS-16 (Soyuz), ISS-50/51 (Soyuz)","deathDate":"","deathMission":"","photo":"https://www.nasa.gov/sites/default/files/styles/side_image/public/thumbnails/image/9415019527_aff06dd838_o.jpg?itok=Ctq0KqgK"},{"name":"Jeffrey N. Williams","year":1996,"group":16,"status":"Active","birthdate":"1/18/1958","birthPlace":"Superior, WI","gender":"Male","almaMater":"US Military Academy; US Naval Postgraduate School; US Naval War College","undergraduateMajor":"Applied Science & Engineering","graduateMajor":"Aeronautical Engineering; National Security & Strategic Studies","militaryRank":"Colonel","militaryBranch":"US Army (Retired)","spaceFlights":4,"spaceFlightHours":12818,"spaceWalks":5,"spaceWalkHours":32,"missions":"STS-101 (Atlantis), ISS-13 (Soyuz), ISS-21/22 (Soyuz), ISS-47/48 (Soyuz)","deathDate":"","deathMission":"","photo":"https://www.nasa.gov/sites/default/files/styles/side_image/public/thumbnails/image/14181722851_c0ab0e1916_o.jpg?itok=Si9hnFvH"},{"name":"Sunita L. Williams","year":1998,"group":17,"status":"Active","birthdate":"9/19/1965","birthPlace":"Euclid, OH","gender":"Female","almaMater":"US Naval Academy; Florida Institute of Technology","undergraduateMajor":"Physical Science","graduateMajor":"Engineering Management","militaryRank":"Captain","militaryBranch":"US Navy","spaceFlights":2,"spaceFlightHours":7721,"spaceWalks":7,"spaceWalkHours":50,"missions":"STS-116/117 (Discovery/Atlantis), ISS-32/33 (Soyuz)","deathDate":"","deathMission":"","photo":"https://www.nasa.gov/sites/default/files/thumbnails/image/jsc2012e096604.jpg"},{"name":"Barry E. Wilmore","year":2000,"group":18,"status":"Active","birthdate":"12/29/1962","birthPlace":"Murfreesboro, TN","gender":"Male","almaMater":"Tennessee Technological University; University of Tennessee","undergraduateMajor":"Electrical Engineering","graduateMajor":"Electrical Engineering; Aviation Systems","militaryRank":"Captain","militaryBranch":"US Navy","spaceFlights":2,"spaceFlightHours":4272,"spaceWalks":4,"spaceWalkHours":25,"missions":"STS-129 (Atlantis), ISS-41/42 (Soyuz)","deathDate":"","deathMission":"","photo":"https://www.nasa.gov/sites/default/files/styles/side_image/public/thumbnails/image/9367741944_1c2e5003a2_o_0.jpg?itok=D9IEbGvg"},{"name":"Stephanie D. Wilson","year":1996,"group":16,"status":"Active","birthdate":"9/27/1966","birthPlace":"Boston, MA","gender":"Female","almaMater":"Harvard University; University of Texas","undergraduateMajor":"Engineering Science","graduateMajor":"Aerospace Engineering","militaryRank":"","militaryBranch":"","spaceFlights":3,"spaceFlightHours":1031,"spaceWalks":0,"spaceWalkHours":0,"missions":"STS-121 (Discovery), STS-120 (Discovery), STS-131 (Discovery)","deathDate":"","deathMission":"","photo":"https://www.nasa.gov/sites/default/files/styles/side_image/public/thumbnails/image/9774695006_9f39d3e615_o.jpg?itok=K7ds1XlL"},{"name":"G. Reid Wiseman","year":2009,"group":20,"status":"Active","birthdate":"11/11/1975","birthPlace":"Baltimore, MD","gender":"Male","almaMater":"Rensselaer Polytechnic Institute; Johns Hopkins University","undergraduateMajor":"Computer & Systems Engineering","graduateMajor":"Systems Engineering","militaryRank":"Commander","militaryBranch":"US Navy","spaceFlights":1,"spaceFlightHours":3968,"spaceWalks":2,"spaceWalkHours":13,"missions":"ISS-40/41 (Soyuz)","deathDate":"","deathMission":"","photo":"https://www.nasa.gov/sites/default/files/styles/side_image/public/thumbnails/image/jsc2015e075864.jpg?itok=UTImZ6Hy"}] 2 | -------------------------------------------------------------------------------- /src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnocc/blast-off-with-am/aed71570b9aea20ffe11653fce3e323f8a516dad/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BlastOffWithAm 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage/blast-off-with-am'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import 'hammerjs'; 2 | import { enableProdMode } from '@angular/core'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | 5 | import { AppModule } from './app/app.module'; 6 | import { environment } from './environments/environment'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule) 13 | .catch(err => console.error(err)); 14 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | html, body { height: 100%; } 4 | body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } 5 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/theme.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Generated theme by Material Theme Generator 3 | * https://material-theme-generator.travetto.io 4 | */ 5 | 6 | @import '~@angular/material/theming'; 7 | // Include the common styles for Angular Material. We include this here so that you only 8 | // have to load a single css file for Angular Material in your app. 9 | 10 | // Fonts 11 | @import url('https://fonts.googleapis.com/css?family=Roboto:300,400,500'); 12 | 13 | $fontConfig: ( 14 | display-4: mat-typography-level(112px, 112px, 300, 'Roboto', -0.0134em), 15 | display-3: mat-typography-level(56px, 56px, 400, 'Roboto', -0.0089em), 16 | display-2: mat-typography-level(45px, 48px, 400, 'Roboto', 0.0000em), 17 | display-1: mat-typography-level(34px, 40px, 400, 'Roboto', 0.0074em), 18 | headline: mat-typography-level(24px, 32px, 400, 'Roboto', 0.0000em), 19 | title: mat-typography-level(20px, 32px, 500, 'Roboto', 0.0075em), 20 | subheading-2: mat-typography-level(16px, 28px, 400, 'Roboto', 0.0094em), 21 | subheading-1: mat-typography-level(15px, 24px, 500, 'Roboto', 0.0067em), 22 | body-2: mat-typography-level(14px, 24px, 500, 'Roboto', 0.0179em), 23 | body-1: mat-typography-level(14px, 20px, 400, 'Roboto', 0.0179em), 24 | button: mat-typography-level(14px, 14px, 500, 'Roboto', 0.0893em), 25 | caption: mat-typography-level(12px, 20px, 400, 'Roboto', 0.0333em), 26 | input: mat-typography-level(inherits, 1.125, 400, 'Roboto', 1.5px) 27 | ); 28 | 29 | // Foreground Elements 30 | 31 | // Light Theme Text 32 | $dark-text: #000000; 33 | $dark-primary-text: rgba($dark-text, 0.87); 34 | $dark-accent-text: rgba($dark-primary-text, 0.54); 35 | $dark-disabled-text: rgba($dark-primary-text, 0.38); 36 | $dark-dividers: rgba($dark-primary-text, 0.12); 37 | $dark-focused: rgba($dark-primary-text, 0.12); 38 | 39 | $mat-light-theme-foreground: ( 40 | base: black, 41 | divider: $dark-dividers, 42 | dividers: $dark-dividers, 43 | disabled: $dark-disabled-text, 44 | disabled-button: rgba($dark-text, 0.26), 45 | disabled-text: $dark-disabled-text, 46 | elevation: black, 47 | secondary-text: $dark-accent-text, 48 | hint-text: $dark-disabled-text, 49 | accent-text: $dark-accent-text, 50 | icon: $dark-accent-text, 51 | icons: $dark-accent-text, 52 | text: $dark-primary-text, 53 | slider-min: $dark-primary-text, 54 | slider-off: rgba($dark-text, 0.26), 55 | slider-off-active: $dark-disabled-text, 56 | ); 57 | 58 | // Dark Theme text 59 | $light-text: #ffffff; 60 | $light-primary-text: $light-text; 61 | $light-accent-text: rgba($light-primary-text, 0.7); 62 | $light-disabled-text: rgba($light-primary-text, 0.5); 63 | $light-dividers: rgba($light-primary-text, 0.12); 64 | $light-focused: rgba($light-primary-text, 0.12); 65 | 66 | $mat-dark-theme-foreground: ( 67 | base: $light-text, 68 | divider: $light-dividers, 69 | dividers: $light-dividers, 70 | disabled: $light-disabled-text, 71 | disabled-button: rgba($light-text, 0.3), 72 | disabled-text: $light-disabled-text, 73 | elevation: black, 74 | hint-text: $light-disabled-text, 75 | secondary-text: $light-accent-text, 76 | accent-text: $light-accent-text, 77 | icon: $light-text, 78 | icons: $light-text, 79 | text: $light-text, 80 | slider-min: $light-text, 81 | slider-off: rgba($light-text, 0.3), 82 | slider-off-active: rgba($light-text, 0.3), 83 | ); 84 | 85 | // Background config 86 | // Light bg 87 | $light-background: #fafafa; 88 | $light-bg-darker-5: darken($light-background, 5%); 89 | $light-bg-darker-10: darken($light-background, 10%); 90 | $light-bg-darker-20: darken($light-background, 20%); 91 | $light-bg-darker-30: darken($light-background, 30%); 92 | $light-bg-lighter-5: lighten($light-background, 5%); 93 | $dark-bg-alpha-4: rgba(#2c2c2c, 0.04); 94 | $dark-bg-alpha-12: rgba(#2c2c2c, 0.12); 95 | 96 | $mat-light-theme-background: ( 97 | background: $light-background, 98 | status-bar: $light-bg-darker-20, 99 | app-bar: $light-bg-darker-5, 100 | hover: $dark-bg-alpha-4, 101 | card: $light-bg-lighter-5, 102 | dialog: $light-bg-lighter-5, 103 | disabled-button: $dark-bg-alpha-12, 104 | raised-button: $light-bg-lighter-5, 105 | focused-button: $dark-focused, 106 | selected-button: $light-bg-darker-20, 107 | selected-disabled-button: $light-bg-darker-30, 108 | disabled-button-toggle: $light-bg-darker-10, 109 | unselected-chip: $light-bg-darker-10, 110 | disabled-list-option: $light-bg-darker-10, 111 | ); 112 | 113 | // Dark bg 114 | $dark-background: #2c2c2c; 115 | $dark-bg-lighter-5: lighten($dark-background, 5%); 116 | $dark-bg-lighter-10: lighten($dark-background, 10%); 117 | $dark-bg-lighter-20: lighten($dark-background, 20%); 118 | $dark-bg-lighter-30: lighten($dark-background, 30%); 119 | $light-bg-alpha-4: rgba(#fafafa, 0.04); 120 | $light-bg-alpha-12: rgba(#fafafa, 0.12); 121 | 122 | // Background palette for dark themes. 123 | $mat-dark-theme-background: ( 124 | background: $dark-background, 125 | status-bar: $dark-bg-lighter-20, 126 | app-bar: $dark-bg-lighter-5, 127 | hover: $light-bg-alpha-4, 128 | card: $dark-bg-lighter-5, 129 | dialog: $dark-bg-lighter-5, 130 | disabled-button: $light-bg-alpha-12, 131 | raised-button: $dark-bg-lighter-5, 132 | focused-button: $light-focused, 133 | selected-button: $dark-bg-lighter-20, 134 | selected-disabled-button: $dark-bg-lighter-30, 135 | disabled-button-toggle: $dark-bg-lighter-10, 136 | unselected-chip: $dark-bg-lighter-20, 137 | disabled-list-option: $dark-bg-lighter-10, 138 | ); 139 | 140 | // Compute font config 141 | @include mat-core($fontConfig); 142 | 143 | // Theme Config 144 | 145 | $mat-primary: ( 146 | main: #105bd8, 147 | lighter: #b7cef3, 148 | darker: #0940c8, 149 | 200: #105bd8, // For slide toggle, 150 | contrast : ( 151 | main: $light-primary-text, 152 | lighter: $dark-primary-text, 153 | darker: $light-primary-text, 154 | ) 155 | ); 156 | $theme-primary: mat-palette($mat-primary, main, lighter, darker); 157 | 158 | $mat-accent: ( 159 | main: #02bfe7, 160 | lighter: #b3ecf8, 161 | darker: #01a9dd, 162 | 200: #02bfe7, // For slide toggle, 163 | contrast : ( 164 | main: $dark-primary-text, 165 | lighter: $dark-primary-text, 166 | darker: $light-primary-text, 167 | ) 168 | ); 169 | $theme-accent: mat-palette($mat-accent, main, lighter, darker); 170 | 171 | $mat-warn: ( 172 | main: #ff0000, 173 | lighter: #ffb3b3, 174 | darker: #ff0000, 175 | 200: #ff0000, // For slide toggle, 176 | contrast : ( 177 | main: $light-primary-text, 178 | lighter: $dark-primary-text, 179 | darker: $light-primary-text, 180 | ) 181 | ); 182 | $theme-warn: mat-palette($mat-warn, main, lighter, darker);; 183 | 184 | $theme: mat-light-theme($theme-primary, $theme-accent, $theme-warn); 185 | $altTheme: mat-dark-theme($theme-primary, $theme-accent, $theme-warn); 186 | 187 | // Theme Init 188 | @include angular-material-theme($theme); 189 | 190 | .theme-alternate { 191 | @include angular-material-theme($altTheme); 192 | } 193 | 194 | 195 | // Specific component overrides, pieces that are not in line with the general theming 196 | 197 | // Handle buttons appropriately, with respect to line-height 198 | .mat-raised-button, .mat-stroked-button, .mat-flat-button { 199 | padding: 0 1.15em; 200 | margin: 0 .65em; 201 | min-width: 3em; 202 | line-height: 36.4px 203 | } 204 | 205 | .mat-standard-chip { 206 | padding: .5em .85em; 207 | min-height: 2.5em; 208 | } 209 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /steps/step_1.md: -------------------------------------------------------------------------------- 1 | * **Step 1 <-** 2 | * [Step 2](./step_2.md) 3 | * [Step 3](./step_3.md) 4 | * [Step 4](./step_4.md) 5 | * [Step 5](./step_5.md) 6 | * [Step 6](./step_6.md) 7 | * [Step 7](./step_7.md) 8 | * [Step 8](./step_8.md) 9 | 10 | ### [Full Demo](https://stackblitz.com/github/rnocc/blast-off-with-am/tree/final) 11 | 12 | ### [Open this StackBlitz to get started](https://stackblitz.com/github/rnocc/blast-off-with-am/tree/start) 13 | ### [Step 1 completed demo](https://stackblitz.com/github/rnocc/blast-off-with-am/tree/step-1) 14 | 15 | ## Step 1 16 | 17 | We already have a StackBlitz instance with some data, a service, and some types. `ng add @angular/material` has already been run, so Angular Material is ready to be used in the project. The pre-built theme is "deeppurple-amber." 18 | 19 | In this step, you will add a `` component. 20 | 21 |
22 | Import the MatToolbarModule into AppModule 23 | 24 | `app.module.ts` 25 | 26 | ```ts 27 | import { MatToolbarModule } from '@angular/material/toolbar'; 28 | 29 | @NgModule({ 30 | ... 31 | imports: [ 32 | BrowserModule, 33 | BrowserAnimationsModule, 34 | MatToolbarModule, 35 | ], 36 | ... 37 | }) 38 | ``` 39 |
40 | 41 |
42 | Replace the welcome message with the toolbar. Make it deeppurple. 43 | 44 | `app.component.html` 45 | 46 | ```html 47 | 48 |

Astronaut Directory

49 |
50 | ``` 51 |
52 | -------------------------------------------------------------------------------- /steps/step_2.md: -------------------------------------------------------------------------------- 1 | * [Step 1](./step_1.md) 2 | * **Step 2 <-** 3 | * [Step 3](./step_3.md) 4 | * [Step 4](./step_4.md) 5 | * [Step 5](./step_5.md) 6 | * [Step 6](./step_6.md) 7 | * [Step 7](./step_7.md) 8 | * [Step 8](./step_8.md) 9 | 10 | ### [Step 2 setup demo](https://stackblitz.com/github/rnocc/blast-off-with-am/tree/step-1) 11 | ### [Step 2 completed demo](https://stackblitz.com/github/rnocc/blast-off-with-am/tree/step-2) 12 | 13 | ## Step 2 14 | 15 | In this step, you will add a `` component and scaffold out the app. 16 | 17 |
18 | Import MatSidenavModule 19 | 20 | `app.module.ts` Add the `MatSidenavModule` 21 | 22 | ```ts 23 | import { MatSidenavModule } from '@angular/material/sidenav'; 24 | 25 | @NgModule({ 26 | ... 27 | imports: [ 28 | ... 29 | MatSidenavModule, 30 | ], 31 | ... 32 | }) 33 | ``` 34 |
35 | 36 |
37 | Add the mat-sidenav-container along with its child components to AppComponent 38 | 39 | `app.component.html` 40 | 41 | ```html 42 | 43 | Sidenav content 44 | Main content 45 | 46 | ``` 47 | 48 |
49 | 50 | 51 |
52 | Add CSS to lay out the page 53 | 54 | `app.component.css` 55 | ```css 56 | :host { 57 | height: 100vh; 58 | display: grid; 59 | grid-template: "toolbar" 60 | "page-container" 1fr; 61 | } 62 | 63 | h1 { 64 | grid-area: title; 65 | } 66 | 67 | mat-toolbar { 68 | grid-area: toolbar; 69 | display: grid; 70 | grid-template: "title menu" auto 71 | / 1fr 50px; 72 | } 73 | 74 | mat-sidenav-container { 75 | grid-area: page-container 76 | } 77 | ``` 78 |
79 | -------------------------------------------------------------------------------- /steps/step_3.md: -------------------------------------------------------------------------------- 1 | * [Step 1](./step_1.md) 2 | * [Step 2](./step_2.md) 3 | * **Step 3 <-** 4 | * [Step 4](./step_4.md) 5 | * [Step 5](./step_5.md) 6 | * [Step 6](./step_6.md) 7 | * [Step 7](./step_7.md) 8 | * [Step 8](./step_8.md) 9 | 10 | ### [Step 3 setup demo](https://stackblitz.com/github/rnocc/blast-off-with-am/tree/step-2) 11 | ### [Step 3 completed demo](https://stackblitz.com/github/rnocc/blast-off-with-am/tree/step-3) 12 | 13 | ## Step 3 14 | 15 | In this step, you will add a card for each astronaut with an image and some biographical details. The astronaut data is available in the component as astronauts (it's an observable, so remember to use astronauts | async) 16 | 17 |
Add the MatCardModule 18 | 19 | `app.module.ts` 20 | 21 | ```ts 22 | import { MatCardModule } from '@angular/material/card'; 23 | 24 | @NgModule({ 25 | ... 26 | imports: [ 27 | ... 28 | MatCardModule, 29 | ], 30 | ... 31 | }) 32 | ``` 33 |
34 | 35 |
Replace the placeholder word "Main content" with the mat-cards (using *ngFor) in mat-sidenav-content. Give each card a title, image, and some content. 36 | 37 | `app.component.html` 38 | 39 | ```html 40 | 41 | 42 | {{astronaut.name}} 43 | 44 | 45 |
46 |
Name
47 |
{{ astronaut.name }}
48 |
Space walks
49 |
{{ astronaut.spaceWalks }}
50 |
Undergraduate major
51 |
{{ astronaut.undergraduateMajor }}
52 |
53 |
54 |
55 |
56 | ``` 57 |
58 | 59 |
Add some layout styles 60 | 61 | `app.component.css` 62 | 63 | ```css 64 | mat-sidenav-content { 65 | padding: 16px; 66 | display: grid; 67 | grid-gap: 10px; 68 | grid-template-columns: repeat(auto-fill, 280px); 69 | } 70 | 71 | mat-card { 72 | width: 232px; 73 | } 74 | 75 | [mat-card-image] { 76 | height: 330px; 77 | } 78 | 79 | dl { 80 | display: grid; 81 | grid-template-columns: 2fr 3fr; 82 | } 83 | 84 | dd { 85 | margin-inline-start: 0; 86 | } 87 | ``` 88 |
89 | 90 |
91 | 92 |
Here's the TypeScript type for an Astronaut (for adding data to your card content) 93 | 94 | export interface Astronaut { 95 | name: string; 96 | year: number; 97 | group: number; 98 | status: string; 99 | birthdate: string; 100 | birthPlace: string; 101 | gender: string; 102 | almaMater: string; 103 | undergraduateMajor: string; 104 | graduateMajor: string; 105 | militaryRank: string; 106 | militaryBranch: string; 107 | spaceFlights: number; 108 | spaceFlightHours: number; 109 | spaceWalks: number; 110 | spaceWalkHours: number; 111 | missions: string; 112 | deathDate: string; 113 | deathMission: string; 114 | photo: string; 115 | } 116 |
117 | -------------------------------------------------------------------------------- /steps/step_4.md: -------------------------------------------------------------------------------- 1 | * [Step 1](./step_1.md) 2 | * [Step 2](./step_2.md) 3 | * [Step 3](./step_3.md) 4 | * **Step 4 <-** 5 | * [Step 5](./step_5.md) 6 | * [Step 6](./step_6.md) 7 | * [Step 7](./step_7.md) 8 | * [Step 8](./step_8.md) 9 | 10 | ### [Step 4 setup demo](https://stackblitz.com/github/rnocc/blast-off-with-am/tree/step-3) 11 | ### [Step 4 completed demo](https://stackblitz.com/github/rnocc/blast-off-with-am/tree/step-4) 12 | 13 | ## Step 4 14 | 15 | In this step, you will add a search box and filter options 16 | 17 |
Add the needed NgModules 18 | `app.module.ts` 19 | 20 | ```ts 21 | import { MatFormFieldModule } from '@angular/material/form-field'; 22 | import { MatInputModule } from '@angular/material/input'; 23 | import { MatDividerModule } from '@angular/material/divider'; 24 | import { MatListModule } from '@angular/material/list'; 25 | import { MatButtonModule } from '@angular/material/button'; 26 | 27 | @NgModule({ 28 | ... 29 | imports: [ 30 | ... 31 | MatFormFieldModule, 32 | MatInputModule, 33 | MatDividerModule, 34 | MatListModule, 35 | MatButtonModule, 36 | ], 37 | ... 38 | }) 39 | ``` 40 |
41 | 42 |
Take out the sidenav placeholder text. 43 | 44 | `app.component.html` 45 | 46 | ```html 47 | 48 | 49 | ``` 50 |
51 |
Add a search box to the mat-sidenav. Give it a placeholder called "Search astronauts" and add a mat-divider underneath 52 | 53 | `app.component.html` 54 | 55 | ```html 56 | 57 | 58 | 59 | 60 | 61 | 62 | ``` 63 |
64 |
Under the divider, use an *ngFor to add a mat-list for each filter 65 | 66 | `app.component.html` 67 | 68 | ```html 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |

{{ filter.displayName }}

77 | 78 | 79 | 82 | 83 | 84 |
85 | 86 |
87 |
88 | ``` 89 |
90 | 91 |
Add some layout styles 92 | 93 | `app.component.css` 94 | 95 | ```css 96 | mat-sidenav { 97 | width: 310px; 98 | } 99 | 100 | .search { 101 | padding: 0 16px; 102 | } 103 | 104 | .list-button { 105 | width: 100%; 106 | text-align: left; 107 | } 108 | 109 | mat-chip.mat-chip { 110 | margin: 0 16px 16px; 111 | } 112 | 113 | ::ng-deep .mat-chip-list-wrapper { 114 | margin: 0 !important; 115 | } 116 | ``` 117 |
118 | -------------------------------------------------------------------------------- /steps/step_5.md: -------------------------------------------------------------------------------- 1 | * [Step 1](./step_1.md) 2 | * [Step 2](./step_2.md) 3 | * [Step 3](./step_3.md) 4 | * [Step 4](./step_4.md) 5 | * **Step 5 <-** 6 | * [Step 6](./step_6.md) 7 | * [Step 7](./step_7.md) 8 | * [Step 8](./step_8.md) 9 | 10 | ### [Step 5 setup demo](https://stackblitz.com/github/rnocc/blast-off-with-am/tree/step-4) 11 | ### [Step 5 completed demo](https://stackblitz.com/github/rnocc/blast-off-with-am/tree/step-5) 12 | 13 | ## Step 5 14 | 15 | In this step, you will turn the selected filter option into a chip 16 |
Add the NgModules for chips and icons 17 | 18 | `app.module.ts` 19 | 20 | ```ts 21 | 22 | 23 | import { MatChipsModule } from '@angular/material/chips'; 24 | import { MatIconModule } from '@angular/material/icon'; 25 | 26 | @NgModule({ 27 | ... 28 | imports: [ 29 | ... 30 | MatChipsModule, 31 | MatIconModule 32 | ], 33 | ... 34 | }) 35 | ``` 36 |
37 |
Add an ng-container around the mat-list-item to only show if a filter for the category isn't selected (i.e. filterState[filter.category] === '') 38 | 39 | `app\app.component.html` 40 | 41 | ```html 42 | 43 | 44 | ... 45 | 46 | 47 | ``` 48 |
49 | 50 |
Add a mat-chip for the selected filter option. This can go right below the ng-container added above. 51 | 52 | `app\app.component.html` 53 | 54 | ```html 55 | 56 | 57 | 58 | 59 | {{val}} 60 | cancel 61 | 62 | 63 | 64 | 65 | ``` 66 |
67 | -------------------------------------------------------------------------------- /steps/step_6.md: -------------------------------------------------------------------------------- 1 | * [Step 1](./step_1.md) 2 | * [Step 2](./step_2.md) 3 | * [Step 3](./step_3.md) 4 | * [Step 4](./step_4.md) 5 | * [Step 5](./step_5.md) 6 | * **Step 6 <-** 7 | * [Step 7](./step_7.md) 8 | * [Step 8](./step_8.md) 9 | 10 | ### [Step 6 setup demo](https://stackblitz.com/github/rnocc/blast-off-with-am/tree/step-5) 11 | ### [Step 6 completed demo](https://stackblitz.com/github/rnocc/blast-off-with-am/tree/step-6) 12 | 13 | ## Step 6 14 | 15 | In this step, you will add a menu to the toolbar with a button to log out. 16 | 17 |
Look at the docs and try this on your own! Get the menu working before you work on the icon. 18 | 19 | You need to add the `MatMenuModule` to `AppModule`. 20 | 21 | Here's the code for the menu: 22 | ```html 23 | 26 | 27 | 28 | 29 | ``` 30 | 31 | And a little bit of CSS: 32 | ```css 33 | .menu-button { 34 | grid-area: menu; 35 | } 36 | 37 | mat-menu { 38 | display: none 39 | } 40 | ``` 41 |
42 | -------------------------------------------------------------------------------- /steps/step_7.md: -------------------------------------------------------------------------------- 1 | * [Step 1](./step_1.md) 2 | * [Step 2](./step_2.md) 3 | * [Step 3](./step_3.md) 4 | * [Step 4](./step_4.md) 5 | * [Step 5](./step_5.md) 6 | * [Step 6](./step_6.md) 7 | * **Step 7 <-** 8 | * [Step 8](./step_8.md) 9 | 10 | ### [Step 7 setup demo](https://stackblitz.com/github/rnocc/blast-off-with-am/tree/step-6) 11 | ### [Step 7 completed demo](https://stackblitz.com/github/rnocc/blast-off-with-am/tree/step-7) 12 | 13 | ## Step 7 14 | 15 | In this step, you will add a custom theme with NASA's colors 16 | 17 | First, go to this Material theme generator and generate a theme with NASA's primary (#105bd8) and secondary (#02bfe7) colors. Click the Copy Angular SCSS button. 18 | 19 |
20 | 21 |
Create a file called theme.scss in the root of the src folder and paste in the generated code. 22 | 23 | `theme.scss` 24 | ```scss 25 | /** 26 | * Generated theme by Material Theme Generator 27 | * https://material-theme-generator.travetto.io 28 | */ 29 | 30 | @import '~@angular/material/theming'; 31 | // Include the common styles for Angular Material. We include this here so that you only 32 | // have to load a single css file for Angular Material in your app. 33 | 34 | // Fonts 35 | @import url('https://fonts.googleapis.com/css?family=Roboto:300,400,500'); 36 | 37 | $fontConfig: ( 38 | display-4: mat-typography-level(112px, 112px, 300, 'Roboto', -0.0134em), 39 | display-3: mat-typography-level(56px, 56px, 400, 'Roboto', -0.0089em), 40 | display-2: mat-typography-level(45px, 48px, 400, 'Roboto', 0.0000em), 41 | display-1: mat-typography-level(34px, 40px, 400, 'Roboto', 0.0074em), 42 | headline: mat-typography-level(24px, 32px, 400, 'Roboto', 0.0000em), 43 | title: mat-typography-level(20px, 32px, 500, 'Roboto', 0.0075em), 44 | subheading-2: mat-typography-level(16px, 28px, 400, 'Roboto', 0.0094em), 45 | subheading-1: mat-typography-level(15px, 24px, 500, 'Roboto', 0.0067em), 46 | body-2: mat-typography-level(14px, 24px, 500, 'Roboto', 0.0179em), 47 | body-1: mat-typography-level(14px, 20px, 400, 'Roboto', 0.0179em), 48 | button: mat-typography-level(14px, 14px, 500, 'Roboto', 0.0893em), 49 | caption: mat-typography-level(12px, 20px, 400, 'Roboto', 0.0333em), 50 | input: mat-typography-level(inherits, 1.125, 400, 'Roboto', 1.5px) 51 | ); 52 | 53 | // Foreground Elements 54 | 55 | // Light Theme Text 56 | $dark-text: #000000; 57 | $dark-primary-text: rgba($dark-text, 0.87); 58 | $dark-accent-text: rgba($dark-primary-text, 0.54); 59 | $dark-disabled-text: rgba($dark-primary-text, 0.38); 60 | $dark-dividers: rgba($dark-primary-text, 0.12); 61 | $dark-focused: rgba($dark-primary-text, 0.12); 62 | 63 | $mat-light-theme-foreground: ( 64 | base: black, 65 | divider: $dark-dividers, 66 | dividers: $dark-dividers, 67 | disabled: $dark-disabled-text, 68 | disabled-button: rgba($dark-text, 0.26), 69 | disabled-text: $dark-disabled-text, 70 | elevation: black, 71 | secondary-text: $dark-accent-text, 72 | hint-text: $dark-disabled-text, 73 | accent-text: $dark-accent-text, 74 | icon: $dark-accent-text, 75 | icons: $dark-accent-text, 76 | text: $dark-primary-text, 77 | slider-min: $dark-primary-text, 78 | slider-off: rgba($dark-text, 0.26), 79 | slider-off-active: $dark-disabled-text, 80 | ); 81 | 82 | // Dark Theme text 83 | $light-text: #ffffff; 84 | $light-primary-text: $light-text; 85 | $light-accent-text: rgba($light-primary-text, 0.7); 86 | $light-disabled-text: rgba($light-primary-text, 0.5); 87 | $light-dividers: rgba($light-primary-text, 0.12); 88 | $light-focused: rgba($light-primary-text, 0.12); 89 | 90 | $mat-dark-theme-foreground: ( 91 | base: $light-text, 92 | divider: $light-dividers, 93 | dividers: $light-dividers, 94 | disabled: $light-disabled-text, 95 | disabled-button: rgba($light-text, 0.3), 96 | disabled-text: $light-disabled-text, 97 | elevation: black, 98 | hint-text: $light-disabled-text, 99 | secondary-text: $light-accent-text, 100 | accent-text: $light-accent-text, 101 | icon: $light-text, 102 | icons: $light-text, 103 | text: $light-text, 104 | slider-min: $light-text, 105 | slider-off: rgba($light-text, 0.3), 106 | slider-off-active: rgba($light-text, 0.3), 107 | ); 108 | 109 | // Background config 110 | // Light bg 111 | $light-background: #fafafa; 112 | $light-bg-darker-5: darken($light-background, 5%); 113 | $light-bg-darker-10: darken($light-background, 10%); 114 | $light-bg-darker-20: darken($light-background, 20%); 115 | $light-bg-darker-30: darken($light-background, 30%); 116 | $light-bg-lighter-5: lighten($light-background, 5%); 117 | $dark-bg-alpha-4: rgba(#2c2c2c, 0.04); 118 | $dark-bg-alpha-12: rgba(#2c2c2c, 0.12); 119 | 120 | $mat-light-theme-background: ( 121 | background: $light-background, 122 | status-bar: $light-bg-darker-20, 123 | app-bar: $light-bg-darker-5, 124 | hover: $dark-bg-alpha-4, 125 | card: $light-bg-lighter-5, 126 | dialog: $light-bg-lighter-5, 127 | disabled-button: $dark-bg-alpha-12, 128 | raised-button: $light-bg-lighter-5, 129 | focused-button: $dark-focused, 130 | selected-button: $light-bg-darker-20, 131 | selected-disabled-button: $light-bg-darker-30, 132 | disabled-button-toggle: $light-bg-darker-10, 133 | unselected-chip: $light-bg-darker-10, 134 | disabled-list-option: $light-bg-darker-10, 135 | ); 136 | 137 | // Dark bg 138 | $dark-background: #2c2c2c; 139 | $dark-bg-lighter-5: lighten($dark-background, 5%); 140 | $dark-bg-lighter-10: lighten($dark-background, 10%); 141 | $dark-bg-lighter-20: lighten($dark-background, 20%); 142 | $dark-bg-lighter-30: lighten($dark-background, 30%); 143 | $light-bg-alpha-4: rgba(#fafafa, 0.04); 144 | $light-bg-alpha-12: rgba(#fafafa, 0.12); 145 | 146 | // Background palette for dark themes. 147 | $mat-dark-theme-background: ( 148 | background: $dark-background, 149 | status-bar: $dark-bg-lighter-20, 150 | app-bar: $dark-bg-lighter-5, 151 | hover: $light-bg-alpha-4, 152 | card: $dark-bg-lighter-5, 153 | dialog: $dark-bg-lighter-5, 154 | disabled-button: $light-bg-alpha-12, 155 | raised-button: $dark-bg-lighter-5, 156 | focused-button: $light-focused, 157 | selected-button: $dark-bg-lighter-20, 158 | selected-disabled-button: $dark-bg-lighter-30, 159 | disabled-button-toggle: $dark-bg-lighter-10, 160 | unselected-chip: $dark-bg-lighter-20, 161 | disabled-list-option: $dark-bg-lighter-10, 162 | ); 163 | 164 | // Compute font config 165 | @include mat-core($fontConfig); 166 | 167 | // Theme Config 168 | 169 | $mat-primary: ( 170 | main: #105bd8, 171 | lighter: #b7cef3, 172 | darker: #0940c8, 173 | 200: #105bd8, // For slide toggle, 174 | contrast : ( 175 | main: $light-primary-text, 176 | lighter: $dark-primary-text, 177 | darker: $light-primary-text, 178 | ) 179 | ); 180 | $theme-primary: mat-palette($mat-primary, main, lighter, darker); 181 | 182 | $mat-accent: ( 183 | main: #02bfe7, 184 | lighter: #b3ecf8, 185 | darker: #01a9dd, 186 | 200: #02bfe7, // For slide toggle, 187 | contrast : ( 188 | main: $dark-primary-text, 189 | lighter: $dark-primary-text, 190 | darker: $light-primary-text, 191 | ) 192 | ); 193 | $theme-accent: mat-palette($mat-accent, main, lighter, darker); 194 | 195 | $mat-warn: ( 196 | main: #ff0000, 197 | lighter: #ffb3b3, 198 | darker: #ff0000, 199 | 200: #ff0000, // For slide toggle, 200 | contrast : ( 201 | main: $light-primary-text, 202 | lighter: $dark-primary-text, 203 | darker: $light-primary-text, 204 | ) 205 | ); 206 | $theme-warn: mat-palette($mat-warn, main, lighter, darker);; 207 | 208 | $theme: mat-light-theme($theme-primary, $theme-accent, $theme-warn); 209 | $altTheme: mat-dark-theme($theme-primary, $theme-accent, $theme-warn); 210 | 211 | // Theme Init 212 | @include angular-material-theme($theme); 213 | 214 | .theme-alternate { 215 | @include angular-material-theme($altTheme); 216 | } 217 | 218 | 219 | // Specific component overrides, pieces that are not in line with the general theming 220 | 221 | // Handle buttons appropriately, with respect to line-height 222 | .mat-raised-button, .mat-stroked-button, .mat-flat-button { 223 | padding: 0 1.15em; 224 | margin: 0 .65em; 225 | min-width: 3em; 226 | line-height: 36.4px 227 | } 228 | 229 | .mat-standard-chip { 230 | padding: .5em .85em; 231 | min-height: 2.5em; 232 | } 233 | ``` 234 |
235 | 236 |
Update the angular.json file to include src/theme.scss instead of the deeppurple-amber.css file (there are two references to the theme file, so update both). 237 | 238 | ```json 239 | "styles": [ 240 | "src/theme.scss", 241 | "src/styles.css" 242 | ], 243 | ``` 244 | -------------------------------------------------------------------------------- /steps/step_8.md: -------------------------------------------------------------------------------- 1 | * [Step 1](./step_1.md) 2 | * [Step 2](./step_2.md) 3 | * [Step 3](./step_3.md) 4 | * [Step 4](./step_4.md) 5 | * [Step 5](./step_5.md) 6 | * [Step 6](./step_6.md) 7 | * [Step 7](./step_7.md) 8 | * **Step 8 <-** 9 | 10 | ### [Step 8 setup demo](https://stackblitz.com/github/rnocc/blast-off-with-am/tree/step-7) 11 | ### **To view the completed demo,** run the master branch locally (to avoid an error on StackBlitz) 12 | 13 | ## Step 8 14 | 15 | In this step, you will add a floating action button (FAB) that will open a dialog to add a new astronaut 16 | 17 |
8a) Add a fab to the bottom right of the page 18 | 19 | `app.component.html` 20 | 21 | ```html 22 | 23 | ... 24 | 25 | 26 | ``` 27 | `app.component.css` 28 | 29 | ```css 30 | [mat-fab] { 31 | position: fixed; 32 | bottom: 50px; 33 | right: 50px; 34 | z-index: 10; 35 | } 36 | ``` 37 |
38 |
39 |
8b) Generate a new component called AddAstronautComponent and add the MatDialogModule to the app 40 | 41 | ```ts 42 | import { MatDialogModule } from '@angular/material/dialog'; 43 | 44 | @NgModule({ 45 | ... 46 | imports: [ 47 | ... 48 | MatDialogModule 49 | ], 50 | ... 51 | }) 52 | ``` 53 |
54 | 55 |
8b) Add the AddAstronautComponent to the entryComponents property of the module. This is needed because this component is added programmatically rather than being part of another component's template. 56 | 57 | ```ts 58 | @NgModule({ 59 | ... 60 | entryComponents: [AddAstronautComponent] 61 | ... 62 | }) 63 | ``` 64 |
65 | 66 |
8b) Add a click event to the FAB and call a method on the component to open the dialog 67 | 68 | 69 | `app.component.html` 70 | ```html 71 | 72 | ``` 73 | `app.component.ts` 74 | ```ts 75 | import { MatDialog } from '@angular/material/dialog'; 76 | import { AddAstronautComponent } from './add-astronaut/add-astronaut.component'; 77 | 78 | constructor(astronautService: AstronautService, private dialog: MatDialog) { 79 | 80 | addAstronaut() { 81 | this.dialog.open(AddAstronautComponent, { 82 | width: '500px', 83 | ariaLabel: 'Add an astronaut' 84 | }); 85 | } 86 | ``` 87 |
88 | 89 |
8b) Inject MatDialogRef<AddAstronautComponent> into AddAstronautComponent and the following method to allow the dialog to close when the user clicks away 90 | 91 | ```ts 92 | constructor(private dialogRef: MatDialogRef) { } 93 | 94 | close(): void { 95 | this.dialogRef.close(); 96 | } 97 | ``` 98 |
99 |
100 |
8c) Add a form that includes a few different Angular Material components. This is open-ended, but you can see the code that I wrote below. 101 | 102 | `app.module.ts` 103 | ```ts 104 | import { MatRadioModule } from '@angular/material/radio'; 105 | import { MatDatepickerModule } from '@angular/material/datepicker'; 106 | import { MatNativeDateModule } from '@angular/material'; 107 | import { MatSelectModule } from '@angular/material/select'; 108 | 109 | import { ReactiveFormsModule } from '@angular/forms'; 110 | 111 | @NgModule({ 112 | ... 113 | imports: [ 114 | ... 115 | MatRadioModule, 116 | MatDatepickerModule, 117 | MatNativeDateModule, 118 | MatSelectModule, 119 | 120 | ReactiveFormsModule, 121 | ], 122 | ... 123 | }) 124 | ``` 125 | `add-astronaut.component.ts` 126 | ```ts 127 | 128 | import { Validators, FormGroup, FormBuilder } from '@angular/forms'; 129 | import { AstronautService } from '../astronaut.service'; 130 | import { Observable } from 'rxjs'; 131 | import { Option } from '../types'; 132 | import { map } from 'rxjs/operators'; 133 | 134 | export class AddAstronautComponent { 135 | astronaut: FormGroup; 136 | undergraduateMajors: Observable; 137 | 138 | constructor( 139 | private dialogRef: MatDialogRef, 140 | fb: FormBuilder, 141 | astronautService: AstronautService 142 | ) { 143 | this.astronaut = fb.group({ 144 | firstName: ['', Validators.required], 145 | lastName: ['', Validators.required], 146 | middleInitial: ['', Validators.maxLength(1)], 147 | active: [true], 148 | birthdate: ['', Validators.required], 149 | undergraduateMajor: ['', Validators.required] 150 | }); 151 | this.undergraduateMajors = astronautService.filters.pipe( 152 | map(filters => filters.find(f => f.category === 'undergraduateMajor')), 153 | map(filter => filter.options), 154 | ); 155 | } 156 | 157 | saveAstronaut() { 158 | // Save to backend 159 | // Display new astronaut 160 | console.log(this.astronaut.value); 161 | this.close(); 162 | } 163 | 164 | } 165 | ``` 166 | 167 | `add-astronaut.component.html` 168 | ```html 169 |

Add astronaut

170 |
171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | Active 183 | 184 | 185 | Inactive 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | Undergraduate major 195 | 200 | 201 | 202 | 203 |
204 | ``` 205 | 206 | `add-astronaut.component.css` 207 | ```css 208 | mat-form-field { 209 | width: 100%; 210 | } 211 | 212 | mat-radio-button { 213 | margin-right: 1.25em; 214 | } 215 | 216 | mat-radio-button { 217 | padding-bottom: .75em; 218 | } 219 | 220 | mat-select { 221 | padding-bottom: 1.25em; 222 | } 223 | ``` 224 | 225 |
226 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rulesDirectory": [ 4 | "codelyzer" 5 | ], 6 | "rules": { 7 | "array-type": false, 8 | "arrow-parens": false, 9 | "deprecation": { 10 | "severity": "warn" 11 | }, 12 | "import-blacklist": [ 13 | true, 14 | "rxjs/Rx" 15 | ], 16 | "interface-name": false, 17 | "max-classes-per-file": false, 18 | "max-line-length": [ 19 | true, 20 | 140 21 | ], 22 | "member-access": false, 23 | "member-ordering": [ 24 | true, 25 | { 26 | "order": [ 27 | "static-field", 28 | "instance-field", 29 | "static-method", 30 | "instance-method" 31 | ] 32 | } 33 | ], 34 | "no-consecutive-blank-lines": false, 35 | "no-console": [ 36 | true, 37 | "debug", 38 | "info", 39 | "time", 40 | "timeEnd", 41 | "trace" 42 | ], 43 | "no-empty": false, 44 | "no-inferrable-types": [ 45 | true, 46 | "ignore-params" 47 | ], 48 | "no-non-null-assertion": true, 49 | "no-redundant-jsdoc": true, 50 | "no-switch-case-fall-through": true, 51 | "no-use-before-declare": true, 52 | "no-var-requires": false, 53 | "object-literal-key-quotes": [ 54 | true, 55 | "as-needed" 56 | ], 57 | "object-literal-sort-keys": false, 58 | "ordered-imports": false, 59 | "quotemark": [ 60 | true, 61 | "single" 62 | ], 63 | "trailing-comma": false, 64 | "no-output-on-prefix": true, 65 | "use-input-property-decorator": true, 66 | "use-output-property-decorator": true, 67 | "use-host-property-decorator": true, 68 | "no-input-rename": true, 69 | "no-output-rename": true, 70 | "use-life-cycle-interface": true, 71 | "use-pipe-transform-interface": true, 72 | "component-class-suffix": true, 73 | "directive-class-suffix": true 74 | } 75 | } 76 | --------------------------------------------------------------------------------