├── .browserslistrc ├── .editorconfig ├── .gitignore ├── INIT.md ├── LICENSE ├── README.md ├── angular.json ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.e2e.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── protractor.conf.js ├── proxy.json ├── server ├── db-data.ts ├── get-courses.route.ts ├── search-lessons.route.ts ├── server.ts └── server.tsconfig.json ├── src ├── _mixins.scss ├── app │ ├── about │ │ ├── about.component.css │ │ ├── about.component.html │ │ └── about.component.ts │ ├── app-routing.module.ts │ ├── app.component.css │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── course-dialog │ │ ├── course-dialog.component.css │ │ ├── course-dialog.component.html │ │ └── course-dialog.component.ts │ ├── course │ │ ├── course.component.html │ │ ├── course.component.scss │ │ └── course.component.ts │ ├── courses-card-list │ │ ├── courses-card-list.component.css │ │ ├── courses-card-list.component.html │ │ └── courses-card-list.component.ts │ ├── create-course │ │ ├── create-course-step-1 │ │ │ ├── create-course-step-1.component.html │ │ │ ├── create-course-step-1.component.scss │ │ │ └── create-course-step-1.component.ts │ │ ├── create-course-step-2 │ │ │ ├── create-course-step-2.component.html │ │ │ ├── create-course-step-2.component.scss │ │ │ └── create-course-step-2.component.ts │ │ ├── create-course.component.html │ │ ├── create-course.component.scss │ │ └── create-course.component.ts │ ├── drag-drop │ │ ├── drag-drop.component.html │ │ ├── drag-drop.component.scss │ │ └── drag-drop.component.ts │ ├── home │ │ ├── home.component.html │ │ ├── home.component.scss │ │ └── home.component.ts │ ├── model │ │ ├── course.ts │ │ └── lesson.ts │ ├── services │ │ ├── course.resolver.ts │ │ └── courses.service.ts │ ├── tree-demo │ │ ├── tree-demo.component.html │ │ ├── tree-demo.component.scss │ │ └── tree-demo.component.ts │ └── virtual-scrolling │ │ ├── virtual-scrolling.component.html │ │ ├── virtual-scrolling.component.scss │ │ └── virtual-scrolling.component.ts ├── assets │ └── .gitkeep ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.scss ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── typings.d.ts ├── tsconfig.json └── tslint.json /.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://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 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.angular/cache 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | testem.log 35 | /typings 36 | 37 | # e2e 38 | /e2e/*.js 39 | /e2e/*.map 40 | 41 | # System Files 42 | .DS_Store 43 | Thumbs.db 44 | -------------------------------------------------------------------------------- /INIT.md: -------------------------------------------------------------------------------- 1 | 2 | # Initializing a clean Angular Material Project 3 | 4 | These are the commands and steps needed to scaffold a new Angular Material project from scratch, 5 | from an empty folder. 6 | 7 | Please make sure to have the latest CLI, and at least NPM 5. 8 | 9 | When is doubt, its recommended to update to the latest version of node using a node versioning tool 10 | such as for example [nave](https://github.com/isaacs/nave) or [nvm-windows](https://github.com/coreybutler/nvm-windows). 11 | 12 | # Step 1 - Scaffold a clean project using the Angular CLI 13 | 14 | With a CLI version 1.5 or above, let's scaffold a new project with routing: 15 | 16 | ng new angular-material-hello-world --routing 17 | 18 | # Step 2 - Installing Angular Material dependencies 19 | 20 | Next, let's install these dependencies: 21 | 22 | npm install @angular/material @angular/cdk @angular/animations hammerjs 23 | 24 | # Step 3 - Adding Google Material Icons Font 25 | 26 | Let's add this to our index.html: 27 | 28 | 29 | 30 | # Step 4 - choosing a Theme 31 | 32 | Before starting to import components, let's choose a widget theme, have a look at the themes available 33 | 34 | inside `node_modules/@angular/material/prebuild-themes`. 35 | 36 | We can for example use the Indigo Pink theme by adding this line to our styles.css file: 37 | 38 | @import "~@angular/material/prebuilt-themes/indigo-pink.css"; 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Angular University 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Angular Material Course 3 | 4 | This repository contains the code of the [Angular Material In Depth](https://angular-university.io/course/angular-material-course) video course. 5 | 6 | This course repository is updated to Angular 19: 7 | 8 | ![Angular Material In Depth](https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-material-course-1.jpg) 9 | 10 | You can find the starting point of the course in the [1-start branch](https://github.com/angular-university/angular-material-course/tree/1-start). 11 | 12 | This master branch contains the *final version of the course code*, that you can use as a reference if you choose to code along. 13 | 14 | # Installation pre-requisites 15 | 16 | IMPORTANT: Please use Node 18 LTS (Long Term Support version). 17 | 18 | # Installing the Angular CLI 19 | 20 | With the following command the angular-cli will be installed globally in your machine: 21 | 22 | npm install -g @angular/cli 23 | 24 | # How To install this repository 25 | 26 | We can install the master branch using the following commands: 27 | 28 | git clone https://github.com/angular-university/angular-material-course.git 29 | 30 | cd angular-material-course 31 | npm ci 32 | 33 | Note: **We recommend using npm ci, instead of npm install**. This will ensure that you use the exact dependency versions set on package-lock.json, unlike npm install which might potentially change those versions. 34 | 35 | # To Run the Development Backend Server 36 | 37 | Our Angular frontend connects to a simple Node server, running also in your local development machine. 38 | 39 | We can start the sample application backend with the following command: 40 | 41 | npm run server 42 | 43 | # To run the Development UI Server 44 | 45 | Once the backend server is up and running, we can now run our frontend server. 46 | 47 | To run the frontend part of our code, we will use the Angular CLI: 48 | 49 | npm start 50 | 51 | The application is visible at port 4200: [http://localhost:4200](http://localhost:4200) 52 | 53 | Note: **make sure to use command npm start and not ng serve, as npm start adds a couple extra options that are needed for our project ** 54 | 55 | # Other Courses 56 | 57 | # Other Courses 58 | # Modern Angular With Signals 59 | 60 | If you are looking for the [Modern Angular With Signals Course](https://angular-university.io/course/angular-signals-course), the repo with the full code can be found here: 61 | 62 | ![Modern Angular With Signals Course](https://d3vigmphadbn9b.cloudfront.net/course-images/large-images/angular-signals-course.jpg) 63 | 64 | # Angular Forms In Depth 65 | 66 | If you are looking for the [Angular Forms In Depth](https://angular-university.io/course/angular-forms-course) course, the repo with the full code can be found here: 67 | 68 | ![Angular Forms In Depth](https://angular-university.s3-us-west-1.amazonaws.com/course-images/angular-forms-course-small.jpg) 69 | 70 | # Angular Router In Depth 71 | 72 | If you are looking for the [Angular Router In Depth](https://angular-university.io/course/angular-router-course) course, the repo with the full code can be found here: 73 | 74 | ![Angular Router In Depth](https://angular-university.s3-us-west-1.amazonaws.com/course-images/angular-router-course.jpg) 75 | 76 | # NgRx (with NgRx Data) - The Complete Guide 77 | 78 | If you are looking for the [Ngrx (with NgRx Data) - The Complete Guide](https://angular-university.io/course/ngrx-course), the repo with the full code can be found here: 79 | 80 | ![Ngrx (with NgRx Data) - The Complete Guide](https://angular-university.s3-us-west-1.amazonaws.com/course-images/ngrx-v2.png) 81 | 82 | 83 | # Angular Core Deep Dive Course 84 | 85 | If you are looking for the [Angular Core Deep Dive Course](https://angular-university.io/course/angular-course), the repo with the full code can be found here: 86 | 87 | ![Angular Core Deep Dive](https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-core-in-depth-small.png) 88 | 89 | # RxJs In Practice 90 | 91 | If you are looking for the [RxJs In Practice](https://angular-university.io/course/rxjs-course), the repo with the full code can be found here: 92 | 93 | ![RxJs In Practice Course](https://s3-us-west-1.amazonaws.com/angular-university/course-images/rxjs-in-practice-course.png) 94 | 95 | # NestJs In Practice (with MongoDB) 96 | 97 | If you are looking for the [NestJs In Practice Course](https://angular-university.io/course/nestjs-course), the repo with the full code can be found here: 98 | 99 | ![NestJs In Practice Course](https://angular-university.s3-us-west-1.amazonaws.com/course-images/nestjs-v2.png) 100 | 101 | # Angular Testing Course 102 | 103 | If you are looking for the [Angular Testing Course](https://angular-university.io/course/angular-testing-course), the repo with the full code can be found here: 104 | 105 | ![Angular Testing Course](https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-testing-small.png) 106 | 107 | # Serverless Angular with Firebase Course 108 | 109 | If you are looking for the [Serverless Angular with Firebase Course](https://angular-university.io/course/firebase-course), the repo with the full code can be found here: 110 | 111 | ![Serverless Angular with Firebase Course](https://s3-us-west-1.amazonaws.com/angular-university/course-images/serverless-angular-small.png) 112 | 113 | # Angular Universal Course 114 | 115 | If you are looking for the [Angular Universal Course](https://angular-university.io/course/angular-universal-course), the repo with the full code can be found here: 116 | 117 | ![Angular Universal Course](https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-universal-small.png) 118 | 119 | # Angular PWA Course 120 | 121 | If you are looking for the [Angular PWA Course](https://angular-university.io/course/angular-pwa-course), the repo with the full code can be found here: 122 | 123 | ![Angular PWA Course - Build the future of the Web Today](https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-pwa-course.png) 124 | 125 | # Angular Security Masterclass 126 | 127 | If you are looking for the [Angular Security Masterclass](https://angular-university.io/course/angular-security-course), the repo with the full code can be found here: 128 | 129 | [Angular Security Masterclass](https://github.com/angular-university/angular-security-course). 130 | 131 | ![Angular Security Masterclass](https://s3-us-west-1.amazonaws.com/angular-university/course-images/security-cover-small-v2.png) 132 | 133 | # Angular Advanced Library Laboratory Course 134 | 135 | If you are looking for the Angular Advanced Course, the repo with the full code can be found here: 136 | 137 | [Angular Advanced Library Laboratory Course: Build Your Own Library](https://angular-university.io/course/angular-advanced-course). 138 | 139 | ![Angular Advanced Library Laboratory Course: Build Your Own Library](https://angular-academy.s3.amazonaws.com/thumbnails/advanced_angular-small-v3.png) 140 | 141 | 142 | ## RxJs and Reactive Patterns Angular Architecture Course 143 | 144 | If you are looking for the RxJs and Reactive Patterns Angular Architecture Course code, the repo with the full code can be found here: 145 | 146 | [RxJs and Reactive Patterns Angular Architecture Course](https://angular-university.io/course/reactive-angular-architecture-course) 147 | 148 | ![RxJs and Reactive Patterns Angular Architecture Course](https://s3-us-west-1.amazonaws.com/angular-academy/blog/images/rxjs-reactive-patterns-small.png) 149 | 150 | 151 | ## Complete Typescript Course - Build A REST API 152 | 153 | If you are looking for the Complete Typescript 2 Course - Build a REST API, the repo with the full code can be found here: 154 | 155 | [https://angular-university.io/course/typescript-2-tutorial](https://github.com/angular-university/complete-typescript-course) 156 | 157 | [Github repo for this course](https://github.com/angular-university/complete-typescript-course) 158 | 159 | ![Complete Typescript Course](https://angular-academy.s3.amazonaws.com/thumbnails/typescript-2-small.png) 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-material-course": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "architect": { 11 | "build": { 12 | "builder": "@angular-devkit/build-angular:application", 13 | "options": { 14 | "outputPath": { 15 | "base": "dist" 16 | }, 17 | "index": "src/index.html", 18 | "tsConfig": "src/tsconfig.app.json", 19 | "polyfills": [ 20 | "src/polyfills.ts" 21 | ], 22 | "assets": [ 23 | "src/assets", 24 | "src/favicon.ico" 25 | ], 26 | "styles": [ 27 | "src/styles.scss" 28 | ], 29 | "scripts": [], 30 | "extractLicenses": false, 31 | "sourceMap": true, 32 | "optimization": false, 33 | "namedChunks": true, 34 | "browser": "src/main.ts" 35 | }, 36 | "configurations": { 37 | "production": { 38 | "budgets": [ 39 | { 40 | "type": "anyComponentStyle", 41 | "maximumWarning": "6kb" 42 | } 43 | ], 44 | "optimization": true, 45 | "outputHashing": "all", 46 | "sourceMap": false, 47 | "namedChunks": false, 48 | "extractLicenses": true, 49 | "fileReplacements": [ 50 | { 51 | "replace": "src/environments/environment.ts", 52 | "with": "src/environments/environment.prod.ts" 53 | } 54 | ] 55 | } 56 | } 57 | }, 58 | "serve": { 59 | "builder": "@angular-devkit/build-angular:dev-server", 60 | "options": { 61 | "buildTarget": "angular-material-course:build" 62 | }, 63 | "configurations": { 64 | "production": { 65 | "buildTarget": "angular-material-course:build:production" 66 | } 67 | } 68 | }, 69 | "extract-i18n": { 70 | "builder": "@angular-devkit/build-angular:extract-i18n", 71 | "options": { 72 | "buildTarget": "angular-material-course:build" 73 | } 74 | }, 75 | "test": { 76 | "builder": "@angular-devkit/build-angular:karma", 77 | "options": { 78 | "main": "src/test.ts", 79 | "karmaConfig": "./karma.conf.js", 80 | "polyfills": "src/polyfills.ts", 81 | "tsConfig": "src/tsconfig.spec.json", 82 | "scripts": [], 83 | "styles": [ 84 | "src/styles.scss" 85 | ], 86 | "assets": [ 87 | "src/assets", 88 | "src/favicon.ico" 89 | ] 90 | } 91 | } 92 | } 93 | }, 94 | "angular-material-course-e2e": { 95 | "root": "", 96 | "sourceRoot": "", 97 | "projectType": "application", 98 | "architect": { 99 | "e2e": { 100 | "builder": "@angular-devkit/build-angular:protractor", 101 | "options": { 102 | "protractorConfig": "./protractor.conf.js", 103 | "devServerTarget": "angular-material-course:serve" 104 | } 105 | } 106 | } 107 | } 108 | }, 109 | "schematics": { 110 | "@schematics/angular:component": { 111 | "style": "scss" 112 | }, 113 | "@schematics/angular:directive": { 114 | "prefix": "" 115 | } 116 | }, 117 | "cli": { 118 | "analytics": "94ed0c6d-944a-4adc-89a1-2bc801038d0b" 119 | } 120 | } -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('angular-material-course App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to app!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /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'), reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | angularCli: { 23 | environment: 'dev' 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | singleRun: false 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-material-course", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve --proxy-config ./proxy.json", 8 | "server": "ts-node -P ./server/server.tsconfig.json ./server/server.ts", 9 | "build": "ng build", 10 | "test": "ng test", 11 | "lint": "ng lint", 12 | "e2e": "ng e2e" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/animations": "19.0.0", 17 | "@angular/cdk": "^19.0.0", 18 | "@angular/common": "19.0.0", 19 | "@angular/compiler": "19.0.0", 20 | "@angular/core": "19.0.0", 21 | "@angular/forms": "19.0.0", 22 | "@angular/material": "^19.0.0", 23 | "@angular/material-moment-adapter": "19.0.0", 24 | "@angular/platform-browser": "19.0.0", 25 | "@angular/platform-browser-dynamic": "19.0.0", 26 | "@angular/router": "19.0.0", 27 | "core-js": "^2.4.1", 28 | "cors": "^2.8.5", 29 | "express": "^4.16.2", 30 | "moment": "^2.22.2", 31 | "rxjs": "6.5.4", 32 | "tslib": "^2.0.0", 33 | "zone.js": "~0.15.0" 34 | }, 35 | "devDependencies": { 36 | "@angular-devkit/build-angular": "^19.0.2", 37 | "@angular-devkit/schematics": "^19.0.2", 38 | "@angular/cli": "^19.0.2", 39 | "@angular/compiler-cli": "19.0.0", 40 | "@angular/language-service": "19.0.0", 41 | "@types/express": "^4.0.39", 42 | "@types/jasmine": "~3.8.0", 43 | "@types/jasminewd2": "~2.0.2", 44 | "@types/node": "^12.11.1", 45 | "jasmine-core": "~3.8.0", 46 | "jasmine-spec-reporter": "~5.0.0", 47 | "karma": "~6.3.2", 48 | "karma-chrome-launcher": "~3.1.0", 49 | "karma-cli": "~1.0.1", 50 | "karma-coverage-istanbul-reporter": "~3.0.2", 51 | "karma-jasmine": "~4.0.0", 52 | "karma-jasmine-html-reporter": "^1.5.0", 53 | "ts-node": "~3.2.0", 54 | "typescript": "5.6.3" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /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 | './e2e/**/*.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: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /proxy.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "http://localhost:9000", 4 | "secure": false 5 | } 6 | } -------------------------------------------------------------------------------- /server/db-data.ts: -------------------------------------------------------------------------------- 1 | export const COURSES: any = { 2 | 3 | 11: { 4 | id: 11, 5 | description: 'Angular Material Course', 6 | longDescription: 'Build Applications with the official Angular UI Widget Library', 7 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-material-course-1.jpg', 8 | category: 'BEGINNER', 9 | seqNo: 0, 10 | url: 'angular-material-course', 11 | price: 50, 12 | lessonsCount: 11, 13 | }, 14 | 15 | 19: { 16 | id: 19, 17 | description: 'Angular Forms In Depth', 18 | longDescription: 'Build complex enterprise data forms with the powerful Angular Forms module', 19 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/angular-forms-course-small.jpg', 20 | courseListIcon: 'https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png', 21 | category: 'BEGINNER', 22 | lessonsCount: 10, 23 | seqNo: 1, 24 | url: 'angular-forms-course', 25 | price: 50 26 | }, 27 | 28 | 29 | 18: { 30 | id: 18, 31 | description: 'Angular Router In Depth', 32 | longDescription: 'Build large-scale Single Page Applications with the powerful Angular Router', 33 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/angular-router-course.jpg', 34 | courseListIcon: 'https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png', 35 | category: 'BEGINNER', 36 | lessonsCount: 10, 37 | seqNo: 2, 38 | url: 'angular-router-course', 39 | price: 50 40 | }, 41 | 42 | 17: { 43 | id: 17, 44 | description: 'Reactive Angular Course', 45 | longDescription: 'How to build Angular applications in Reactive style using plain RxJs - Patterns and Anti-Patterns', 46 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/reactive-angular-course.jpg', 47 | courseListIcon: 'https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png', 48 | category: 'BEGINNER', 49 | lessonsCount: 10, 50 | seqNo: 3, 51 | url: 'reactive-angular-course', 52 | price: 50 53 | 54 | }, 55 | 3: { 56 | id: 3, 57 | description: 'RxJs In Practice Course', 58 | longDescription: 'Understand the RxJs Observable pattern, learn the RxJs Operators via practical examples', 59 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/rxjs-in-practice-course.png', 60 | courseListIcon: 'https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png', 61 | category: 'BEGINNER', 62 | lessonsCount: 10, 63 | seqNo: 4, 64 | url: 'rxjs-course', 65 | price: 50 66 | }, 67 | 68 | 4: { 69 | id: 4, 70 | description: 'NgRx (with NgRx Data) - The Complete Guide', 71 | longDescription: 'Learn the modern Ngrx Ecosystem, including NgRx Data, Store, Effects, Router Store, Ngrx Entity, and Dev Tools.', 72 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/ngrx-v2.png', 73 | category: 'BEGINNER', 74 | lessonsCount: 10, 75 | seqNo: 5, 76 | url: 'ngrx-course', 77 | promo: false, 78 | price: 50 79 | }, 80 | 81 | 82 | 2: { 83 | id: 2, 84 | description: 'Angular Core Deep Dive', 85 | longDescription: 'A detailed walk-through of the most important part of Angular - the Core and Common modules', 86 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-core-in-depth-small.png', 87 | lessonsCount: 10, 88 | category: 'BEGINNER', 89 | seqNo: 6, 90 | url: 'angular-core-course', 91 | price: 50 92 | }, 93 | 94 | 95 | 5: { 96 | id: 5, 97 | 98 | description: 'Angular for Beginners', 99 | longDescription: 'Establish a solid layer of fundamentals, learn what\'s under the hood of Angular', 100 | iconUrl: 'https://angular-academy.s3.amazonaws.com/thumbnails/angular2-for-beginners-small-v2.png', 101 | courseListIcon: 'https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png', 102 | category: 'BEGINNER', 103 | lessonsCount: 10, 104 | seqNo: 7, 105 | url: 'angular-for-beginners', 106 | price: 50 107 | }, 108 | 109 | 12: { 110 | id: 12, 111 | description: 'Angular Testing Course', 112 | longDescription: 'In-depth guide to Unit Testing and E2E Testing of Angular Applications', 113 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-testing-small.png', 114 | category: 'BEGINNER', 115 | seqNo: 8, 116 | url: 'angular-testing-course', 117 | lessonsCount: 10, 118 | promo: false, 119 | price: 50 120 | }, 121 | 122 | 123 | 1: { 124 | id: 1, 125 | description: 'Serverless Angular with Firebase Course', 126 | longDescription: 'Serveless Angular with Firestore, Firebase Storage & Hosting, Firebase Cloud Functions & AngularFire', 127 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/serverless-angular-small.png', 128 | lessonsCount: 10, 129 | category: 'BEGINNER', 130 | seqNo: 9, 131 | url: 'serverless-angular', 132 | price: 50 133 | }, 134 | 135 | 16: { 136 | id: 16, 137 | description: 'Stripe Payments In Practice', 138 | longDescription: 'Build your own ecommerce store & membership website with Firebase, Stripe and Express', 139 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/stripe-course.jpg', 140 | lessonsCount: 10, 141 | category: 'BEGINNER', 142 | seqNo: 10, 143 | url: 'stripe-course', 144 | price: 50 145 | }, 146 | 147 | 148 | 14: { 149 | id: 14, 150 | description: 'NestJs In Practice (with MongoDB)', 151 | longDescription: 'Build a modern REST backend using Typescript, MongoDB and the familiar Angular API.', 152 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/nestjs-v2.png', 153 | category: 'BEGINNER', 154 | lessonsCount: 10, 155 | seqNo: 11, 156 | url: 'nestjs-course', 157 | promo: false, 158 | price: 50 159 | }, 160 | 161 | 162 | 6: { 163 | id: 6, 164 | description: 'Angular Security Course - Web Security Fundamentals', 165 | longDescription: 'Learn Web Security Fundamentals and apply them to defend an Angular / Node Application from multiple types of attacks.', 166 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/security-cover-small-v2.png', 167 | courseListIcon: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/lock-v2.png', 168 | category: 'ADVANCED', 169 | lessonsCount: 11, 170 | seqNo: 12, 171 | url: 'angular-security-course', 172 | price: 50 173 | }, 174 | 175 | 7: { 176 | id: 7, 177 | description: 'Angular PWA - Progressive Web Apps Course', 178 | longDescription: 'Learn Angular Progressive Web Applications, build the future of the Web Today.', 179 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-pwa-course.png', 180 | courseListIcon: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/alien.png', 181 | category: 'ADVANCED', 182 | lessonsCount: 8, 183 | seqNo: 14, 184 | url: 'angular-pwa-course', 185 | price: 50 186 | }, 187 | 188 | 8: { 189 | id: 8, 190 | description: 'Angular Advanced Library Laboratory: Build Your Own Library', 191 | longDescription: 'Learn Advanced Angular functionality typically used in Library Development. Advanced Components, Directives, Testing, Npm', 192 | iconUrl: 'https://angular-academy.s3.amazonaws.com/thumbnails/advanced_angular-small-v3.png', 193 | courseListIcon: 'https://angular-academy.s3.amazonaws.com/thumbnails/angular-advanced-lesson-icon.png', 194 | category: 'ADVANCED', 195 | seqNo: 15, 196 | url: 'angular-advanced-course', 197 | price: 50 198 | }, 199 | 200 | 9: { 201 | id: 9, 202 | description: 'The Complete Typescript Course', 203 | longDescription: 'Complete Guide to Typescript From Scratch: Learn the language in-depth and use it to build a Node REST API.', 204 | iconUrl: 'https://angular-academy.s3.amazonaws.com/thumbnails/typescript-2-small.png', 205 | courseListIcon: 'https://angular-academy.s3.amazonaws.com/thumbnails/typescript-2-lesson.png', 206 | category: 'BEGINNER', 207 | seqNo: 16, 208 | url: 'typescript-course', 209 | price: 50 210 | }, 211 | 212 | }; 213 | 214 | export const LESSONS = { 215 | 216 | 1: { 217 | id: 1, 218 | 'description': 'Angular Tutorial For Beginners - Build Your First App - Hello World Step By Step', 219 | 'duration': '4:17', 220 | 'seqNo': 1, 221 | courseId: 5 222 | }, 223 | 2: { 224 | id: 2, 225 | 'description': 'Building Your First Component - Component Composition', 226 | 'duration': '2:07', 227 | 'seqNo': 2, 228 | courseId: 5 229 | }, 230 | 3: { 231 | id: 3, 232 | 'description': 'Component @Input - How To Pass Input Data To an Component', 233 | 'duration': '2:33', 234 | 'seqNo': 3, 235 | courseId: 5 236 | }, 237 | 4: { 238 | id: 4, 239 | 'description': ' Component Events - Using @Output to create custom events', 240 | 'duration': '4:44', 241 | 'seqNo': 4, 242 | courseId: 5 243 | }, 244 | 5: { 245 | id: 5, 246 | 'description': ' Component Templates - Inline Vs External', 247 | 'duration': '2:55', 248 | 'seqNo': 5, 249 | courseId: 5 250 | }, 251 | 6: { 252 | id: 6, 253 | 'description': 'Styling Components - Learn About Component Style Isolation', 254 | 'duration': '3:27', 255 | 'seqNo': 6, 256 | courseId: 5 257 | }, 258 | 7: { 259 | id: 7, 260 | 'description': ' Component Interaction - Extended Components Example', 261 | 'duration': '9:22', 262 | 'seqNo': 7, 263 | courseId: 5 264 | }, 265 | 8: { 266 | id: 8, 267 | 'description': ' Components Tutorial For Beginners - Components Exercise !', 268 | 'duration': '1:26', 269 | 'seqNo': 8, 270 | courseId: 5 271 | }, 272 | 9: { 273 | id: 9, 274 | 'description': ' Components Tutorial For Beginners - Components Exercise Solution Inside', 275 | 'duration': '2:08', 276 | 'seqNo': 9, 277 | courseId: 5 278 | }, 279 | 10: { 280 | id: 10, 281 | 'description': ' Directives - Inputs, Output Event Emitters and How To Export Template References', 282 | 'duration': '4:01', 283 | 'seqNo': 10, 284 | courseId: 5 285 | }, 286 | 287 | 288 | // Security Course 289 | 11: { 290 | id: 11, 291 | 'description': 'Course Helicopter View', 292 | 'duration': '08:19', 293 | 'seqNo': 1, 294 | courseId: 6 295 | }, 296 | 297 | 12: { 298 | id: 12, 299 | 'description': 'Installing Git, Node, NPM and Choosing an IDE', 300 | 'duration': '04:17', 301 | 'seqNo': 2, 302 | courseId: 6 303 | }, 304 | 305 | 13: { 306 | id: 13, 307 | 'description': 'Installing The Lessons Code - Learn Why Its Essential To Use NPM 5', 308 | 'duration': '06:05', 309 | 'seqNo': 3, 310 | courseId: 6 311 | }, 312 | 313 | 14: { 314 | id: 14, 315 | 'description': 'How To Run Node In TypeScript With Hot Reloading', 316 | 'duration': '03:57', 317 | 'seqNo': 4, 318 | courseId: 6 319 | }, 320 | 321 | 15: { 322 | id: 15, 323 | 'description': 'Guided Tour Of The Sample Application', 324 | 'duration': '06:00', 325 | 'seqNo': 5, 326 | courseId: 6 327 | }, 328 | 16: { 329 | id: 16, 330 | 'description': 'Client Side Authentication Service - API Design', 331 | 'duration': '04:53', 332 | 'seqNo': 6, 333 | courseId: 6 334 | }, 335 | 17: { 336 | id: 17, 337 | 'description': 'Client Authentication Service - Design and Implementation', 338 | 'duration': '09:14', 339 | 'seqNo': 7, 340 | courseId: 6 341 | }, 342 | 18: { 343 | id: 18, 344 | 'description': 'The New Angular HTTP Client - Doing a POST Call To The Server', 345 | 'duration': '06:08', 346 | 'seqNo': 8, 347 | courseId: 6 348 | }, 349 | 19: { 350 | id: 19, 351 | 'description': 'User Sign Up Server-Side Implementation in Express', 352 | 'duration': '08:50', 353 | 'seqNo': 9, 354 | courseId: 6 355 | }, 356 | 20: { 357 | id: 20, 358 | 'description': 'Introduction To Cryptographic Hashes - A Running Demo', 359 | 'duration': '05:46', 360 | 'seqNo': 10, 361 | courseId: 6 362 | }, 363 | 21: { 364 | id: 21, 365 | 'description': 'Some Interesting Properties Of Hashing Functions - Validating Passwords', 366 | 'duration': '06:31', 367 | 'seqNo': 11, 368 | courseId: 6 369 | }, 370 | 371 | 372 | // PWA course 373 | 374 | 22: { 375 | id: 22, 376 | 'description': 'Course Kick-Off - Install Node, NPM, IDE And Service Workers Section Code', 377 | 'duration': '07:19', 378 | 'seqNo': 1, 379 | courseId: 7 380 | }, 381 | 23: { 382 | id: 23, 383 | 'description': 'Service Workers In a Nutshell - Service Worker Registration', 384 | 'duration': '6:59', 385 | 'seqNo': 2, 386 | courseId: 7 387 | }, 388 | 24: { 389 | id: 24, 390 | 'description': 'Service Workers Hello World - Lifecycle Part 1 and PWA Chrome Dev Tools', 391 | 'duration': '7:28', 392 | 'seqNo': 3, 393 | courseId: 7 394 | }, 395 | 25: { 396 | id: 25, 397 | 'description': 'Service Workers and Application Versioning - Install & Activate Lifecycle Phases', 398 | 'duration': '10:17', 399 | 'seqNo': 4, 400 | courseId: 7 401 | }, 402 | 403 | 26: { 404 | id: 26, 405 | 'description': 'Downloading The Offline Page - The Service Worker Installation Phase', 406 | 'duration': '09:50', 407 | 'seqNo': 5, 408 | courseId: 7 409 | }, 410 | 27: { 411 | id: 27, 412 | 'description': 'Introduction to the Cache Storage PWA API', 413 | 'duration': '04:44', 414 | 'seqNo': 6, 415 | courseId: 7 416 | }, 417 | 28: { 418 | id: 28, 419 | 'description': 'View Service Workers HTTP Interception Features In Action', 420 | 'duration': '06:07', 421 | 'seqNo': 7, 422 | courseId: 7 423 | }, 424 | 29: { 425 | id: 29, 426 | 'description': 'Service Workers Error Handling - Serving The Offline Page', 427 | 'duration': '5:38', 428 | 'seqNo': 8, 429 | courseId: 7 430 | }, 431 | 432 | // Serverless Angular with Firebase Course 433 | 434 | 30: { 435 | id: 30, 436 | description: 'Development Environment Setup', 437 | 'duration': '5:38', 438 | 'seqNo': 1, 439 | courseId: 1 440 | }, 441 | 442 | 31: { 443 | id: 31, 444 | description: 'Introduction to the Firebase Ecosystem', 445 | 'duration': '5:12', 446 | 'seqNo': 2, 447 | courseId: 1 448 | }, 449 | 450 | 32: { 451 | id: 32, 452 | description: 'Importing Data into Firestore', 453 | 'duration': '4:07', 454 | 'seqNo': 3, 455 | courseId: 1 456 | }, 457 | 458 | 33: { 459 | id: 33, 460 | description: 'Firestore Documents in Detail', 461 | 'duration': '7:32', 462 | 'seqNo': 4, 463 | courseId: 1 464 | }, 465 | 466 | 34: { 467 | id: 34, 468 | description: 'Firestore Collections in Detail', 469 | 'duration': '6:28', 470 | 'seqNo': 5, 471 | courseId: 1 472 | }, 473 | 474 | 35: { 475 | id: 35, 476 | description: 'Firestore Unique Identifiers', 477 | 'duration': '4:38', 478 | 'seqNo': 6, 479 | courseId: 1 480 | }, 481 | 482 | 36: { 483 | id: 36, 484 | description: 'Querying Firestore Collections', 485 | 'duration': '7:54', 486 | 'seqNo': 7, 487 | courseId: 1 488 | }, 489 | 490 | 37: { 491 | id: 37, 492 | description: 'Firebase Security Rules In Detail', 493 | 'duration': '5:31', 494 | 'seqNo': 8, 495 | courseId: 1 496 | }, 497 | 498 | 38: { 499 | id: 38, 500 | description: 'Firebase Cloud Functions In Detail', 501 | 'duration': '8:19', 502 | 'seqNo': 9, 503 | courseId: 1 504 | }, 505 | 506 | 39: { 507 | id: 39, 508 | description: 'Firebase Storage In Detail', 509 | 'duration': '7:05', 510 | 'seqNo': 10, 511 | courseId: 1 512 | }, 513 | 514 | 515 | // Angular Testing Course 516 | 517 | 40: { 518 | id: 40, 519 | description: 'Angular Testing Course - Helicopter View', 520 | 'duration': '5:38', 521 | 'seqNo': 1, 522 | courseId: 12 523 | }, 524 | 525 | 41: { 526 | id: 41, 527 | description: 'Setting Up the Development Environment', 528 | 'duration': '5:12', 529 | 'seqNo': 2, 530 | courseId: 12 531 | }, 532 | 533 | 42: { 534 | id: 42, 535 | description: 'Introduction to Jasmine, Spies and specs', 536 | 'duration': '4:07', 537 | 'seqNo': 3, 538 | courseId: 12 539 | }, 540 | 541 | 43: { 542 | id: 43, 543 | description: 'Introduction to Service Testing', 544 | 'duration': '7:32', 545 | 'seqNo': 4, 546 | courseId: 12 547 | }, 548 | 549 | 44: { 550 | id: 44, 551 | description: 'Settting up the Angular TestBed', 552 | 'duration': '6:28', 553 | 'seqNo': 5, 554 | courseId: 12 555 | }, 556 | 557 | 45: { 558 | id: 45, 559 | description: 'Mocking Angular HTTP requests', 560 | 'duration': '4:38', 561 | 'seqNo': 6, 562 | courseId: 12 563 | }, 564 | 565 | 46: { 566 | id: 46, 567 | description: 'Simulating Failing HTTP Requests', 568 | 'duration': '7:54', 569 | 'seqNo': 7, 570 | courseId: 12 571 | }, 572 | 573 | 47: { 574 | id: 47, 575 | description: 'Introduction to Angular Component Testing', 576 | 'duration': '5:31', 577 | 'seqNo': 8, 578 | courseId: 12 579 | }, 580 | 581 | 48: { 582 | id: 48, 583 | description: 'Testing Angular Components without the DOM', 584 | 'duration': '8:19', 585 | 'seqNo': 9, 586 | courseId: 12 587 | }, 588 | 589 | 49: { 590 | id: 49, 591 | description: 'Testing Angular Components with the DOM', 592 | 'duration': '7:05', 593 | 'seqNo': 10, 594 | courseId: 12 595 | }, 596 | 597 | 598 | // Ngrx Course 599 | 50: { 600 | id: 50, 601 | 'description': 'Welcome to the Angular Ngrx Course', 602 | 'duration': '6:53', 603 | 'seqNo': 1, 604 | courseId: 4 605 | 606 | }, 607 | 51: { 608 | id: 51, 609 | 'description': 'The Angular Ngrx Architecture Course - Helicopter View', 610 | 'duration': '5:52', 611 | 'seqNo': 2, 612 | courseId: 4 613 | }, 614 | 52: { 615 | id: 52, 616 | 'description': 'The Origins of Flux - Understanding the Famous Facebook Bug Problem', 617 | 'duration': '8:17', 618 | 'seqNo': 3, 619 | courseId: 4 620 | }, 621 | 53: { 622 | id: 53, 623 | 'description': 'Custom Global Events - Why Don\'t They Scale In Complexity?', 624 | 'duration': '7:47', 625 | 'seqNo': 4, 626 | courseId: 4 627 | }, 628 | 54: { 629 | id: 54, 630 | 'description': 'The Flux Architecture - How Does it Solve Facebook Counter Problem?', 631 | 'duration': '9:22', 632 | 'seqNo': 5, 633 | courseId: 4 634 | }, 635 | 55: { 636 | id: 55, 637 | 'description': 'Unidirectional Data Flow And The Angular Development Mode', 638 | 'duration': '7:07', 639 | 'seqNo': 6, 640 | courseId: 4 641 | }, 642 | 643 | 56: { 644 | id: 56, 645 | 'description': 'Dispatching an Action - Implementing the Login Component', 646 | 'duration': '4:39', 647 | 'seqNo': 7, 648 | courseId: 4 649 | }, 650 | 57: { 651 | id: 57, 652 | 'description': 'Setting Up the Ngrx DevTools - Demo', 653 | 'duration': '4:44', 654 | 'seqNo': 8, 655 | courseId: 4 656 | }, 657 | 58: { 658 | id: 58, 659 | 'description': 'Understanding Reducers - Writing Our First Reducer', 660 | 'duration': '9:10', 661 | 'seqNo': 9, 662 | courseId: 4 663 | }, 664 | 59: { 665 | id: 59, 666 | 'description': 'How To Define the Store Initial State', 667 | 'duration': '9:10', 668 | 'seqNo': 10, 669 | courseId: 4 670 | }, 671 | 672 | // NestJs Course 673 | 674 | 60: { 675 | id: 60, 676 | 'description': 'Introduction to NestJs', 677 | 'duration': '4:29', 678 | 'seqNo': 1, 679 | courseId: 14 680 | }, 681 | 61: { 682 | id: 61, 683 | 'description': 'Development Environment Setup', 684 | 'duration': '6:37', 685 | 'seqNo': 2, 686 | courseId: 14 687 | }, 688 | 62: { 689 | id: 62, 690 | 'description': 'Setting up a MongoDB Database', 691 | 'duration': '6:38', 692 | 'seqNo': 3, 693 | courseId: 14 694 | }, 695 | 63: { 696 | id: 63, 697 | 'description': 'CRUD with NestJs - Controllers and Repositories', 698 | 'duration': '12:12', 699 | 'seqNo': 4, 700 | courseId: 14 701 | }, 702 | 64: { 703 | id: 64, 704 | 'description': 'First REST endpoint - Get All Courses', 705 | 'duration': '3:42', 706 | 'seqNo': 5, 707 | courseId: 14 708 | }, 709 | 65: { 710 | id: 65, 711 | 'description': 'Error Handling', 712 | 'duration': '5:15', 713 | 'seqNo': 6, 714 | courseId: 14 715 | }, 716 | 66: { 717 | id: 66, 718 | 'description': 'NestJs Middleware', 719 | 'duration': '7:08', 720 | 'seqNo': 7, 721 | courseId: 14 722 | }, 723 | 67: { 724 | id: 67, 725 | 'description': 'Authentication in NestJs', 726 | 'duration': '13:22', 727 | 'seqNo': 8, 728 | courseId: 14 729 | }, 730 | 68: { 731 | id: 68, 732 | 'description': 'Authorization in NestJs', 733 | 'duration': '6:43', 734 | 'seqNo': 9, 735 | courseId: 14 736 | }, 737 | 69: { 738 | id: 69, 739 | 'description': 'Guards & Interceptors', 740 | 'duration': '8:16', 741 | 'seqNo': 10, 742 | courseId: 14 743 | }, 744 | 745 | // Stripe Course 746 | 747 | 70: { 748 | id: 70, 749 | 'description': 'Introduction to Stripe Payments', 750 | 'duration': '03:45', 751 | 'seqNo': 0, 752 | courseId: 16 753 | }, 754 | 71: { 755 | id: 71, 756 | 'description': 'The advantages of Stripe Checkout', 757 | 'duration': '08:36', 758 | 'seqNo': 1, 759 | courseId: 16 760 | }, 761 | 72: { 762 | id: 72, 763 | 'description': 'Setting up the development environment', 764 | 'duration': '09:10', 765 | 'seqNo': 2, 766 | courseId: 16 767 | }, 768 | 73: { 769 | id: 73, 770 | 'description': 'Creating a server Checkout Session', 771 | 'duration': '07:20', 772 | 'seqNo': 3, 773 | courseId: 16 774 | }, 775 | 74: { 776 | id: 74, 777 | 'description': 'Redirecting to the Stripe Checkout page', 778 | 'duration': '11:47', 779 | 'seqNo': 4, 780 | courseId: 16 781 | }, 782 | 75: { 783 | id: 75, 784 | 'description': 'Order fulfillment webhook', 785 | 'duration': '06:30', 786 | 'seqNo': 5, 787 | courseId: 16 788 | }, 789 | 76: { 790 | id: 76, 791 | 'description': 'Installing the Stripe CLI', 792 | 'duration': '4:13', 793 | 'seqNo': 6, 794 | courseId: 16 795 | }, 796 | 77: { 797 | id: 77, 798 | 'description': 'Firestore Security Rules for protecting Premium content', 799 | 'duration': '05:47', 800 | 'seqNo': 7, 801 | courseId: 16 802 | }, 803 | 78: { 804 | id: 78, 805 | 'description': 'Stripe Subscriptions with Stripe Checkout', 806 | 'duration': '05:17', 807 | 'seqNo': 8, 808 | courseId: 16 809 | }, 810 | 79: { 811 | id: 79, 812 | 'description': 'Stripe Subscription Fulfillment', 813 | 'duration': '07:50', 814 | 'seqNo': 9, 815 | courseId: 16 816 | }, 817 | 818 | 819 | // Reactive Angular Course 820 | 821 | 80: { 822 | id: 80, 823 | 'description': 'Introduction to Reactive Programming', 824 | 'duration': '03:45', 825 | 'seqNo': 0, 826 | courseId: 17, 827 | videoId: 'Df1QnesgB_s', 828 | }, 829 | 81: { 830 | id: 81, 831 | 'description': 'Introduction to RxJs', 832 | 'duration': '08:36', 833 | 'seqNo': 1, 834 | courseId: 17, 835 | videoId: '8m5RrAtqlyw', 836 | }, 837 | 82: { 838 | id: 82, 839 | 'description': 'Setting up the development environment', 840 | 'duration': '09:10', 841 | 'seqNo': 2, 842 | courseId: 17, 843 | videoId: '3fDbUB-nKqc', 844 | }, 845 | 83: { 846 | id: 83, 847 | 'description': 'Designing and building a Service Layer', 848 | 'duration': '07:20', 849 | 'seqNo': 3, 850 | courseId: 17, 851 | videoId: '', 852 | }, 853 | 84: { 854 | id: 84, 855 | 'description': 'Stateless Observable Services', 856 | 'duration': '11:47', 857 | 'seqNo': 4, 858 | courseId: 17, 859 | videoId: 'qvDPnRs_ZPA', 860 | }, 861 | 85: { 862 | id: 85, 863 | 'description': 'Smart vs Presentational Components', 864 | 'duration': '06:30', 865 | 'seqNo': 5, 866 | courseId: 17, 867 | videoId: '5bsZJGAelFM', 868 | }, 869 | 86: { 870 | id: 86, 871 | 'description': 'Lightweight state management', 872 | 'duration': '4:13', 873 | 'seqNo': 6, 874 | courseId: 17, 875 | videoId: '9m3_HHeP9Ko', 876 | }, 877 | 87: { 878 | id: 87, 879 | 'description': 'Event bubbling anti-pattern', 880 | 'duration': '05:47', 881 | 'seqNo': 7, 882 | courseId: 17, 883 | videoId: 'PRQCAL_RMVo', 884 | }, 885 | 88: { 886 | id: 88, 887 | 'description': 'Master detail with cached master table', 888 | 'duration': '05:17', 889 | 'seqNo': 8, 890 | courseId: 17, 891 | videoId: 'du4ib4jBUG0' 892 | }, 893 | 89: { 894 | id: 89, 895 | 'description': 'Error handling', 896 | 'duration': '07:50', 897 | 'seqNo': 9, 898 | courseId: 17, 899 | videoId: '8m5RrAtqlyw' 900 | }, 901 | 902 | 903 | 904 | // Angular Router Course 905 | 90: { 906 | id: 90, 907 | 'description': 'What is a Single Page Application?', 908 | 'duration': '04:00', 909 | 'seqNo': 1, 910 | courseId: 18, 911 | videoId: 'VES1eTNxi1s' 912 | }, 913 | 91: { 914 | id: 91, 915 | 'description': 'Setting Up The Development Environment', 916 | 'duration': '06:05', 917 | 'seqNo': 2, 918 | courseId: 18, 919 | videoId: 'ANfplcxnl78' 920 | }, 921 | 92: { 922 | id: 92, 923 | 'description': 'Angular Router Setup', 924 | 'duration': '02:36', 925 | 'seqNo': 3, 926 | courseId: 18, 927 | videoId: '9ez72LAd6mM' 928 | }, 929 | 93: { 930 | id: 93, 931 | 'description': 'Configuring a Home Route and Fallback Route', 932 | 'duration': '02:55', 933 | 'seqNo': 4, 934 | courseId: 18, 935 | videoId: 'Clj-jZpl64w' 936 | }, 937 | 94: { 938 | id: 94, 939 | 'description': 'Styling Active Routes With The routerLinkActive And routerLinkActiveOptions', 940 | 'duration': '07:50', 941 | 'seqNo': 5, 942 | courseId: 18, 943 | videoId: 'zcgnsmPVc30' 944 | }, 945 | 95: { 946 | id: 95, 947 | 'description': 'Child Routes - How To Setup a Master Detail Route', 948 | 'duration': '04:10', 949 | 'seqNo': 6, 950 | courseId: 18, 951 | videoId: 'zcgnsmPVc30' 952 | }, 953 | 96: { 954 | id: 96, 955 | 'description': 'Programmatic Router Navigation via the Router API ', 956 | 'duration': '03:59', 957 | 'seqNo': 7, 958 | courseId: 18, 959 | videoId: 'VES1eTNxi1s' 960 | }, 961 | 97: { 962 | id: 97, 963 | 'description': 'Relative And Absolute Router Navigation', 964 | 'duration': '04:58', 965 | 'seqNo': 8, 966 | courseId: 18, 967 | videoId: 'MQl9Zs3QqGM' 968 | }, 969 | 98: { 970 | id: 98, 971 | 'description': 'Master Detail Navigation And Route Parameters', 972 | 'duration': '06:03', 973 | 'seqNo': 9, 974 | courseId: 18, 975 | videoId: 'ANfplcxnl78' 976 | }, 977 | 978 | 99: { 979 | id: 99, 980 | 'description': 'The Route Parameters Observable', 981 | 'duration': '06:50', 982 | 'seqNo': 10, 983 | courseId: 18, 984 | videoId: 'zcgnsmPVc30' 985 | }, 986 | 100: { 987 | id: 100, 988 | 'description': 'Optional Route Query Parameters', 989 | 'duration': '03:03', 990 | 'seqNo': 11, 991 | courseId: 18, 992 | videoId: '0Qsg8fyKwO4' 993 | }, 994 | 101: { 995 | id: 101, 996 | 'description': 'The queryParams Directive and the Query Parameters Observable', 997 | 'duration': '07:50', 998 | 'seqNo': 12, 999 | courseId: 18, 1000 | videoId: 'VES1eTNxi1s' 1001 | }, 1002 | 102: { 1003 | id: 102, 1004 | 'description': 'Exiting an Angular Route - How To Prevent Memory Leaks', 1005 | 'duration': '07:50', 1006 | 'seqNo': 13, 1007 | courseId: 18, 1008 | videoId: 'ANfplcxnl78' 1009 | }, 1010 | 103: { 1011 | id: 103, 1012 | 'description': 'CanDeactivate Route Guard', 1013 | 'duration': '04:50', 1014 | 'seqNo': 14, 1015 | courseId: 18, 1016 | videoId: '9ez72LAd6mM' 1017 | }, 1018 | 104: { 1019 | id: 104, 1020 | 'description': 'CanActivate Route Guard - An Example of An Asynchronous Route Guard', 1021 | 'duration': '03:32', 1022 | 'seqNo': 15, 1023 | courseId: 18, 1024 | videoId: 'Clj-jZpl64w' 1025 | }, 1026 | 1027 | 1028 | 105: { 1029 | id: 105, 1030 | 'description': 'Configure Auxiliary Routes in the Angular Router', 1031 | 'duration': '05:16', 1032 | 'seqNo': 16, 1033 | courseId: 18, 1034 | videoId: 'zcgnsmPVc30' 1035 | }, 1036 | 1037 | 106: { 1038 | id: 106, 1039 | 'description': 'Angular Auxiliary Routes - How To Pass Router Parameters', 1040 | 'duration': '07:50', 1041 | 'seqNo': 17, 1042 | courseId: 18, 1043 | videoId: 'yjQUkNHb1Is' 1044 | }, 1045 | 107: { 1046 | id: 107, 1047 | 'description': 'Angular Router Redirects and Path Matching', 1048 | 'duration': '02:59', 1049 | 'seqNo': 18, 1050 | courseId: 18, 1051 | videoId: 'VES1eTNxi1s' 1052 | }, 1053 | 108: { 1054 | id: 108, 1055 | 'description': 'Angular Router Hash Location Strategy', 1056 | 'duration': '07:50', 1057 | 'seqNo': 19, 1058 | courseId: 18, 1059 | videoId: 'MQl9Zs3QqGM' 1060 | }, 1061 | 109: { 1062 | id: 109, 1063 | 'description': 'Angular Router Lazy Loading and Shared Modules', 1064 | 'duration': '08:45', 1065 | 'seqNo': 20, 1066 | courseId: 18, 1067 | videoId: '0Qsg8fyKwO4' 1068 | }, 1069 | 110: { 1070 | id: 110, 1071 | 'description': 'Exercise - Implement a Widget Dashboard', 1072 | 'duration': '07:50', 1073 | 'seqNo': 21, 1074 | courseId: 18, 1075 | videoId: 'VES1eTNxi1s' 1076 | }, 1077 | 111: { 1078 | id: 111, 1079 | 'description': 'Exercise Solution ', 1080 | 'duration': '07:50', 1081 | 'seqNo': 22, 1082 | courseId: 18, 1083 | videoId: '0Qsg8fyKwO4' 1084 | }, 1085 | 1086 | // Angular Material In Depth 1087 | 1088 | 120: { 1089 | id: 120, 1090 | 'description': 'Introduction to Angular Material', 1091 | 'duration': '4:17', 1092 | 'seqNo': 1, 1093 | courseId: 11, 1094 | longDescription: "A quick introduction to the Angular Material library." 1095 | }, 1096 | 121: { 1097 | id: 121, 1098 | 'description': 'Navigation and Containers', 1099 | 'duration': '6:37', 1100 | 'seqNo': 2, 1101 | courseId: 11, 1102 | longDescription: "Guided tour of navigation elements and container." 1103 | }, 1104 | 122: { 1105 | id: 122, 1106 | 'description': 'Data Tables', 1107 | 'duration': '8:03', 1108 | 'seqNo': 3, 1109 | courseId: 11, 1110 | longDescription: "Angular Material Data Tables in detail." 1111 | }, 1112 | 123: { 1113 | id: 123, 1114 | 'description': 'Dialogs', 1115 | 'duration': '11:46', 1116 | 'seqNo': 4, 1117 | courseId: 11, 1118 | longDescription: "Modal elements and how to use them." 1119 | }, 1120 | 124: { 1121 | id: 124, 1122 | 'description': 'Commonly used Form Controls', 1123 | 'duration': '7:17', 1124 | 'seqNo': 5, 1125 | courseId: 11, 1126 | longDescription: "All sorts of commonly needed form controls." 1127 | }, 1128 | 125: { 1129 | id: 125, 1130 | 'description': 'Drag and Drop', 1131 | 'duration': '8:16', 1132 | 'seqNo': 6, 1133 | courseId: 11, 1134 | longDescription: "How to use drag and drop." 1135 | }, 1136 | 126: { 1137 | id: 126, 1138 | 'description': 'Responsive Design', 1139 | 'duration': '7:28', 1140 | 'seqNo': 7, 1141 | courseId: 11, 1142 | longDescription: "Everything about making our screens responsive." 1143 | }, 1144 | 127: { 1145 | id: 127, 1146 | 'description': 'Tree Component', 1147 | 'duration': '11:09', 1148 | 'seqNo': 8, 1149 | courseId: 11, 1150 | longDescription: "All about the Angular Material Tree component." 1151 | }, 1152 | 128: { 1153 | id: 128, 1154 | 'description': 'Virtual Scrolling', 1155 | 'duration': '3:44', 1156 | 'seqNo': 9, 1157 | courseId: 11, 1158 | longDescription: "How to use virtual scrolling to handle large amounts of data." 1159 | }, 1160 | 129: { 1161 | id: 129, 1162 | 'description': 'Custom Themes', 1163 | 'duration': '8:55', 1164 | 'seqNo': 10, 1165 | courseId: 11, 1166 | longDescription: "How to build your own custom Angular Material theme." 1167 | }, 1168 | 130: { 1169 | id: 130, 1170 | 'description': 'Changing Theme at Runtime', 1171 | 'duration': '12:37', 1172 | 'seqNo': 11, 1173 | courseId: 11, 1174 | longDescription: "" 1175 | } 1176 | }; 1177 | 1178 | 1179 | export const USERS = { 1180 | 1: { 1181 | id: 1, 1182 | email: 'test@angular-university.io', 1183 | password: 'test', 1184 | pictureUrl: 'https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png' 1185 | } 1186 | 1187 | }; 1188 | 1189 | 1190 | export function findCourseById(courseId: number) { 1191 | return COURSES[courseId]; 1192 | } 1193 | 1194 | export function findLessonsForCourse(courseId: number) { 1195 | return Object.values(LESSONS).filter(lesson => lesson.courseId == courseId); 1196 | } 1197 | 1198 | export function authenticate(email: string, password: string) { 1199 | 1200 | const user: any = Object.values(USERS).find(user => user.email === email); 1201 | 1202 | if (user && user.password == password) { 1203 | return user; 1204 | } else { 1205 | return undefined; 1206 | } 1207 | 1208 | } 1209 | -------------------------------------------------------------------------------- /server/get-courses.route.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import {Request, Response} from 'express'; 4 | import {COURSES} from "./db-data"; 5 | 6 | 7 | 8 | export function getAllCourses(req: Request, res: Response) { 9 | 10 | res.status(200).json({payload:Object.values(COURSES).sort((c1:any, c2:any) => c1.seqNo - c2.seqNo)}); 11 | 12 | } 13 | 14 | 15 | export function getCourseById(req: Request, res: Response) { 16 | 17 | const courseId = req.params["id"]; 18 | 19 | const courses:any = Object.values(COURSES); 20 | 21 | const course = courses.find(course => course.id == courseId); 22 | 23 | res.status(200).json(course); 24 | } 25 | -------------------------------------------------------------------------------- /server/search-lessons.route.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import {Request, Response} from 'express'; 5 | import {LESSONS} from "./db-data"; 6 | import {setTimeout} from "timers"; 7 | 8 | 9 | 10 | export function searchLessons(req: Request, res: Response) { 11 | 12 | const queryParams = req.query as any; 13 | 14 | const courseId = queryParams.courseId, 15 | filter = queryParams.filter || '', 16 | sortOrder = queryParams.sortOrder, 17 | pageNumber = parseInt(queryParams.pageNumber) || 0, 18 | pageSize = parseInt(queryParams.pageSize), 19 | sortColumn = queryParams.sortColumn ?? "seqNo"; 20 | 21 | let lessons = Object.values(LESSONS) 22 | .filter(lesson => lesson.courseId == courseId) 23 | .sort((l1, l2) => { 24 | if (l1[sortColumn] > l2[sortColumn]) { 25 | return 1; 26 | } 27 | else if (l1[sortColumn] < l2[sortColumn]) { 28 | return -1; 29 | } 30 | else { 31 | return 0; 32 | } 33 | }); 34 | 35 | if (filter) { 36 | lessons = lessons.filter(lesson => lesson.description.trim().toLowerCase().search(filter.toLowerCase()) >= 0); 37 | } 38 | 39 | if (sortOrder == "desc") { 40 | lessons = lessons.reverse(); 41 | } 42 | 43 | const initialPos = pageNumber * pageSize; 44 | 45 | const lessonsPage = lessons.slice(initialPos, initialPos + pageSize); 46 | 47 | setTimeout(() => { 48 | res.status(200).json({payload: lessonsPage}); 49 | },1000); 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /server/server.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import * as express from 'express'; 4 | import {Application} from "express"; 5 | import {getAllCourses, getCourseById} from "./get-courses.route"; 6 | import {searchLessons} from "./search-lessons.route"; 7 | 8 | 9 | const app: Application = express(); 10 | 11 | const cors = require('cors'); 12 | 13 | app.use(cors({origin: true})); 14 | 15 | app.route('/api/courses').get(getAllCourses); 16 | 17 | app.route('/api/courses/:id').get(getCourseById); 18 | 19 | app.route('/api/lessons').get(searchLessons); 20 | 21 | 22 | 23 | 24 | const httpServer:any = app.listen(9000, () => { 25 | console.log("HTTP REST API Server running at http://localhost:" + httpServer.address().port); 26 | }); 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /server/server.tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "lib": ["es2017"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/_mixins.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | @mixin drag-drop { 4 | 5 | .cdk-drag-preview { 6 | box-sizing: border-box; 7 | border-radius: 4px; 8 | font-family: Roboto; 9 | box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 10 | 0 8px 10px 1px rgba(0, 0, 0, 0.14), 11 | 0 3px 14px 2px rgba(0, 0, 0, 0.12); 12 | } 13 | 14 | .cdk-drag-animating { 15 | transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); 16 | } 17 | 18 | .lesson:last-child { 19 | border: none; 20 | } 21 | 22 | .drag-drop-list.cdk-drop-list-dragging .drag-drop-item:not(.cdk-drag-placeholder) { 23 | transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); 24 | } 25 | 26 | .drop-placeholder { 27 | background: #ccc; 28 | border: dotted 3px #999; 29 | min-height: 60px; 30 | transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); 31 | } 32 | 33 | } 34 | 35 | @mixin responsive-dialogs { 36 | 37 | @media screen and (max-width: 959px) { 38 | 39 | .modal-panel { 40 | min-width: 0 !important; 41 | max-width: 100% !important; 42 | max-height: 100% !important; 43 | width: 100% !important; 44 | height: 100% !important; 45 | 46 | .mat-dialog-container { 47 | border-radius: 0; 48 | padding: 14px !important; 49 | } 50 | 51 | } 52 | 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/app/about/about.component.css: -------------------------------------------------------------------------------- 1 | 2 | .about-container { 3 | margin: 30px auto; 4 | max-width: 400px; 5 | } 6 | 7 | .course-image { 8 | max-width: 200px; 9 | } 10 | 11 | :host { 12 | font-family: Roboto; 13 | } 14 | -------------------------------------------------------------------------------- /src/app/about/about.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

Welcome!

5 | 6 |

Welcome to the Angular Material In Depth Course

7 | 8 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/app/about/about.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'about', 5 | templateUrl: './about.component.html', 6 | styleUrls: ['./about.component.css'], 7 | standalone: false 8 | }) 9 | export class AboutComponent implements OnInit { 10 | 11 | constructor() { } 12 | 13 | ngOnInit() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import {HomeComponent} from "./home/home.component"; 4 | import {AboutComponent} from "./about/about.component"; 5 | import {CourseComponent} from "./course/course.component"; 6 | import {courseResolver} from "./services/course.resolver"; 7 | import {CreateCourseComponent} from './create-course/create-course.component'; 8 | import {DragDropComponent} from './drag-drop/drag-drop.component'; 9 | import {TreeDemoComponent} from './tree-demo/tree-demo.component'; 10 | import {VirtualScrollingComponent} from './virtual-scrolling/virtual-scrolling.component'; 11 | 12 | const routes: Routes = [ 13 | { 14 | path: "", 15 | component: HomeComponent 16 | 17 | }, 18 | { 19 | path: "about", 20 | component: AboutComponent 21 | }, 22 | { 23 | path: 'courses/:id', 24 | component: CourseComponent, 25 | resolve: { 26 | course: courseResolver 27 | } 28 | }, 29 | { 30 | path: 'add-new-course', 31 | component: CreateCourseComponent 32 | }, 33 | { 34 | path: "drag-drop-example", 35 | component: DragDropComponent 36 | }, 37 | { 38 | path: "tree-demo", 39 | component: TreeDemoComponent 40 | }, 41 | { 42 | path: 'virtual-scrolling', 43 | component: VirtualScrollingComponent 44 | }, 45 | { 46 | path: "**", 47 | redirectTo: '/' 48 | } 49 | ]; 50 | 51 | @NgModule({ 52 | imports: [RouterModule.forRoot(routes)], 53 | exports: [RouterModule] 54 | }) 55 | export class AppRoutingModule { } 56 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | 2 | >>> body { 3 | margin: 0; 4 | } 5 | 6 | main { 7 | margin: 30px; 8 | } 9 | 10 | mat-toolbar a { 11 | color: white; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | library_books 10 | Courses 11 | 12 | 13 | 14 | question_answer 15 | About 16 | 17 | 18 | 19 | drag_indicator 20 | Drag and Drop 21 | 22 | 23 | 24 | park 25 | Tree Demo 26 | 27 | 28 | 29 | import_export 30 | Virtual Scrolling 31 | 32 | 33 | 34 | person_add 35 | Register 36 | 37 | 38 | 39 | account_circle 40 | Login 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 54 | 55 | 56 | Screens 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | person_add 70 | Register 71 | 72 | 73 | 74 | account_circle 75 | Login 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | describe('AppComponent', () => { 5 | beforeEach(waitForAsync(() => { 6 | TestBed.configureTestingModule({ 7 | imports: [ 8 | RouterTestingModule 9 | ], 10 | declarations: [ 11 | AppComponent 12 | ], 13 | }).compileComponents(); 14 | })); 15 | it('should create the app', waitForAsync(() => { 16 | const fixture = TestBed.createComponent(AppComponent); 17 | const app = fixture.debugElement.componentInstance; 18 | expect(app).toBeTruthy(); 19 | })); 20 | it(`should have as title 'app'`, waitForAsync(() => { 21 | const fixture = TestBed.createComponent(AppComponent); 22 | const app = fixture.debugElement.componentInstance; 23 | expect(app.title).toEqual('app'); 24 | })); 25 | it('should render title in a h1 tag', waitForAsync(() => { 26 | const fixture = TestBed.createComponent(AppComponent); 27 | fixture.detectChanges(); 28 | const compiled = fixture.debugElement.nativeElement; 29 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!'); 30 | })); 31 | }); 32 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'], 7 | standalone: false 8 | }) 9 | export class AppComponent { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import {BrowserModule} from '@angular/platform-browser'; 2 | import {NgModule} from '@angular/core'; 3 | 4 | import {AppRoutingModule} from './app-routing.module'; 5 | import {AppComponent} from './app.component'; 6 | import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; 7 | import {MatMenuModule} from '@angular/material/menu'; 8 | import { MatButtonModule} from '@angular/material/button' 9 | import {MatIconModule} from '@angular/material/icon'; 10 | import {MatCardModule} from '@angular/material/card'; 11 | import { HomeComponent } from './home/home.component'; 12 | import { AboutComponent } from './about/about.component'; 13 | import {MatTabsModule} from '@angular/material/tabs'; 14 | import { CoursesCardListComponent } from './courses-card-list/courses-card-list.component'; 15 | import {CourseComponent} from "./course/course.component"; 16 | import { MatDatepickerModule } from "@angular/material/datepicker"; 17 | import { MatDialogModule } from "@angular/material/dialog"; 18 | import { MatInputModule } from "@angular/material/input"; 19 | import { MatListModule } from "@angular/material/list"; 20 | import { MatPaginatorModule } from "@angular/material/paginator"; 21 | import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; 22 | import { MatSelectModule } from "@angular/material/select"; 23 | import { MatSidenavModule } from "@angular/material/sidenav"; 24 | import { MatSortModule } from "@angular/material/sort"; 25 | import { MatTableModule } from "@angular/material/table"; 26 | import { MatToolbarModule } from "@angular/material/toolbar"; 27 | import {CoursesService} from "./services/courses.service"; 28 | import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; 29 | import { CourseDialogComponent } from './course-dialog/course-dialog.component'; 30 | import { ReactiveFormsModule} from "@angular/forms"; 31 | import {CreateCourseComponent} from './create-course/create-course.component'; 32 | import {MatStepperModule} from '@angular/material/stepper'; 33 | import {CreateCourseStep1Component} from './create-course/create-course-step-1/create-course-step-1.component'; 34 | import {CreateCourseStep2Component} from './create-course/create-course-step-2/create-course-step-2.component'; 35 | import { MatCheckboxModule} from '@angular/material/checkbox'; 36 | import { MatRadioModule} from '@angular/material/radio'; 37 | import {MatNativeDateModule} from '@angular/material/core'; 38 | import { MatSliderModule} from '@angular/material/slider'; 39 | import { MatTooltipModule} from '@angular/material/tooltip'; 40 | import {DragDropModule} from '@angular/cdk/drag-drop'; 41 | import {DragDropComponent} from './drag-drop/drag-drop.component'; 42 | 43 | import {MatGridListModule} from '@angular/material/grid-list'; 44 | import {TreeDemoComponent} from './tree-demo/tree-demo.component'; 45 | import {MatTreeModule} from '@angular/material/tree'; 46 | import {VirtualScrollingComponent} from './virtual-scrolling/virtual-scrolling.component'; 47 | import {ScrollingModule} from '@angular/cdk/scrolling'; 48 | 49 | @NgModule({ declarations: [ 50 | AppComponent, 51 | HomeComponent, 52 | AboutComponent, 53 | CourseComponent, 54 | CoursesCardListComponent, 55 | CourseDialogComponent, 56 | CreateCourseComponent, 57 | CreateCourseStep1Component, 58 | CreateCourseStep2Component, 59 | DragDropComponent, 60 | TreeDemoComponent, 61 | VirtualScrollingComponent 62 | ], 63 | bootstrap: [AppComponent], imports: [BrowserModule, 64 | BrowserAnimationsModule, 65 | MatMenuModule, 66 | MatButtonModule, 67 | MatIconModule, 68 | MatCardModule, 69 | MatTabsModule, 70 | MatSidenavModule, 71 | MatListModule, 72 | MatToolbarModule, 73 | MatTooltipModule, 74 | MatInputModule, 75 | MatTableModule, 76 | MatPaginatorModule, 77 | MatSortModule, 78 | MatProgressSpinnerModule, 79 | MatStepperModule, 80 | MatDialogModule, 81 | AppRoutingModule, 82 | MatSelectModule, 83 | MatCheckboxModule, 84 | DragDropModule, 85 | MatRadioModule, 86 | MatSliderModule, 87 | MatDatepickerModule, 88 | MatNativeDateModule, 89 | ReactiveFormsModule, 90 | MatGridListModule, 91 | MatTreeModule, 92 | ScrollingModule], providers: [ 93 | CoursesService, 94 | provideHttpClient(withInterceptorsFromDi()) 95 | ] }) 96 | export class AppModule { 97 | } 98 | -------------------------------------------------------------------------------- /src/app/course-dialog/course-dialog.component.css: -------------------------------------------------------------------------------- 1 | 2 | .edit-course-form { 3 | min-width: 300px; 4 | } 5 | 6 | mat-dialog-content { 7 | display: flex; 8 | flex-direction: column; 9 | } 10 | 11 | textarea { 12 | height: 100px; 13 | resize: vertical; 14 | } 15 | -------------------------------------------------------------------------------- /src/app/course-dialog/course-dialog.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |

{{description}}

6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | Beginner 24 | 25 | Intermediate 26 | 27 | Advanced 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 |
67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/app/course-dialog/course-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Inject, OnInit, ViewEncapsulation} from '@angular/core'; 2 | import {MAT_DIALOG_DATA, MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material/dialog'; 3 | import {Course} from "../model/course"; 4 | import {FormBuilder, Validators, FormGroup} from "@angular/forms"; 5 | import * as moment from 'moment'; 6 | 7 | @Component({ 8 | selector: 'course-dialog', 9 | templateUrl: './course-dialog.component.html', 10 | styleUrls: ['./course-dialog.component.css'], 11 | standalone: false 12 | }) 13 | export class CourseDialogComponent implements OnInit { 14 | 15 | description:string; 16 | 17 | form = this.fb.group({ 18 | description: [this.course.description, Validators.required], 19 | category: [this.course.category, Validators.required], 20 | releasedAt: [new Date(), Validators.required], 21 | longDescription: [this.course.longDescription, Validators.required] 22 | }); 23 | 24 | constructor(private fb: FormBuilder, 25 | @Inject(MAT_DIALOG_DATA) private course:Course, 26 | private dialogRef: MatDialogRef) { 27 | 28 | this.description = course.description; 29 | 30 | } 31 | 32 | ngOnInit() { 33 | 34 | } 35 | 36 | close() { 37 | 38 | this.dialogRef.close(); 39 | 40 | } 41 | 42 | save() { 43 | 44 | this.dialogRef.close(this.form.value); 45 | 46 | } 47 | } 48 | 49 | 50 | export function openEditCourseDialog(dialog: MatDialog, course:Course) { 51 | 52 | const config = new MatDialogConfig(); 53 | 54 | config.disableClose = true; 55 | config.autoFocus = true; 56 | config.panelClass = "modal-panel"; 57 | config.backdropClass = "backdrop-modal-panel"; 58 | 59 | config.data = { 60 | ...course 61 | }; 62 | 63 | const dialogRef = dialog.open(CourseDialogComponent, config); 64 | 65 | return dialogRef.afterClosed(); 66 | } 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/app/course/course.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

{{course?.description}}

4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 18 | 19 | 20 | 21 | 33 | 34 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 85 | 86 | 87 | 88 | 90 | 91 | 92 | 93 | 94 |
22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 38 | 39 | 40 | 41 | 42 | #{{lesson.seqNo}}Description{{lesson.description}}Duration{{lesson.duration}} 73 | {{lesson.longDescription}} 74 |
95 | 96 |
97 | 98 | 102 | 103 | 104 | 105 |
106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/app/course/course.component.scss: -------------------------------------------------------------------------------- 1 | 2 | .course { 3 | text-align: center; 4 | width: 390px; 5 | margin: 40px auto; 6 | } 7 | 8 | .course-thumbnail { 9 | width: 150px; 10 | margin: 20px auto 0 auto; 11 | display: block; 12 | } 13 | 14 | .scrolling-container { 15 | 16 | } 17 | 18 | .description-cell { 19 | text-align: left; 20 | margin: 10px auto; 21 | } 22 | 23 | .collapsed-detail { 24 | display: none; 25 | } 26 | 27 | .duration-cell { 28 | text-align: center; 29 | } 30 | 31 | .duration-cell mat-icon { 32 | display: inline-block; 33 | vertical-align: middle; 34 | font-size: 20px; 35 | } 36 | 37 | .spinner-container { 38 | height: 360px; 39 | width: 390px; 40 | position: fixed; 41 | } 42 | 43 | .lessons-table { 44 | min-height: 360px; 45 | margin-top: 10px; 46 | width: 400px; 47 | margin-left: auto; 48 | margin-right: auto; 49 | tr:hover { 50 | background: whitesmoke; 51 | cursor: pointer; 52 | } 53 | } 54 | 55 | mat-paginator { 56 | width: 400px; 57 | padding-bottom: 10px; 58 | margin-bottom: 100px; 59 | } 60 | 61 | .spinner-container mat-spinner { 62 | margin: 130px auto 0 auto; 63 | } 64 | 65 | .mat-column-seqNo { 66 | width: 32px; 67 | border-right: 1px solid black; 68 | padding-right: 24px; 69 | text-align: center; 70 | } 71 | 72 | .mat-column-description { 73 | padding-left:20px; 74 | text-align: left; 75 | } 76 | 77 | .mat-column-duration { 78 | font-style: italic; 79 | } 80 | -------------------------------------------------------------------------------- /src/app/course/course.component.ts: -------------------------------------------------------------------------------- 1 | import {AfterViewInit, Component, OnInit, ViewChild} from '@angular/core'; 2 | import {ActivatedRoute} from "@angular/router"; 3 | import { MatPaginator } from "@angular/material/paginator"; 4 | import { MatSort } from "@angular/material/sort"; 5 | import {Course} from "../model/course"; 6 | import {CoursesService} from "../services/courses.service"; 7 | import { tap, catchError, finalize} from 'rxjs/operators'; 8 | import {merge, throwError} from 'rxjs'; 9 | import {Lesson} from '../model/lesson'; 10 | import {SelectionModel} from '@angular/cdk/collections'; 11 | 12 | 13 | @Component({ 14 | selector: 'course', 15 | templateUrl: './course.component.html', 16 | styleUrls: ['./course.component.scss'], 17 | standalone: false 18 | }) 19 | export class CourseComponent implements OnInit, AfterViewInit { 20 | 21 | course:Course; 22 | 23 | lessons: Lesson[] = []; 24 | 25 | loading = false; 26 | 27 | @ViewChild(MatPaginator) 28 | paginator: MatPaginator; 29 | 30 | @ViewChild(MatSort) 31 | sort: MatSort; 32 | 33 | selection = new SelectionModel(true, []); 34 | 35 | 36 | constructor(private route: ActivatedRoute, 37 | private coursesService: CoursesService) { 38 | 39 | } 40 | 41 | displayedColumns = ['select', 'seqNo', "description", "duration"]; 42 | 43 | expandedLesson: Lesson = null; 44 | 45 | ngOnInit() { 46 | 47 | this.course = this.route.snapshot.data["course"]; 48 | 49 | this.loadLessonsPage(); 50 | 51 | } 52 | 53 | onLessonToggled(lesson:Lesson) { 54 | 55 | this.selection.toggle(lesson); 56 | 57 | console.log(this.selection.selected); 58 | 59 | } 60 | 61 | loadLessonsPage() { 62 | 63 | this.loading = true; 64 | 65 | this.coursesService.findLessons( 66 | this.course.id, 67 | this.sort?.direction ?? "asc", 68 | this.paginator?.pageIndex ?? 0, 69 | this.paginator?.pageSize ?? 3, 70 | this.sort?.active ?? "seqNo") 71 | .pipe( 72 | tap(lessons => this.lessons = lessons), 73 | catchError(err => { 74 | console.log("Error loading lessons", err); 75 | alert("Error loading lessons."); 76 | return throwError(err); 77 | 78 | }), 79 | finalize(() => this.loading = false) 80 | ) 81 | .subscribe(); 82 | 83 | } 84 | 85 | onToggleLesson(lesson:Lesson) { 86 | if (lesson == this.expandedLesson) { 87 | this.expandedLesson = null; 88 | } 89 | else { 90 | this.expandedLesson = lesson; 91 | } 92 | 93 | } 94 | 95 | ngAfterViewInit() { 96 | 97 | this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); 98 | 99 | merge(this.sort.sortChange, this.paginator.page) 100 | .pipe( 101 | tap(() => this.loadLessonsPage()) 102 | ) 103 | .subscribe(); 104 | 105 | 106 | } 107 | 108 | isAllSelected() { 109 | return this.selection.selected?.length == this.lessons?.length; 110 | } 111 | 112 | toggleAll() { 113 | if (this.isAllSelected()) { 114 | this.selection.clear(); 115 | } 116 | else { 117 | this.selection.select(...this.lessons); 118 | } 119 | } 120 | 121 | } 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /src/app/courses-card-list/courses-card-list.component.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .course-card { 4 | margin: 20px 10px; 5 | max-width:474px; 6 | } 7 | 8 | .course-actions { 9 | display: flex; 10 | justify-content: center; 11 | } 12 | 13 | .course-actions button { 14 | margin-right: 5px; 15 | } 16 | 17 | .course-thumbnail { 18 | max-width:474px; 19 | max-height: 267px; 20 | } 21 | 22 | 23 | .long-description { 24 | min-height: 50px; 25 | max-width: 442px; 26 | } 27 | 28 | mat-card-content p{ 29 | padding: 10px; 30 | } 31 | 32 | 33 | .handset-portrait .course-card { 34 | width: 100%; 35 | margin: 0; 36 | padding: 16px 0; 37 | } 38 | 39 | .handset-portrait .course-thumbnail { 40 | width: 100%; 41 | margin: 0; 42 | } 43 | 44 | .handset-portrait .long-description { 45 | padding: 10px 10px 0 10px; 46 | } 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/app/courses-card-list/courses-card-list.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {{course.description}} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

{{course.longDescription}}

24 | 25 |
26 | 27 | 28 | 29 | 30 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | 45 |
46 | 47 | 48 |
49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/app/courses-card-list/courses-card-list.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, OnInit, ViewEncapsulation} from '@angular/core'; 2 | import {Course} from '../model/course'; 3 | import {MatDialog, MatDialogConfig} from '@angular/material/dialog'; 4 | import {openEditCourseDialog} from '../course-dialog/course-dialog.component'; 5 | import {filter} from 'rxjs/operators'; 6 | import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout'; 7 | 8 | @Component({ 9 | selector: 'courses-card-list', 10 | templateUrl: './courses-card-list.component.html', 11 | styleUrls: ['./courses-card-list.component.css'], 12 | standalone: false 13 | }) 14 | export class CoursesCardListComponent implements OnInit { 15 | 16 | @Input() 17 | courses: Course[]; 18 | 19 | cols = 1; 20 | 21 | rowHeight = '500px'; 22 | 23 | handsetPortrait = false; 24 | 25 | 26 | constructor(private dialog: MatDialog, 27 | private responsive: BreakpointObserver) { 28 | } 29 | 30 | ngOnInit() { 31 | /* 32 | this.responsive.observe([ 33 | Breakpoints.TabletPortrait, 34 | Breakpoints.TabletLandscape, 35 | Breakpoints.HandsetPortrait, 36 | Breakpoints.HandsetLandscape 37 | ]) 38 | .subscribe(result => { 39 | 40 | this.cols = 3; 41 | this.rowHeight = "500px"; 42 | this.handsetPortrait = false; 43 | 44 | const breakpoints = result.breakpoints; 45 | 46 | if (breakpoints[Breakpoints.TabletPortrait]) { 47 | this.cols = 1; 48 | } 49 | else if (breakpoints[Breakpoints.HandsetPortrait]) { 50 | this.cols = 1; 51 | this.rowHeight = "430px"; 52 | this.handsetPortrait = true; 53 | } 54 | else if (breakpoints[Breakpoints.HandsetLandscape]) { 55 | this.cols = 1; 56 | } 57 | else if (breakpoints[Breakpoints.TabletLandscape]) { 58 | this.cols = 2; 59 | } 60 | 61 | }); 62 | */ 63 | 64 | 65 | } 66 | 67 | editCourse(course: Course) { 68 | 69 | openEditCourseDialog(this.dialog, course) 70 | .pipe( 71 | filter(val => !!val) 72 | ) 73 | .subscribe( 74 | val => console.log('new course value:', val) 75 | ); 76 | 77 | 78 | } 79 | 80 | } 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/app/create-course/create-course-step-1/create-course-step-1.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{title.value.length}} / 60 12 | 13 | 14 | 15 | 16 | 17 | A minimum length of 5 characters is required. 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Free 27 | 28 | Premium 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | None 38 | 39 | 40 | 41 | New To Programming 42 | New To Javascript 43 | New To Angular 44 | 45 | 46 | 47 | Intermediate 48 | Advanced 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 74 | 75 | Downloads allowed? 76 | 77 | 78 | 79 | 80 | 81 | 88 | 89 | 90 | 91 | 92 |
93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/app/create-course/create-course-step-1/create-course-step-1.component.scss: -------------------------------------------------------------------------------- 1 | 2 | .course-details-form { 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | .course-type { 8 | margin-top: 15px; 9 | margin-bottom: 15px; 10 | } 11 | 12 | mat-radio-button { 13 | margin-right: 5px; 14 | } 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/app/create-course/create-course-step-1/create-course-step-1.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {FormBuilder, Validators} from '@angular/forms'; 3 | import {MatCalendarCellClassFunction} from '@angular/material/datepicker'; 4 | 5 | const SAMPLE_TEXT= "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin enim quam, semper et sodales in, aliquam vitae leo. Suspendisse quis eleifend nisl. Nunc ante ligula, ultricies sed quam et, consectetur laoreet enim. Praesent scelerisque velit efficitur blandit dapibus. Etiam vulputate, lacus eu vestibulum faucibus, leo odio consectetur sem, nec rhoncus nulla diam vitae ante. Aenean lacinia porta quam, vel pretium mi. Cras sed leo ut dui gravida faucibus ut at nibh. In hac habitasse platea dictumst. Praesent finibus tempor lobortis. Integer ut urna lacus. Fusce imperdiet dolor efficitur erat facilisis venenatis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nullam non eros efficitur, accumsan est vel, pulvinar sem."; 6 | 7 | 8 | @Component({ 9 | selector: "create-course-step-1", 10 | templateUrl: "create-course-step-1.component.html", 11 | styleUrls: ["create-course-step-1.component.scss"], 12 | standalone: false 13 | }) 14 | export class CreateCourseStep1Component { 15 | 16 | form = this.fb.group({ 17 | title: ['', [ 18 | Validators.required, 19 | Validators.minLength(5), 20 | Validators.maxLength(60) 21 | ]], 22 | releasedAt: [new Date(1990,0,1), Validators.required], 23 | category: ['BEGINNER', Validators.required], 24 | courseType: ['premium', Validators.required], 25 | downloadsAllowed: [false, Validators.requiredTrue], 26 | longDescription: [SAMPLE_TEXT, [Validators.required, Validators.minLength(3)]] 27 | }); 28 | 29 | dateClass: MatCalendarCellClassFunction = (cellDate, view) => { 30 | 31 | const date = cellDate.getDate(); 32 | 33 | if (view == 'month') { 34 | return (date == 1) ? 'highlight-date' : ""; 35 | } 36 | 37 | return ""; 38 | } 39 | 40 | constructor(private fb: FormBuilder) { 41 | 42 | } 43 | 44 | get courseTitle() { 45 | return this.form.controls['title']; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/app/create-course/create-course-step-2/create-course-step-2.component.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/angular-material-course/7cb00690dca823f5554a6e2cf4d5a43c1b440eb8/src/app/create-course/create-course-step-2/create-course-step-2.component.html -------------------------------------------------------------------------------- /src/app/create-course/create-course-step-2/create-course-step-2.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/angular-material-course/7cb00690dca823f5554a6e2cf4d5a43c1b440eb8/src/app/create-course/create-course-step-2/create-course-step-2.component.scss -------------------------------------------------------------------------------- /src/app/create-course/create-course-step-2/create-course-step-2.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | 3 | 4 | @Component({ 5 | selector: "create-course-step-2", 6 | templateUrl: "create-course-step-2.component.html", 7 | styleUrls: ["create-course-step-2.component.scss"], 8 | standalone: false 9 | }) 10 | export class CreateCourseStep2Component { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/app/create-course/create-course.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Create New Course

4 | 5 | 8 | 9 | 10 | 11 | Course Details 12 | 13 | 14 | 15 |
16 | 17 | 20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 | Course Lessons 28 | 29 | 30 | 31 |
32 | 33 | 34 | 35 | 36 | 37 |
38 | 39 |
40 | 41 |
42 | 43 | 44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /src/app/create-course/create-course.component.scss: -------------------------------------------------------------------------------- 1 | 2 | h2 { 3 | font-family: "Roboto"; 4 | } 5 | 6 | .scrolling-space { 7 | width: 100%; 8 | height: 500px; 9 | } 10 | 11 | .data-form { 12 | max-width: 600px; 13 | margin: 40px auto; 14 | } 15 | 16 | .stepper-buttons { 17 | margin-top: 10px; 18 | } 19 | 20 | .stepper-buttons button { 21 | margin-right: 5px; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/app/create-course/create-course.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {STEPPER_GLOBAL_OPTIONS} from '@angular/cdk/stepper'; 3 | 4 | 5 | @Component({ 6 | selector: "create-course", 7 | styleUrls: ["create-course.component.scss"], 8 | templateUrl: "create-course.component.html", 9 | standalone: false 10 | }) 11 | export class CreateCourseComponent { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/app/drag-drop/drag-drop.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 6 |

Lessons To Watch:

7 | 8 |
11 | 12 |
13 | 14 |
15 | 16 | {{lesson.description}} 17 | 18 |
19 | 20 | 21 |
22 | 23 |
24 | 25 |
26 | 27 |

Done:

28 | 29 |
32 | 33 |
34 | 35 |
36 | 37 | {{lesson.description}} 38 | 39 |
40 | 41 |
42 | 43 | 44 |
45 | 46 |
47 | -------------------------------------------------------------------------------- /src/app/drag-drop/drag-drop.component.scss: -------------------------------------------------------------------------------- 1 | 2 | @import "../../mixins"; 3 | 4 | 5 | @include drag-drop(); 6 | 7 | 8 | .drag-drop-container { 9 | font-family: "Roboto"; 10 | display: flex; 11 | justify-content: center; 12 | margin-top: 50px; 13 | } 14 | 15 | .lessons-list { 16 | width: 500px; 17 | max-width: 100%; 18 | border: solid 1px #ccc; 19 | min-height: 60px; 20 | display: block; 21 | background: white; 22 | border-radius: 4px; 23 | overflow: hidden; 24 | } 25 | 26 | 27 | .lesson { 28 | padding: 20px 10px; 29 | border-bottom: solid 1px #ccc; 30 | color: rgba(0, 0, 0, 0.87); 31 | display: flex; 32 | flex-direction: row; 33 | align-items: center; 34 | justify-content: space-between; 35 | box-sizing: border-box; 36 | cursor: move; 37 | background: white; 38 | font-size: 14px; 39 | } 40 | 41 | .done { 42 | margin-left: 30px; 43 | } 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/app/drag-drop/drag-drop.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag-drop'; 3 | import {Lesson} from '../model/lesson'; 4 | 5 | 6 | @Component({ 7 | selector: 'drag-drop-example', 8 | templateUrl: "drag-drop.component.html", 9 | styleUrls: ["drag-drop.component.scss"], 10 | standalone: false 11 | }) 12 | export class DragDropComponent { 13 | 14 | lessons = [ 15 | { 16 | id: 120, 17 | 'description': 'Introduction to Angular Material', 18 | 'duration': '4:17', 19 | 'seqNo': 1, 20 | courseId: 11 21 | }, 22 | { 23 | id: 121, 24 | 'description': 'Navigation and Containers', 25 | 'duration': '6:37', 26 | 'seqNo': 2, 27 | courseId: 11 28 | }, 29 | { 30 | id: 122, 31 | 'description': 'Data Tables', 32 | 'duration': '8:03', 33 | 'seqNo': 3, 34 | courseId: 11 35 | }, 36 | { 37 | id: 123, 38 | 'description': 'Dialogs and Overlays', 39 | 'duration': '11:46', 40 | 'seqNo': 4, 41 | courseId: 11 42 | }, 43 | { 44 | id: 124, 45 | 'description': 'Commonly used Form Controls', 46 | 'duration': '7:17', 47 | 'seqNo': 5, 48 | courseId: 11 49 | }, 50 | { 51 | id: 125, 52 | 'description': 'Drag and Drop', 53 | 'duration': '8:16', 54 | 'seqNo': 6, 55 | courseId: 11 56 | }, 57 | { 58 | id: 126, 59 | 'description': 'Responsive Design', 60 | 'duration': '7:28', 61 | 'seqNo': 7, 62 | courseId: 11 63 | }, 64 | { 65 | id: 127, 66 | 'description': 'Tree Component', 67 | 'duration': '11:09', 68 | 'seqNo': 8, 69 | courseId: 11 70 | }, 71 | { 72 | id: 128, 73 | 'description': 'Virtual Scrolling', 74 | 'duration': '3:44', 75 | 'seqNo': 9, 76 | courseId: 11 77 | }, 78 | { 79 | id: 129, 80 | 'description': 'Custom Themes', 81 | 'duration': '8:55', 82 | 'seqNo': 10, 83 | courseId: 11 84 | }, 85 | { 86 | id: 130, 87 | 'description': 'Changing Theme at Runtime', 88 | 'duration': '12:37', 89 | 'seqNo': 11, 90 | courseId: 11 91 | } 92 | ]; 93 | 94 | done = []; 95 | 96 | dropMultiList(event: CdkDragDrop) { 97 | 98 | if (event.previousContainer == event.container) { 99 | moveItemInArray(this.lessons, event.previousIndex, event.currentIndex); 100 | } 101 | else { 102 | transferArrayItem( 103 | event.previousContainer.data, 104 | event.container.data, 105 | event.previousIndex, 106 | event.currentIndex 107 | 108 | ); 109 | } 110 | 111 | } 112 | 113 | 114 | drop(event: CdkDragDrop) { 115 | 116 | console.log("previousIndex = ", event.previousIndex); 117 | 118 | console.log("currentIndex = " + event.currentIndex); 119 | 120 | moveItemInArray(this.lessons, event.previousIndex, event.currentIndex); 121 | 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 6 |

All Courses

7 | 8 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | 45 | -------------------------------------------------------------------------------- /src/app/home/home.component.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | .courses-panel { 4 | max-width: 460px; 5 | margin: 0 auto; 6 | } 7 | 8 | .header { 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | margin-top: 50px; 13 | } 14 | 15 | h2 { 16 | font-family: "Roboto"; 17 | } 18 | 19 | .title { 20 | text-align: center; 21 | margin-right: 15px; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {Course} from "../model/course"; 3 | import {Observable} from "rxjs"; 4 | import {CoursesService} from "../services/courses.service"; 5 | import {map} from "rxjs/operators"; 6 | 7 | @Component({ 8 | selector: 'home', 9 | templateUrl: './home.component.html', 10 | styleUrls: ['./home.component.scss'], 11 | standalone: false 12 | }) 13 | export class HomeComponent implements OnInit { 14 | 15 | beginnerCourses$: Observable; 16 | 17 | advancedCourses$: Observable; 18 | 19 | constructor(private coursesService: CoursesService) { 20 | 21 | } 22 | 23 | ngOnInit() { 24 | 25 | const courses$ = this.coursesService.findAllCourses(); 26 | 27 | this.beginnerCourses$ = courses$.pipe( 28 | map(courses => courses.filter(course => course.category === 'BEGINNER') ) 29 | ); 30 | 31 | this.advancedCourses$ = courses$.pipe( 32 | map(courses => courses.filter(course => course.category === 'ADVANCED') ) 33 | ); 34 | 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/app/model/course.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export interface Course { 4 | id:number; 5 | description:string; 6 | iconUrl: string; 7 | courseListIcon: string; 8 | longDescription: string; 9 | category:string; 10 | lessonsCount:number; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/model/lesson.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export interface Lesson { 4 | id: number; 5 | description: string; 6 | duration: string; 7 | seqNo: number; 8 | courseId: number; 9 | } -------------------------------------------------------------------------------- /src/app/services/course.resolver.ts: -------------------------------------------------------------------------------- 1 | import {inject} from '@angular/core'; 2 | import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router"; 3 | import {Course} from "../model/course"; 4 | import {Observable} from "rxjs"; 5 | import {CoursesService} from "./courses.service"; 6 | 7 | 8 | export function courseResolver(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { 9 | 10 | const coursesService = inject(CoursesService); 11 | 12 | return coursesService.findCourseById(route.params['id']); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/app/services/courses.service.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import {Injectable} from "@angular/core"; 4 | import { HttpClient, HttpParams } from "@angular/common/http"; 5 | import {Observable} from "rxjs"; 6 | import {Course} from "../model/course"; 7 | import {map} from "rxjs/operators"; 8 | import {Lesson} from "../model/lesson"; 9 | 10 | 11 | @Injectable() 12 | export class CoursesService { 13 | 14 | constructor(private http:HttpClient) { 15 | 16 | } 17 | 18 | findCourseById(courseId: number): Observable { 19 | return this.http.get(`/api/courses/${courseId}`); 20 | } 21 | 22 | findAllCourses(): Observable { 23 | return this.http.get('/api/courses') 24 | .pipe( 25 | map(res => res['payload']) 26 | ); 27 | } 28 | 29 | findAllCourseLessons(courseId:number): Observable { 30 | return this.http.get('/api/lessons', { 31 | params: new HttpParams() 32 | .set('courseId', courseId.toString()) 33 | .set('pageNumber', "0") 34 | .set('pageSize', "1000") 35 | }).pipe( 36 | map(res => res["payload"]) 37 | ); 38 | } 39 | 40 | findLessons( 41 | courseId:number, sortOrder = 'asc', 42 | pageNumber = 0, pageSize = 3, sortColumn = 'seqNo'): Observable { 43 | 44 | return this.http.get('/api/lessons', { 45 | params: new HttpParams() 46 | .set('courseId', courseId.toString()) 47 | .set('sortOrder', sortOrder) 48 | .set('pageNumber', pageNumber.toString()) 49 | .set('pageSize', pageSize.toString()) 50 | .set('sortColumn', sortColumn) 51 | }).pipe( 52 | map(res => res["payload"]) 53 | ); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/app/tree-demo/tree-demo.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

Nested Tree Demo

5 | 6 | 9 | 10 | 11 | 12 | {{node.name}} 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 30 | 31 | {{node.name}} 32 | 33 |
34 | 35 |
37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 | 45 |
46 | 47 |

Flat Tree Demo

48 | 49 | 51 | 52 | 53 | 54 | 55 |
56 | 57 | {{node.name}} 58 | 59 |
60 | 61 |
62 | 63 | 64 | 65 | 66 | 71 | 72 | {{node.name}} 73 | 74 | 75 | 76 | 77 | 78 |
79 | 80 | 81 |
82 | -------------------------------------------------------------------------------- /src/app/tree-demo/tree-demo.component.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | .tree-demo-container { 4 | font-family: "Roboto"; 5 | display: flex; 6 | flex-direction: column; 7 | justify-content: center; 8 | padding: 30px; 9 | } 10 | 11 | .leaf-node { 12 | padding-left: 30px; 13 | } 14 | 15 | 16 | 17 | 18 | 19 | .example-tree-invisible { 20 | display: none; 21 | } 22 | 23 | .example-tree ul, 24 | .example-tree li { 25 | margin-top: 0; 26 | margin-bottom: 0; 27 | list-style-type: none; 28 | } 29 | 30 | /* 31 | * This padding sets alignment of the nested nodes. 32 | */ 33 | .example-tree .mat-nested-tree-node .nested-node { 34 | padding-left: 40px; 35 | } 36 | 37 | /* 38 | * Padding for leaf nodes. 39 | * Leaf nodes need to have padding so as to align with other non-leaf nodes 40 | * under the same parent. 41 | */ 42 | .example-tree .nested-node > .mat-tree-node { 43 | padding-left: 40px; 44 | } 45 | -------------------------------------------------------------------------------- /src/app/tree-demo/tree-demo.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {MatTreeFlatDataSource, MatTreeFlattener, MatTreeNestedDataSource} from '@angular/material/tree'; 3 | import {FlatTreeControl, NestedTreeControl} from '@angular/cdk/tree'; 4 | 5 | interface CourseNode { 6 | name: string; 7 | children?: CourseNode[]; 8 | } 9 | 10 | interface CourseFlatNode { 11 | name:string; 12 | expandable:boolean; 13 | level: number; 14 | } 15 | 16 | 17 | const TREE_DATA: CourseNode[] = [ 18 | { 19 | name: 'Angular For Beginners', 20 | children: [ 21 | { 22 | name: 'Introduction to Angular' 23 | }, 24 | { 25 | name: 'Angular Component @Input()' 26 | }, 27 | { 28 | name: 'Angular Component @Output()' 29 | } 30 | ], 31 | }, 32 | { 33 | name: 'Angular Material In Depth', 34 | children: [ 35 | { 36 | name: 'Introduction to Angular Material', 37 | children: [ 38 | { 39 | name: 'Form Components' 40 | }, 41 | { 42 | name: 'Navigation and Containers' 43 | } 44 | ], 45 | }, 46 | { 47 | name: 'Advanced Angular Material', 48 | children: [ 49 | { 50 | name: 'Custom Themes' 51 | }, 52 | { 53 | name: 'Tree Components' 54 | } 55 | ], 56 | }, 57 | ], 58 | }, 59 | ]; 60 | 61 | @Component({ 62 | selector: 'tree-demo', 63 | templateUrl: 'tree-demo.component.html', 64 | styleUrls: ['tree-demo.component.scss'], 65 | standalone: false 66 | }) 67 | export class TreeDemoComponent implements OnInit { 68 | 69 | // Nested Tree 70 | nestedDataSource = new MatTreeNestedDataSource(); 71 | 72 | nestedTreeControl = new NestedTreeControl(node => node.children); 73 | 74 | // Flat Tree 75 | flatTreeControl = new FlatTreeControl( 76 | node => node.level, 77 | node => node.expandable 78 | ); 79 | 80 | treeFlattener = new MatTreeFlattener( 81 | (node:CourseNode, level: number): CourseFlatNode => { 82 | return { 83 | name: node.name, 84 | expandable: node.children?.length > 0, 85 | level 86 | } 87 | }, 88 | node => node.level, 89 | node => node.expandable, 90 | node => node.children 91 | ); 92 | 93 | flatDataSource = new MatTreeFlatDataSource(this.flatTreeControl, this.treeFlattener); 94 | 95 | 96 | 97 | ngOnInit() { 98 | 99 | this.nestedDataSource.data = TREE_DATA; 100 | 101 | this.flatDataSource.data = TREE_DATA; 102 | 103 | } 104 | 105 | hasNestedChild(index: number, node:CourseNode) { 106 | return node?.children?.length > 0; 107 | } 108 | 109 | hasFlatChild(index: number, node:CourseFlatNode) { 110 | return node.expandable; 111 | } 112 | 113 | } 114 | 115 | 116 | -------------------------------------------------------------------------------- /src/app/virtual-scrolling/virtual-scrolling.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 6 |

Virtual Scrolling Demo

7 | 8 | 9 | 10 | 11 | 12 | 18 | 19 | {{item}} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
32 | 33 | -------------------------------------------------------------------------------- /src/app/virtual-scrolling/virtual-scrolling.component.scss: -------------------------------------------------------------------------------- 1 | 2 | .container { 3 | font-family: "Roboto"; 4 | display: flex; 5 | justify-content: center; 6 | padding: 30px; 7 | } 8 | 9 | 10 | .scrolling-container { 11 | display: block; 12 | width: 200px; 13 | height: 200px; 14 | overflow-y: scroll; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/virtual-scrolling/virtual-scrolling.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | 3 | 4 | @Component({ 5 | selector: "virtual-scrolling", 6 | templateUrl: 'virtual-scrolling.component.html', 7 | styleUrls: ["virtual-scrolling.component.scss"], 8 | standalone: false 9 | }) 10 | export class VirtualScrollingComponent implements OnInit { 11 | 12 | items = Array.from({length: 100000}).map((value, i) => `Item #${i}`); 13 | 14 | ngOnInit() { 15 | 16 | } 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/angular-material-course/7cb00690dca823f5554a6e2cf4d5a43c1b440eb8/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/angular-material-course/7cb00690dca823f5554a6e2cf4d5a43c1b440eb8/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular Material In Depth 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | 8 | 9 | if (environment.production) { 10 | enableProdMode(); 11 | } 12 | 13 | platformBrowserDynamic().bootstrapModule(AppModule) 14 | .catch(err => console.log(err)); 15 | -------------------------------------------------------------------------------- /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/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for the Reflect API. */ 38 | // import 'core-js/es6/reflect'; 39 | 40 | 41 | /** Evergreen browsers require these. **/ 42 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 43 | 44 | 45 | 46 | /*************************************************************************************************** 47 | * Zone JS is required by Angular itself. 48 | */ 49 | import 'zone.js'; // Included with Angular CLI. 50 | 51 | 52 | 53 | /*************************************************************************************************** 54 | * APPLICATION IMPORTS 55 | */ 56 | 57 | /** 58 | * Date, currency, decimal and percent pipes. 59 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 60 | */ 61 | // import 'intl'; // Run `npm install --save intl`. 62 | /** 63 | * Need to import at least one locale-data with intl. 64 | */ 65 | // import 'intl/locale-data/jsonp/en'; 66 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | 2 | @use '@angular/material' as mat; 3 | 4 | html, body { height: 100%; } 5 | body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } 6 | 7 | .mat-elevation-z7 { 8 | box-shadow: 0 4px 5px -2px #0003, 0 7px 10px 1px #00000024, 0 2px 16px 1px #0000001f; 9 | } 10 | 11 | 12 | @include mat.elevation-classes(); 13 | @include mat.app-background(); 14 | 15 | $dark-primary-text: rgba(black, 0.87); 16 | 17 | $light-primary-text: white; 18 | 19 | $mat-primary: ( 20 | 50: #e8eaf6, 21 | 100: #c5cae9, 22 | 200: #9fa8da, 23 | 300: #7986cb, 24 | 400: #5c6bc0, 25 | 500: #3f51b5, 26 | 600: #3949ab, 27 | 700: #303f9f, 28 | 800: #283593, 29 | 900: #1a237e, 30 | A100: #8c9eff, 31 | A200: #536dfe, 32 | A400: #3d5afe, 33 | A700: #304ffe, 34 | contrast: ( 35 | 50: $dark-primary-text, 36 | 100: $dark-primary-text, 37 | 200: $dark-primary-text, 38 | 300: $light-primary-text, 39 | 400: $light-primary-text, 40 | 500: $light-primary-text, 41 | 600: $light-primary-text, 42 | 700: $light-primary-text, 43 | 800: $light-primary-text, 44 | 900: $light-primary-text, 45 | A100: $dark-primary-text, 46 | A200: $light-primary-text, 47 | A400: $light-primary-text, 48 | A700: $light-primary-text, 49 | ) 50 | ); 51 | 52 | $mat-accent: ( 53 | 50: #fce4ec, 54 | 100: #f8bbd0, 55 | 200: #f48fb1, 56 | 300: #f06292, 57 | 400: #ec407a, 58 | 500: #e91e63, 59 | 600: #d81b60, 60 | 700: #c2185b, 61 | 800: #ad1457, 62 | 900: #880e4f, 63 | A100: #ff80ab, 64 | A200: #ff4081, 65 | A400: #f50057, 66 | A700: #c51162, 67 | contrast: ( 68 | 50: $dark-primary-text, 69 | 100: $dark-primary-text, 70 | 200: $dark-primary-text, 71 | 300: $dark-primary-text, 72 | 400: $dark-primary-text, 73 | 500: $light-primary-text, 74 | 600: $light-primary-text, 75 | 700: $light-primary-text, 76 | 800: $light-primary-text, 77 | 900: $light-primary-text, 78 | A100: $dark-primary-text, 79 | A200: $light-primary-text, 80 | A400: $light-primary-text, 81 | A700: $light-primary-text, 82 | ) 83 | ); 84 | 85 | 86 | 87 | $primary: mat.m2-define-palette($mat-primary, 500); 88 | 89 | $accent: mat.m2-define-palette($mat-accent, A200, A100, A400); 90 | 91 | $theme: mat.m2-define-light-theme(( 92 | color: ( 93 | primary: $primary, 94 | accent: $accent, 95 | ), 96 | typography: mat.m2-define-typography-config(), 97 | density: 0, 98 | )); 99 | 100 | @include mat.all-component-themes($theme); 101 | 102 | @import "./mixins"; 103 | 104 | @include responsive-dialogs(); 105 | 106 | .highlight-date { 107 | background: red; 108 | } 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /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/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare const __karma__: any; 17 | 18 | // Prevent Karma from running prematurely. 19 | __karma__.loaded = function () {}; 20 | 21 | // First, initialize the Angular testing environment. 22 | getTestBed().initTestEnvironment( 23 | BrowserDynamicTestingModule, 24 | platformBrowserDynamicTesting(), { 25 | teardown: { destroyAfterEach: false } 26 | } 27 | ); 28 | // Finally, start Karma to run the tests. 29 | __karma__.start(); 30 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "baseUrl": "./", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "main.ts", 10 | "polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "baseUrl": "./", 6 | "types": [ 7 | "jasmine", 8 | "node" 9 | ] 10 | }, 11 | "files": [ 12 | "test.ts", 13 | "polyfills.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "module": "es2020", 5 | "esModuleInterop": true, 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "moduleResolution": "node", 10 | "experimentalDecorators": true, 11 | "target": "ES2022", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2017", 17 | "dom", 18 | "ES2017.object" 19 | ], 20 | "useDefineForClassFields": false 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warning" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-shadowed-variable": true, 69 | "no-string-literal": false, 70 | "no-string-throw": true, 71 | "no-switch-case-fall-through": true, 72 | "no-trailing-whitespace": true, 73 | "no-unnecessary-initializer": true, 74 | "no-unused-expression": true, 75 | "no-var-keyword": true, 76 | "object-literal-sort-keys": false, 77 | "one-line": [ 78 | true, 79 | "check-open-brace", 80 | "check-catch", 81 | "check-else", 82 | "check-whitespace" 83 | ], 84 | "prefer-const": true, 85 | "quotemark": [ 86 | true, 87 | "single" 88 | ], 89 | "radix": true, 90 | "semicolon": [ 91 | true, 92 | "always" 93 | ], 94 | "triple-equals": [ 95 | true, 96 | "allow-null-check" 97 | ], 98 | "typedef-whitespace": [ 99 | true, 100 | { 101 | "call-signature": "nospace", 102 | "index-signature": "nospace", 103 | "parameter": "nospace", 104 | "property-declaration": "nospace", 105 | "variable-declaration": "nospace" 106 | } 107 | ], 108 | "typeof-compare": true, 109 | "unified-signatures": true, 110 | "variable-name": false, 111 | "whitespace": [ 112 | true, 113 | "check-branch", 114 | "check-decl", 115 | "check-operator", 116 | "check-separator", 117 | "check-type" 118 | ], 119 | "directive-selector": [ 120 | true, 121 | "attribute", 122 | "app", 123 | "camelCase" 124 | ], 125 | "component-selector": [ 126 | true, 127 | "element", 128 | "app", 129 | "kebab-case" 130 | ], 131 | "no-inputs-metadata-property": true, 132 | "no-outputs-metadata-property": true, 133 | "no-host-metadata-property": true, 134 | "no-input-rename": true, 135 | "no-output-rename": true, 136 | "use-lifecycle-interface": true, 137 | "use-pipe-transform-interface": true, 138 | "component-class-suffix": true, 139 | "directive-class-suffix": true, 140 | "invoke-injectable": true 141 | } 142 | } 143 | --------------------------------------------------------------------------------