├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── angular.json ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.json ├── images ├── gof-observer.png └── loading.gif ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── proxy.conf.json ├── src ├── app │ ├── all-lessons │ │ ├── all-lessons.component.css │ │ ├── all-lessons.component.html │ │ ├── all-lessons.component.spec.ts │ │ └── all-lessons.component.ts │ ├── app.component.css │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── browser-event-experiments │ │ ├── browser-event-experiments.component.css │ │ ├── browser-event-experiments.component.html │ │ ├── browser-event-experiments.component.spec.ts │ │ └── browser-event-experiments.component.ts │ ├── course-detail-header │ │ ├── course-detail-header.component.css │ │ ├── course-detail-header.component.html │ │ ├── course-detail-header.component.spec.ts │ │ └── course-detail-header.component.ts │ ├── course-detail │ │ ├── course-detail.component.css │ │ ├── course-detail.component.html │ │ ├── course-detail.component.ts │ │ └── course-detail.resolver.ts │ ├── course │ │ ├── course.component.css │ │ ├── course.component.html │ │ ├── course.component.spec.ts │ │ └── course.component.ts │ ├── courses-list │ │ ├── courses-list.component.css │ │ ├── courses-list.component.html │ │ ├── courses-list.component.spec.ts │ │ └── courses-list.component.ts │ ├── create-lesson │ │ ├── create-lesson.component.css │ │ ├── create-lesson.component.html │ │ ├── create-lesson.component.spec.ts │ │ └── create-lesson.component.ts │ ├── event-bus-experiments │ │ ├── app-data.ts │ │ ├── event-bus-experiments.component.css │ │ ├── event-bus-experiments.component.html │ │ └── event-bus-experiments.component.ts │ ├── home │ │ ├── home.component.css │ │ ├── home.component.html │ │ ├── home.component.spec.ts │ │ └── home.component.ts │ ├── lesson-detail │ │ ├── lesson-detail.component.css │ │ ├── lesson-detail.component.html │ │ ├── lesson-detail.component.spec.ts │ │ └── lesson-detail.component.ts │ ├── lessons-counter │ │ ├── lessons-counter.component.css │ │ ├── lessons-counter.component.html │ │ ├── lessons-counter.component.spec.ts │ │ └── lessons-counter.component.ts │ ├── lessons-list │ │ ├── lessons-list.component.css │ │ ├── lessons-list.component.html │ │ └── lessons-list.component.ts │ ├── loading │ │ ├── loading.component.css │ │ ├── loading.component.html │ │ ├── loading.component.spec.ts │ │ └── loading.component.ts │ ├── login │ │ ├── login.component.css │ │ ├── login.component.html │ │ ├── login.component.spec.ts │ │ └── login.component.ts │ ├── messages │ │ ├── messages.component.css │ │ ├── messages.component.html │ │ ├── messages.component.spec.ts │ │ └── messages.component.ts │ ├── newsletter │ │ ├── newsletter.component.css │ │ ├── newsletter.component.html │ │ ├── newsletter.component.spec.ts │ │ └── newsletter.component.ts │ ├── router.config.ts │ ├── services │ │ ├── courses-http.service.spec.ts │ │ ├── courses-http.service.ts │ │ ├── courses.service.spec.ts │ │ ├── courses.service.ts │ │ ├── lessons-pager.service.spec.ts │ │ ├── lessons-pager.service.ts │ │ ├── messages.service.spec.ts │ │ ├── messages.service.ts │ │ ├── newsletter.service.spec.ts │ │ ├── newsletter.service.ts │ │ ├── user.service.spec.ts │ │ └── user.service.ts │ ├── shared │ │ ├── model │ │ │ ├── course.ts │ │ │ ├── lesson.ts │ │ │ ├── test-lessons.ts │ │ │ └── user.ts │ │ └── pipes │ │ │ └── safe-url.pipe.ts │ └── top-menu │ │ ├── top-menu.component.css │ │ ├── top-menu.component.html │ │ ├── top-menu.component.spec.ts │ │ └── top-menu.component.ts ├── assets │ ├── .gitkeep │ └── common.css ├── environments │ ├── environment.prod.ts │ ├── environment.ts │ └── firebase.config.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── server │ ├── courseRoute.ts │ ├── db-data.ts │ ├── lessonDetailRoute.ts │ ├── lessonsRoute.ts │ ├── loginRoute.ts │ ├── newsletterRoute.ts │ └── server.ts ├── styles.css ├── test.ts └── tsconfig.json ├── tslint.json └── yarn.lock /.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 | 7 | # dependencies 8 | /node_modules 9 | 10 | # IDEs and editors 11 | /.idea 12 | .project 13 | .classpath 14 | .c9/ 15 | *.launch 16 | .settings/ 17 | 18 | # IDE - VSCode 19 | .vscode/* 20 | !.vscode/settings.json 21 | !.vscode/tasks.json 22 | !.vscode/launch.json 23 | !.vscode/extensions.json 24 | 25 | # misc 26 | /.sass-cache 27 | /connect.lock 28 | /coverage/* 29 | /libpeerconnection.log 30 | npm-debug.log 31 | testem.log 32 | /typings 33 | 34 | # e2e 35 | /e2e/*.js 36 | /e2e/*.map 37 | 38 | #System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /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 | # THIS COURSE IS ARCHIVED 2 | 3 | This course has been archived and is now replaced by the [Reactive Angular Course](https://github.com/angular-university/reactive-angular-course). 4 | 5 | # Repository Content 6 | 7 | This repository contains of the Angular University [RxJs and Reactive Patterns Angular Architecture Course](https://angular-university.io/course/reactive-angular-architecture-course). 8 | 9 | This course repository is updated to Angular v7, there is a Yarn lock file available. 10 | 11 | [RxJs and Reactive Patterns Angular Architecture Course](https://angular-university.io/course/reactive-angular-architecture-course) 12 | 13 | ![RxJs and Reactive Patterns Angular Architecture Course](https://s3-us-west-1.amazonaws.com/angular-academy/blog/images/rxjs-reactive-patterns-small.png) 14 | 15 | # Installation pre-requisites 16 | 17 | This project has minimal dependencies, you only need node and npm installed on your machine. These are some tutorials to install node in different operating systems. 18 | 19 | *Make sure to install the latest version of Node* 20 | 21 | - [Install Node and NPM on Windows](https://www.youtube.com/watch?v=8ODS6RM6x7g) 22 | - [Install Node and NPM on Linux](https://www.youtube.com/watch?v=yUdHk-Dk_BY) 23 | - [Install Node and NPM on Mac](https://www.youtube.com/watch?v=Imj8PgG3bZU) 24 | 25 | 26 | # Installation Instructions 27 | 28 | First clone or download as a Zip file using the green "Clone Or Download" button on the top right of the document. 29 | 30 | Then change directory to the folder 01-getting-started-with-angular2, where you will find a small node project with a `package.json`. 31 | 32 | On the command line run the following: 33 | 34 | npm install 35 | 36 | If you prefer the Yarn package manager, you can also run: 37 | 38 | yarn 39 | 40 | Although npm install would also work, its recommended to use Yarn to install the course dependencies. Yarn has the big advantage that if you use it you will be 41 | installing the exact same dependencies than I installed in my machine, so you wont run into issues caused by semantic versioning updates. 42 | 43 | This should take a couple of minutes. If there are issues, please post the complete error message in the Questions section of the course. 44 | 45 | # Starting the development server 46 | 47 | To start the server, run the following command: 48 | 49 | npm start 50 | 51 | If you now go to [http://localhost:8080](http://localhost:8080), there will be a running application at this url. 52 | 53 | # Making changes 54 | 55 | If you edit a Typescript file and refresh the browser, the changes will be applied. 56 | 57 | # Firebase data for later in the course 58 | In one of the sections of the course, we will be using a Firebase database. If you would like the data to create your own instance, you can find it [here](https://github.com/angular-university/angular-firebase-app/blob/master/db-data.ts). 59 | 60 | This is completely optional, as the course will use a connection to an existing Firebase database, and everything will work out of the box. 61 | 62 | 63 | # Which Course are You Looking For ? 64 | 65 | If you are looking for the code of the following courses, see further the installation instructions on this page: 66 | 67 | - [Angular For Beginners](https://angular-university.io/course/getting-started-with-angular2) 68 | - [Angular Services and HTTP](https://angular-university.io/course/angular2-http) 69 | - [Angular Router](https://angular-university.io/course/angular2-routing) 70 | - [Angular Forms](https://angular-university.io/course/angular2-forms) 71 | 72 | # Angular and Firebase - Build a Web Application Course 73 | 74 | If you are looking for the Angular and Firebase - Build a Web Application Course code, the repo with the full code can be found here: 75 | 76 | [Angular and Firebase - Build a Web Application](https://angular-university.io/course/build-an-application-with-angular2) 77 | 78 | [Github Repo For this course](https://github.com/angular-university/angular-firebase-app) 79 | 80 | ![Angular firebase course](https://angular-academy.s3.amazonaws.com/thumbnails/angular_app-firebase-small-v2.png) 81 | 82 | 83 | # Complete Typescript 2 Course - Build A REST API 84 | 85 | If you are looking for the Complete Typescript 2 Course - Build a REST API, the repo with the full code can be found here: 86 | 87 | [Complete Typescript 2 Course - Build A REST API](https://angular-university.io/course/typescript-2-tutorial) 88 | 89 | [Github Repo for this course](https://github.com/angular-university/complete-typescript-course) 90 | 91 | ![Complete Typescript Course](https://angular-academy.s3.amazonaws.com/thumbnails/typescript-2-small.png) 92 | 93 | 94 | # Angular Ngrx Reactive Extensions Architecture Course 95 | 96 | If you are looking for the Angular Ngrx Reactive Extensions Architecture Course code, the repo with the full code can be found here: 97 | 98 | [Angular Ngrx Reactive Extensions Architecture Course](https://angular-university.io/course/angular2-ngrx) 99 | 100 | [Github repo for this course](https://github.com/angular-university/ngrx-course) 101 | 102 | ![Angular Ngrx Course](https://angular-academy.s3.amazonaws.com/thumbnails/ngrx-angular.png) 103 | 104 | 105 | 106 | ## Brought to you by the Angular University 107 | 108 | Have a look at our course pipeline at [angular-university.io](https://angular-university.io/). 109 | 110 | 111 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "reactive-patterns-course": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "architect": { 11 | "build": { 12 | "builder": "@angular-devkit/build-angular:browser", 13 | "options": { 14 | "outputPath": "dist", 15 | "index": "src/index.html", 16 | "main": "src/main.ts", 17 | "tsConfig": "src/tsconfig.json", 18 | "polyfills": "src/polyfills.ts", 19 | "assets": [ 20 | "src/assets", 21 | "src/favicon.ico" 22 | ], 23 | "styles": [ 24 | "src/styles.css" 25 | ], 26 | "scripts": [] 27 | }, 28 | "configurations": {} 29 | }, 30 | "serve": { 31 | "builder": "@angular-devkit/build-angular:dev-server", 32 | "options": { 33 | "browserTarget": "reactive-patterns-course:build" 34 | }, 35 | "configurations": {} 36 | }, 37 | "extract-i18n": { 38 | "builder": "@angular-devkit/build-angular:extract-i18n", 39 | "options": { 40 | "browserTarget": "reactive-patterns-course:build" 41 | } 42 | }, 43 | "test": { 44 | "builder": "@angular-devkit/build-angular:karma", 45 | "options": { 46 | "main": "src/test.ts", 47 | "karmaConfig": "./karma.conf.js", 48 | "polyfills": "src/polyfills.ts", 49 | "scripts": [], 50 | "styles": [ 51 | "src/styles.css" 52 | ], 53 | "assets": [ 54 | "src/assets", 55 | "src/favicon.ico" 56 | ] 57 | } 58 | }, 59 | "lint": { 60 | "builder": "@angular-devkit/build-angular:tslint", 61 | "options": { 62 | "tsConfig": [ 63 | "src/tsconfig.json" 64 | ], 65 | "exclude": [] 66 | } 67 | } 68 | } 69 | }, 70 | "reactive-patterns-course-e2e": { 71 | "root": "", 72 | "sourceRoot": "", 73 | "projectType": "application", 74 | "architect": { 75 | "e2e": { 76 | "builder": "@angular-devkit/build-angular:protractor", 77 | "options": { 78 | "protractorConfig": "./protractor.conf.js", 79 | "devServerTarget": "reactive-patterns-course:serve" 80 | } 81 | }, 82 | "lint": { 83 | "builder": "@angular-devkit/build-angular:tslint", 84 | "options": { 85 | "tsConfig": [ 86 | "e2e/tsconfig.json" 87 | ], 88 | "exclude": [] 89 | } 90 | } 91 | } 92 | } 93 | }, 94 | "defaultProject": "reactive-patterns-course", 95 | "schematics": { 96 | "@schematics/angular:component": { 97 | "prefix": "", 98 | "styleext": "css" 99 | }, 100 | "@schematics/angular:directive": { 101 | "prefix": "" 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { ReactivePatternsCoursePage } from './app.po'; 2 | 3 | describe('reactive-patterns-course App', function() { 4 | let page: ReactivePatternsCoursePage; 5 | 6 | beforeEach(() => { 7 | page = new ReactivePatternsCoursePage(); 8 | }); 9 | 10 | it('should display message saying app works', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('app works!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | export class ReactivePatternsCoursePage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "es2016" 9 | ], 10 | "module": "commonjs", 11 | "moduleResolution": "node", 12 | "outDir": "../dist/out-tsc-e2e", 13 | "sourceMap": true, 14 | "target": "es6", 15 | "typeRoots": [ 16 | "../node_modules/@types" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /images/gof-observer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/reactive-patterns-course/4ad84f61ea8f6433eea934e7bb95b00d0b8b83ee/images/gof-observer.png -------------------------------------------------------------------------------- /images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/reactive-patterns-course/4ad84f61ea8f6433eea934e7bb95b00d0b8b83ee/images/loading.gif -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/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-coverage-istanbul-reporter'), 12 | require('@angular-devkit/build-angular/plugins/karma') 13 | ], 14 | files: [ 15 | 16 | ], 17 | preprocessors: { 18 | 19 | }, 20 | mime: { 21 | 'text/x-typescript': ['ts','tsx'] 22 | }, 23 | coverageIstanbulReporter: { 24 | dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ], 25 | fixWebpackSourcePaths: true 26 | }, 27 | angularCli: { 28 | config: './angular-cli.json', 29 | environment: 'dev' 30 | }, 31 | reporters: config.angularCli && config.angularCli.codeCoverage 32 | ? ['progress', 'coverage-istanbul'] 33 | : ['progress'], 34 | port: 9876, 35 | colors: true, 36 | logLevel: config.LOG_INFO, 37 | autoWatch: true, 38 | browsers: ['Chrome'], 39 | singleRun: false 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactive-patterns-course", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "angular-cli": {}, 6 | "scripts": { 7 | "ng": "ng", 8 | "start": "./node_modules/.bin/ng serve --proxy-config proxy.conf.json", 9 | "rest-api": "./node_modules/.bin/ts-node ./src/server/server.ts", 10 | "test": "ng test", 11 | "lint": "ng lint", 12 | "e2e": "ng e2e" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/animations": "7.0.0", 17 | "@angular/common": "7.0.0", 18 | "@angular/compiler": "7.0.0", 19 | "@angular/core": "7.0.0", 20 | "@angular/forms": "7.0.0", 21 | "@angular/http": "7.0.0", 22 | "@angular/platform-browser": "7.0.0", 23 | "@angular/platform-browser-dynamic": "7.0.0", 24 | "@angular/router": "7.0.0", 25 | "@types/express": "^4.0.35", 26 | "@types/lodash": "^4.14.52", 27 | "@angular/fire": "^5.1.0", 28 | "body-parser": "^1.17.1", 29 | "cookies-js": "^1.2.3", 30 | "core-js": "^2.4.1", 31 | "express": "^4.15.2", 32 | "firebase": "^5.5.5", 33 | "lodash": "^4.17.4", 34 | "promise-polyfill": "^6.0.2", 35 | "rxjs": "6.3.3", 36 | "rxjs-compat": "^6.0.0-rc.0", 37 | "ts-helpers": "^1.1.1", 38 | "zone.js": "0.8.26" 39 | }, 40 | "devDependencies": { 41 | "@angular-devkit/build-angular": "~0.10.0", 42 | "@angular/cli": "^7.0.0", 43 | "@angular/compiler-cli": "7.0.0", 44 | "@angular/language-service": "7.0.0", 45 | "@types/jasmine": "~2.5.53", 46 | "@types/jasminewd2": "~2.0.2", 47 | "@types/node": "~6.0.60", 48 | "codelyzer": "~4.2.0", 49 | "jasmine-core": "~2.6.2", 50 | "jasmine-spec-reporter": "~4.1.0", 51 | "karma": "~1.7.0", 52 | "karma-chrome-launcher": "~2.1.1", 53 | "karma-cli": "~1.0.1", 54 | "karma-coverage-istanbul-reporter": "^1.2.1", 55 | "karma-jasmine": "~1.1.0", 56 | "karma-jasmine-html-reporter": "^0.2.2", 57 | "protractor": "~5.1.2", 58 | "ts-node": "~3.2.0", 59 | "tslint": "~5.7.0", 60 | "typescript": "~3.1.3" 61 | } 62 | } -------------------------------------------------------------------------------- /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 | /*global jasmine */ 5 | var SpecReporter = require('jasmine-spec-reporter'); 6 | 7 | exports.config = { 8 | allScriptsTimeout: 11000, 9 | specs: [ 10 | './e2e/**/*.e2e-spec.ts' 11 | ], 12 | capabilities: { 13 | 'browserName': 'chrome' 14 | }, 15 | directConnect: true, 16 | baseUrl: 'http://localhost:4200/', 17 | framework: 'jasmine', 18 | jasmineNodeOpts: { 19 | showColors: true, 20 | defaultTimeoutInterval: 30000, 21 | print: function() {} 22 | }, 23 | useAllAngular2AppRoots: true, 24 | beforeLaunch: function() { 25 | require('ts-node').register({ 26 | project: 'e2e' 27 | }); 28 | }, 29 | onPrepare: function() { 30 | jasmine.getEnv().addReporter(new SpecReporter()); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "http://localhost:8090", 4 | "secure": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/app/all-lessons/all-lessons.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/reactive-patterns-course/4ad84f61ea8f6433eea934e7bb95b00d0b8b83ee/src/app/all-lessons/all-lessons.component.css -------------------------------------------------------------------------------- /src/app/all-lessons/all-lessons.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | -------------------------------------------------------------------------------- /src/app/all-lessons/all-lessons.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AllLessonsComponent } from './all-lessons.component'; 4 | 5 | describe('AllLessonsComponent', () => { 6 | let component: AllLessonsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ AllLessonsComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AllLessonsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/all-lessons/all-lessons.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'all-lessons', 5 | templateUrl: './all-lessons.component.html', 6 | styleUrls: ['./all-lessons.component.css'] 7 | }) 8 | export class AllLessonsComponent implements OnInit { 9 | 10 | constructor() { 11 | 12 | 13 | } 14 | 15 | ngOnInit() { 16 | 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/reactive-patterns-course/4ad84f61ea8f6433eea934e7bb95b00d0b8b83ee/src/app/app.component.css -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { AppComponent } from './app.component'; 5 | 6 | describe('AppComponent', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | declarations: [ 10 | AppComponent 11 | ], 12 | }); 13 | TestBed.compileComponents(); 14 | }); 15 | 16 | it('should create the app', async(() => { 17 | const fixture = TestBed.createComponent(AppComponent); 18 | const app = fixture.debugElement.componentInstance; 19 | expect(app).toBeTruthy(); 20 | })); 21 | 22 | it(`should have as title 'app works!'`, async(() => { 23 | const fixture = TestBed.createComponent(AppComponent); 24 | const app = fixture.debugElement.componentInstance; 25 | expect(app.title).toEqual('app works!'); 26 | })); 27 | 28 | it('should render title in a h1 tag', async(() => { 29 | const fixture = TestBed.createComponent(AppComponent); 30 | fixture.detectChanges(); 31 | const compiled = fixture.debugElement.nativeElement; 32 | expect(compiled.querySelector('h1').textContent).toContain('app works!'); 33 | })); 34 | }); 35 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | 10 | title = 'app works!'; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import {BrowserModule} from '@angular/platform-browser'; 2 | import {NgModule} from '@angular/core'; 3 | import {FormsModule, ReactiveFormsModule} from '@angular/forms'; 4 | import {HttpModule} from '@angular/http'; 5 | import {AppComponent} from './app.component'; 6 | import {BrowserEventExperimentsComponent} from './browser-event-experiments/browser-event-experiments.component'; 7 | import {EventBusExperimentsComponent} from './event-bus-experiments/event-bus-experiments.component'; 8 | import {LessonsListComponent} from './lessons-list/lessons-list.component'; 9 | import {LessonsCounterComponent} from './lessons-counter/lessons-counter.component'; 10 | import {HomeComponent} from './home/home.component'; 11 | import {AngularFireModule} from '@angular/fire'; 12 | import {RouterModule} from '@angular/router'; 13 | import {routerConfig} from "./router.config"; 14 | import {CourseDetailComponent} from './course-detail/course-detail.component'; 15 | import {CoursesService} from "./services/courses.service"; 16 | import {CoursesListComponent} from './courses-list/courses-list.component'; 17 | import {CourseDetailHeaderComponent} from './course-detail-header/course-detail-header.component'; 18 | import {NewsletterComponent} from './newsletter/newsletter.component'; 19 | import {NewsletterService} from "./services/newsletter.service"; 20 | import {TopMenuComponent} from './top-menu/top-menu.component'; 21 | import {LoginComponent} from './login/login.component'; 22 | import {UserService} from "./services/user.service"; 23 | import {AllLessonsComponent} from './all-lessons/all-lessons.component'; 24 | import {CourseComponent} from './course/course.component'; 25 | import {LessonDetailComponent} from './lesson-detail/lesson-detail.component'; 26 | import {CoursesHttpService} from "./services/courses-http.service"; 27 | import {SafeUrlPipe} from "./shared/pipes/safe-url.pipe"; 28 | import { MessagesComponent } from './messages/messages.component'; 29 | import {MessagesService} from "./services/messages.service"; 30 | import { CreateLessonComponent } from './create-lesson/create-lesson.component'; 31 | import {CourseDetailResolver} from "./course-detail/course-detail.resolver"; 32 | import { LoadingComponent } from './loading/loading.component'; 33 | import {firebaseConfig} from "../environments/firebase.config"; 34 | import {AngularFireDatabaseModule} from "@angular/fire/database"; 35 | 36 | 37 | @NgModule({ 38 | declarations: [ 39 | AppComponent, 40 | BrowserEventExperimentsComponent, 41 | EventBusExperimentsComponent, 42 | LessonsListComponent, 43 | LessonsCounterComponent, 44 | HomeComponent, 45 | CourseDetailComponent, 46 | CoursesListComponent, 47 | CourseDetailHeaderComponent, 48 | NewsletterComponent, 49 | TopMenuComponent, 50 | LoginComponent, 51 | AllLessonsComponent, 52 | CourseComponent, 53 | LessonDetailComponent, 54 | SafeUrlPipe, 55 | MessagesComponent, 56 | CreateLessonComponent, 57 | LoadingComponent 58 | ], 59 | imports: [ 60 | BrowserModule, 61 | HttpModule, 62 | FormsModule, 63 | AngularFireModule.initializeApp(firebaseConfig), 64 | RouterModule.forRoot(routerConfig), 65 | ReactiveFormsModule, 66 | AngularFireDatabaseModule 67 | ], 68 | providers: [ 69 | CoursesService, 70 | NewsletterService, 71 | UserService, 72 | CoursesHttpService, 73 | MessagesService, 74 | CourseDetailResolver 75 | ], 76 | bootstrap: [AppComponent] 77 | }) 78 | export class AppModule { 79 | } 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/app/browser-event-experiments/browser-event-experiments.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/reactive-patterns-course/4ad84f61ea8f6433eea934e7bb95b00d0b8b83ee/src/app/browser-event-experiments/browser-event-experiments.component.css -------------------------------------------------------------------------------- /src/app/browser-event-experiments/browser-event-experiments.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

Browser Events Experiments

5 | 6 |
7 | 8 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /src/app/browser-event-experiments/browser-event-experiments.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { BrowserEventExperimentsComponent } from './browser-event-experiments.component'; 7 | 8 | describe('BrowserEventExperimentsComponent', () => { 9 | let component: BrowserEventExperimentsComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ BrowserEventExperimentsComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(BrowserEventExperimentsComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/browser-event-experiments/browser-event-experiments.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import {fromEvent, Subscription} from 'rxjs'; 3 | 4 | @Component({ 5 | selector: 'browser-event-experiments', 6 | templateUrl: './browser-event-experiments.component.html', 7 | styleUrls: ['./browser-event-experiments.component.css'] 8 | }) 9 | export class BrowserEventExperimentsComponent implements OnInit { 10 | 11 | hoverSection: HTMLElement; 12 | 13 | sub: Subscription; 14 | 15 | ngOnInit() { 16 | 17 | this.hoverSection = document.getElementById('hover'); 18 | 19 | const mouseMove$ = fromEvent(this.hoverSection, 'mousemove'); 20 | 21 | const click$ = fromEvent(this.hoverSection, 'click'); 22 | 23 | const combined$ = click$.withLatestFrom(mouseMove$) 24 | .map(events => events[1]); 25 | 26 | combined$.subscribe(console.log); 27 | 28 | } 29 | 30 | unsubscribe() { 31 | console.log('Called unsubscribe()'); 32 | this.sub.unsubscribe(); 33 | 34 | } 35 | 36 | } 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/app/course-detail-header/course-detail-header.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/reactive-patterns-course/4ad84f61ea8f6433eea934e7bb95b00d0b8b83ee/src/app/course-detail-header/course-detail-header.component.css -------------------------------------------------------------------------------- /src/app/course-detail-header/course-detail-header.component.html: -------------------------------------------------------------------------------- 1 | 2 |

{{course?.description}}

3 | 4 |
Total lessons: {{lessons?.length}}
5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/course-detail-header/course-detail-header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CourseDetailHeaderComponent } from './course-detail-header.component'; 4 | 5 | describe('CourseDetailHeaderComponent', () => { 6 | let component: CourseDetailHeaderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ CourseDetailHeaderComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CourseDetailHeaderComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/course-detail-header/course-detail-header.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, Input, EventEmitter, Output, ChangeDetectionStrategy} from '@angular/core'; 2 | import {Course} from "../shared/model/course"; 3 | import {Lesson} from "../shared/model/lesson"; 4 | 5 | @Component({ 6 | selector: 'course-detail-header', 7 | templateUrl: './course-detail-header.component.html', 8 | styleUrls: ['./course-detail-header.component.css'], 9 | changeDetection: ChangeDetectionStrategy.OnPush 10 | 11 | }) 12 | export class CourseDetailHeaderComponent { 13 | 14 | @Input() 15 | course: Course; 16 | 17 | @Input() 18 | lessons: Lesson[]; 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/app/course-detail/course-detail.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/reactive-patterns-course/4ad84f61ea8f6433eea934e7bb95b00d0b8b83ee/src/app/course-detail/course-detail.component.css -------------------------------------------------------------------------------- /src/app/course-detail/course-detail.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

{{ (course$ | async)?.description }}

5 | 6 | 7 | 8 |
9 | 10 | -------------------------------------------------------------------------------- /src/app/course-detail/course-detail.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {ActivatedRoute} from '@angular/router'; 3 | import {Course} from "../shared/model/course"; 4 | import {Lesson} from "../shared/model/lesson"; 5 | import {Observable} from "rxjs"; 6 | 7 | 8 | @Component({ 9 | selector: 'course-detail', 10 | templateUrl: './course-detail.component.html', 11 | styleUrls: ['./course-detail.component.css'] 12 | }) 13 | export class CourseDetailComponent implements OnInit { 14 | 15 | course$: Observable; 16 | lessons$: Observable; 17 | 18 | constructor(private route: ActivatedRoute) { 19 | 20 | } 21 | 22 | ngOnInit() { 23 | 24 | this.course$ = this.route.data.map(data => data['detail'][0]); 25 | 26 | this.lessons$ = this.route.data.map(data => data['detail'][1]); 27 | 28 | } 29 | 30 | 31 | } 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/app/course-detail/course-detail.resolver.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import {Resolve, ActivatedRouteSnapshot, RouterStateSnapshot} from "@angular/router"; 4 | import {Course} from "../shared/model/course"; 5 | import {Lesson} from "../shared/model/lesson"; 6 | import {Observable} from "rxjs"; 7 | import {Injectable} from "@angular/core"; 8 | import {CoursesService} from "../services/courses.service"; 9 | 10 | 11 | @Injectable() 12 | export class CourseDetailResolver implements Resolve<[Course,Lesson[]]> { 13 | 14 | constructor(private coursesService: CoursesService) {} 15 | 16 | resolve( 17 | route: ActivatedRouteSnapshot, 18 | state: RouterStateSnapshot): Observable<[Course, (Lesson[])]> { 19 | 20 | return this.coursesService.findCourseByUrl(route.params['id']) 21 | .switchMap( 22 | course => this.coursesService.findLessonsForCourse(course.id).map(lessons => [course,lessons]) 23 | ); 24 | 25 | 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/app/course/course.component.css: -------------------------------------------------------------------------------- 1 | 2 | .course-md { 3 | margin-top: 40px; 4 | } 5 | 6 | .lessons-nav { 7 | margin-bottom: 20px; 8 | } -------------------------------------------------------------------------------- /src/app/course/course.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 |

{{ (course$ | async)?.description }}

7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 |
23 | 24 | 26 | 27 |
28 | 29 | 30 | 31 |
32 | 33 | -------------------------------------------------------------------------------- /src/app/course/course.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CourseComponent } from './course.component'; 4 | 5 | describe('CourseComponent', () => { 6 | let component: CourseComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ CourseComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CourseComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/course/course.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, Input, OnDestroy} from '@angular/core'; 2 | import {Observable} from "rxjs"; 3 | import {Lesson} from "../shared/model/lesson"; 4 | import {CoursesHttpService} from "../services/courses-http.service"; 5 | import {Course} from "../shared/model/course"; 6 | import {LessonsPagerService} from "../services/lessons-pager.service"; 7 | import {MessagesService} from "../services/messages.service"; 8 | 9 | @Component({ 10 | selector: 'course', 11 | templateUrl: './course.component.html', 12 | styleUrls: ['./course.component.css'], 13 | providers: [LessonsPagerService, MessagesService] 14 | }) 15 | export class CourseComponent implements OnInit, OnDestroy { 16 | 17 | @Input() 18 | id: number; 19 | 20 | course$: Observable; 21 | lessons$: Observable; 22 | 23 | detail$: Observable; 24 | 25 | constructor(private coursesService: CoursesHttpService, 26 | private lessonsPager:LessonsPagerService, 27 | private messagesService:MessagesService) { 28 | 29 | } 30 | 31 | ngOnInit() { 32 | this.course$ = this.coursesService.findCourseById(this.id); 33 | this.lessons$ = this.lessonsPager.lessonsPage$; 34 | 35 | this.lessonsPager.loadFirstPage(this.id) 36 | .subscribe( 37 | () => {}, 38 | err => this.messagesService.error('Could not load first page') 39 | ); 40 | } 41 | 42 | previousLessonsPage() { 43 | this.lessonsPager.previous().subscribe( 44 | () => {}, 45 | err => this.messagesService.error('Could not load previous page') 46 | ); 47 | } 48 | 49 | nextLessonsPage() { 50 | this.lessonsPager.next().subscribe( 51 | () => {}, 52 | err => this.messagesService.error('Could not load next page') 53 | ); 54 | } 55 | 56 | selectDetail(lesson:Lesson) { 57 | this.detail$ = this.coursesService.findLessonDetailById(lesson.url); 58 | } 59 | 60 | backToMaster() { 61 | this.detail$ = undefined; 62 | } 63 | 64 | ngOnDestroy() { 65 | console.log('destroying CourseComponent ...'); 66 | } 67 | 68 | } 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/app/courses-list/courses-list.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/reactive-patterns-course/4ad84f61ea8f6433eea934e7bb95b00d0b8b83ee/src/app/courses-list/courses-list.component.css -------------------------------------------------------------------------------- /src/app/courses-list/courses-list.component.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 13 | 14 |
5 | {{course.description}} 6 | 8 | 12 |
15 | 16 | 17 |
Loading ...
18 |
-------------------------------------------------------------------------------- /src/app/courses-list/courses-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CoursesListComponent } from './courses-list.component'; 4 | 5 | describe('CoursesListComponent', () => { 6 | let component: CoursesListComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ CoursesListComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CoursesListComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/courses-list/courses-list.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, Input} from '@angular/core'; 2 | import {Course} from "../shared/model/course"; 3 | 4 | @Component({ 5 | selector: 'courses-list', 6 | templateUrl: './courses-list.component.html', 7 | styleUrls: ['./courses-list.component.css'] 8 | }) 9 | export class CoursesListComponent implements OnInit { 10 | 11 | @Input() 12 | courses: Course[]; 13 | 14 | constructor() { 15 | 16 | } 17 | 18 | ngOnInit() { 19 | 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/app/create-lesson/create-lesson.component.css: -------------------------------------------------------------------------------- 1 | 2 | .lesson-form { 3 | width: 320px; 4 | margin: 0 auto; 5 | } 6 | 7 | form label { 8 | width: 100px; 9 | display: inline-block; 10 | text-align: right; 11 | vertical-align: top; 12 | margin-right: 5px; 13 | } 14 | 15 | .form-field { 16 | margin-bottom: 15px; 17 | } 18 | 19 | 20 | .form-buttons { 21 | margin-top: 30px; 22 | text-align: left; 23 | margin-left: 15px; 24 | } -------------------------------------------------------------------------------- /src/app/create-lesson/create-lesson.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

Create New Lesson

5 | 6 |
7 | 8 |
9 | Lesson 10 |
11 | 12 | 13 |
14 |
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 | -------------------------------------------------------------------------------- /src/app/create-lesson/create-lesson.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CreateLessonComponent } from './create-lesson.component'; 4 | 5 | describe('CreateLessonComponent', () => { 6 | let component: CreateLessonComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ CreateLessonComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CreateLessonComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/create-lesson/create-lesson.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {FormGroup, FormBuilder, Validators} from "@angular/forms"; 3 | import * as Cookies from 'cookies-js'; 4 | 5 | @Component({ 6 | selector: 'create-lesson', 7 | templateUrl: './create-lesson.component.html', 8 | styleUrls: ['./create-lesson.component.css'] 9 | }) 10 | export class CreateLessonComponent implements OnInit { 11 | 12 | private static readonly DRAFT_COOKIE = 'create-lesson-draft'; 13 | 14 | form: FormGroup; 15 | 16 | constructor(private fb: FormBuilder) { 17 | 18 | this.form = this.fb.group({ 19 | description: ['',Validators.required], 20 | url: ['',Validators.required], 21 | longDescription: [''] 22 | }); 23 | 24 | } 25 | 26 | ngOnInit() { 27 | 28 | const draft = Cookies.get(CreateLessonComponent.DRAFT_COOKIE); 29 | 30 | if (draft) { 31 | this.form.setValue(JSON.parse(draft)); 32 | } 33 | 34 | this.form.valueChanges 35 | .filter(() => this.form.valid) 36 | .do(validValue => Cookies.set( 37 | CreateLessonComponent.DRAFT_COOKIE, 38 | JSON.stringify(validValue)) ) 39 | .subscribe(); 40 | 41 | } 42 | 43 | } 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/app/event-bus-experiments/app-data.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as _ from 'lodash'; 3 | import {Lesson} from "../shared/model/lesson"; 4 | 5 | import {Subject, Observable, Observer,BehaviorSubject } from 'rxjs'; 6 | 7 | 8 | 9 | 10 | 11 | class DataStore { 12 | 13 | private lessonsListSubject = new BehaviorSubject([]); 14 | 15 | public lessonsList$: Observable = this.lessonsListSubject.asObservable(); 16 | 17 | initializeLessonsList(newList: Lesson[]) { 18 | this.lessonsListSubject.next(_.cloneDeep(newList)); 19 | } 20 | 21 | addLesson(newLesson: Lesson) { 22 | const lessons = this.cloneLessons(); 23 | lessons.push(_.cloneDeep(newLesson)); 24 | 25 | this.lessonsListSubject.next(lessons); 26 | } 27 | 28 | deleteLesson(deleted:Lesson) { 29 | 30 | const lessons = this.cloneLessons(); 31 | 32 | _.remove(lessons, lesson => lesson.id === deleted.id ); 33 | 34 | this.lessonsListSubject.next(lessons); 35 | } 36 | 37 | toggleLessonViewed(toggled:Lesson) { 38 | 39 | const lessons = this.cloneLessons(); 40 | 41 | const lesson = _.find(lessons, lesson => lesson.id === toggled.id); 42 | 43 | lesson.completed = ! lesson.completed; 44 | this.lessonsListSubject.next(lessons); 45 | } 46 | 47 | private cloneLessons() { 48 | return _.cloneDeep(this.lessonsListSubject.getValue()); 49 | } 50 | 51 | } 52 | 53 | export const store = new DataStore(); 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/app/event-bus-experiments/event-bus-experiments.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/reactive-patterns-course/4ad84f61ea8f6433eea934e7bb95b00d0b8b83ee/src/app/event-bus-experiments/event-bus-experiments.component.css -------------------------------------------------------------------------------- /src/app/event-bus-experiments/event-bus-experiments.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Introducing RxJs

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 |
-------------------------------------------------------------------------------- /src/app/event-bus-experiments/event-bus-experiments.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {testLessons} from "../shared/model/test-lessons"; 3 | import {Lesson} from "../shared/model/lesson"; 4 | import {store} from "./app-data"; 5 | 6 | @Component({ 7 | selector: 'event-bus-experiments', 8 | templateUrl: './event-bus-experiments.component.html', 9 | styleUrls: ['./event-bus-experiments.component.css'] 10 | }) 11 | export class EventBusExperimentsComponent implements OnInit { 12 | 13 | ngOnInit() { 14 | 15 | console.log('Top level component broadcasted all lessons ...'); 16 | 17 | store.initializeLessonsList(testLessons.slice(0)); 18 | 19 | setTimeout(() => { 20 | 21 | const newLesson:any = { 22 | id: Math.random(), 23 | description: 'New lesson arriving from the backend' 24 | }; 25 | 26 | store.addLesson(newLesson); 27 | 28 | }, 10000); 29 | 30 | } 31 | 32 | addLesson(lessonText: string) { 33 | const newLesson:any = { 34 | id: Math.random(), 35 | description: lessonText 36 | }; 37 | 38 | store.addLesson(newLesson); 39 | } 40 | 41 | } 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/app/home/home.component.css: -------------------------------------------------------------------------------- 1 | 2 | .button-highlight { 3 | margin-bottom: 20px; 4 | } -------------------------------------------------------------------------------- /src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 |

Smart vs Presentational Components

7 | 8 | 9 | 10 |

Latest Lessons Published

11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/app/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { HomeComponent } from './home.component'; 7 | 8 | describe('HomeComponent', () => { 9 | let component: HomeComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ HomeComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(HomeComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {Course} from "../shared/model/course"; 3 | import {Lesson} from "../shared/model/lesson"; 4 | import {CoursesService} from "../services/courses.service"; 5 | import {Observable} from 'rxjs'; 6 | 7 | @Component({ 8 | selector: 'home', 9 | templateUrl: './home.component.html', 10 | styleUrls: ['./home.component.css'] 11 | }) 12 | export class HomeComponent implements OnInit { 13 | 14 | courses$: Observable; 15 | latestLessons$: Observable; 16 | 17 | constructor(private coursesService: CoursesService) { 18 | 19 | } 20 | 21 | ngOnInit() { 22 | 23 | this.courses$ = this.coursesService.findAllCourses(); 24 | 25 | this.latestLessons$ = this.coursesService.findLatestLessons(); 26 | 27 | 28 | 29 | } 30 | 31 | } 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/app/lesson-detail/lesson-detail.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/reactive-patterns-course/4ad84f61ea8f6433eea934e7bb95b00d0b8b83ee/src/app/lesson-detail/lesson-detail.component.css -------------------------------------------------------------------------------- /src/app/lesson-detail/lesson-detail.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

{{ lesson?.description }}

4 |
Duration: {{ lesson?.duration }}
5 | 6 | 11 | 12 |
Description
13 |

{{ lesson?.longDescription }}

14 | 15 |
16 | 17 |
-------------------------------------------------------------------------------- /src/app/lesson-detail/lesson-detail.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LessonDetailComponent } from './lesson-detail.component'; 4 | 5 | describe('LessonDetailComponent', () => { 6 | let component: LessonDetailComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LessonDetailComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LessonDetailComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/lesson-detail/lesson-detail.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, Input} from '@angular/core'; 2 | import {Lesson} from "../shared/model/lesson"; 3 | 4 | @Component({ 5 | selector: 'lesson-detail', 6 | templateUrl: './lesson-detail.component.html', 7 | styleUrls: ['./lesson-detail.component.css'] 8 | }) 9 | export class LessonDetailComponent implements OnInit { 10 | 11 | @Input() 12 | lesson: Lesson; 13 | 14 | constructor() { } 15 | 16 | ngOnInit() { 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/app/lessons-counter/lessons-counter.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/reactive-patterns-course/4ad84f61ea8f6433eea934e7bb95b00d0b8b83ee/src/app/lessons-counter/lessons-counter.component.css -------------------------------------------------------------------------------- /src/app/lessons-counter/lessons-counter.component.html: -------------------------------------------------------------------------------- 1 |

2 | Total Lessons: {{ lessonsCounter }} 3 |

4 | -------------------------------------------------------------------------------- /src/app/lessons-counter/lessons-counter.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { LessonsCounterComponent } from './lessons-counter.component'; 7 | 8 | describe('LessonsCounterComponent', () => { 9 | let component: LessonsCounterComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ LessonsCounterComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(LessonsCounterComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/lessons-counter/lessons-counter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import {Lesson} from "../shared/model/lesson"; 3 | import {store} from "../event-bus-experiments/app-data"; 4 | import {Observer} from 'rxjs'; 5 | 6 | @Component({ 7 | selector: 'lessons-counter', 8 | templateUrl: './lessons-counter.component.html', 9 | styleUrls: ['./lessons-counter.component.css'] 10 | }) 11 | export class LessonsCounterComponent implements Observer, OnInit { 12 | 13 | lessonsCounter = 0; 14 | 15 | ngOnInit() { 16 | 17 | console.log('lesson list component is registered as observer ..'); 18 | 19 | store.lessonsList$.subscribe(this); 20 | 21 | } 22 | 23 | next(data: Lesson[]) { 24 | console.log('counter component received data ..'); 25 | this.lessonsCounter = data.length; 26 | } 27 | 28 | error(err: any) { 29 | console.error(err); 30 | }; 31 | 32 | 33 | complete() { 34 | console.log('completed'); 35 | }; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/app/lessons-list/lessons-list.component.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | table { 4 | max-width: 300px; 5 | margin: 0 auto 30px auto; 6 | padding: 0 10px; 7 | } -------------------------------------------------------------------------------- /src/app/lessons-list/lessons-list.component.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 10 | 11 | 12 |
{{lesson.description}} 7 | access_time 8 | {{lesson.duration}} 9 |
13 | 14 | 15 |
Loading ...
16 |
-------------------------------------------------------------------------------- /src/app/lessons-list/lessons-list.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, Input, Output, EventEmitter} from '@angular/core'; 2 | import {Lesson} from "../shared/model/lesson"; 3 | import * as _ from 'lodash'; 4 | import {store} from "../event-bus-experiments/app-data"; 5 | import {Observer} from 'rxjs'; 6 | 7 | @Component({ 8 | selector: 'lessons-list', 9 | templateUrl: './lessons-list.component.html', 10 | styleUrls: ['./lessons-list.component.css'] 11 | }) 12 | export class LessonsListComponent { 13 | 14 | @Input() 15 | lessons: Lesson[]; 16 | 17 | @Output() 18 | selected = new EventEmitter(); 19 | 20 | 21 | 22 | select(lesson:Lesson) { 23 | this.selected.next(lesson); 24 | } 25 | 26 | } 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/app/loading/loading.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/reactive-patterns-course/4ad84f61ea8f6433eea934e7bb95b00d0b8b83ee/src/app/loading/loading.component.css -------------------------------------------------------------------------------- /src/app/loading/loading.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /src/app/loading/loading.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LoadingComponent } from './loading.component'; 4 | 5 | describe('LoadingComponent', () => { 6 | let component: LoadingComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LoadingComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LoadingComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/loading/loading.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import {Observable} from "rxjs"; 3 | import {Router, NavigationStart, RoutesRecognized} from "@angular/router"; 4 | 5 | @Component({ 6 | selector: 'loading', 7 | templateUrl: './loading.component.html', 8 | styleUrls: ['./loading.component.css'] 9 | }) 10 | export class LoadingComponent implements OnInit { 11 | 12 | loading$: Observable; 13 | 14 | constructor(private router: Router) { 15 | 16 | 17 | } 18 | 19 | ngOnInit() { 20 | 21 | this.loading$ = this.router.events 22 | .map(event => event instanceof NavigationStart || 23 | event instanceof RoutesRecognized); 24 | 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/app/login/login.component.css: -------------------------------------------------------------------------------- 1 | 2 | .login-screen { 3 | max-width: 300px; 4 | } 5 | 6 | .login-screen input { 7 | 8 | width: 100%; 9 | margin-bottom: 5px; 10 | } -------------------------------------------------------------------------------- /src/app/login/login.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/login/login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LoginComponent } from './login.component'; 4 | 5 | describe('LoginComponent', () => { 6 | let component: LoginComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LoginComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LoginComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {UserService} from "../services/user.service"; 3 | import {Router} from "@angular/router"; 4 | 5 | @Component({ 6 | selector: 'login', 7 | templateUrl: './login.component.html', 8 | styleUrls: ['./login.component.css'] 9 | }) 10 | export class LoginComponent implements OnInit { 11 | 12 | constructor(private userService:UserService, private router:Router) { 13 | } 14 | 15 | ngOnInit() { 16 | 17 | } 18 | 19 | 20 | login(email:string, password:string) { 21 | 22 | this.userService.login(email, password) 23 | .subscribe( 24 | () => { 25 | alert('Login successful'); 26 | this.router.navigateByUrl('/home') 27 | }, 28 | console.error 29 | ); 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/app/messages/messages.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/reactive-patterns-course/4ad84f61ea8f6433eea934e7bb95b00d0b8b83ee/src/app/messages/messages.component.css -------------------------------------------------------------------------------- /src/app/messages/messages.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | close 4 |
5 |
{{message}}
6 |
7 |
8 |
-------------------------------------------------------------------------------- /src/app/messages/messages.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { MessagesComponent } from './messages.component'; 4 | 5 | describe('MessagesComponent', () => { 6 | let component: MessagesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ MessagesComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(MessagesComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/messages/messages.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import {Observable} from "rxjs"; 3 | import {MessagesService} from "../services/messages.service"; 4 | 5 | @Component({ 6 | selector: 'messages', 7 | templateUrl: './messages.component.html', 8 | styleUrls: ['./messages.component.css'] 9 | }) 10 | export class MessagesComponent implements OnInit { 11 | 12 | errors$: Observable; 13 | 14 | constructor(private messagesService:MessagesService) { 15 | 16 | } 17 | 18 | ngOnInit() { 19 | 20 | this.errors$ = this.messagesService.errors$; 21 | 22 | } 23 | 24 | close() { 25 | this.messagesService.error(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/app/newsletter/newsletter.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/reactive-patterns-course/4ad84f61ea8f6433eea934e7bb95b00d0b8b83ee/src/app/newsletter/newsletter.component.css -------------------------------------------------------------------------------- /src/app/newsletter/newsletter.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/newsletter/newsletter.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NewsletterComponent } from './newsletter.component'; 4 | 5 | describe('NewsletterComponent', () => { 6 | let component: NewsletterComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ NewsletterComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(NewsletterComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/newsletter/newsletter.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, Input, Output, EventEmitter, ChangeDetectionStrategy} from '@angular/core'; 2 | import {NewsletterService} from "../services/newsletter.service"; 3 | import {UserService} from "../services/user.service"; 4 | import {Observable} from "rxjs"; 5 | 6 | @Component({ 7 | selector: 'newsletter', 8 | templateUrl: './newsletter.component.html', 9 | styleUrls: ['./newsletter.component.css'], 10 | changeDetection: ChangeDetectionStrategy.OnPush 11 | }) 12 | export class NewsletterComponent implements OnInit { 13 | 14 | firstName$:Observable; 15 | 16 | constructor( 17 | private userService:UserService, 18 | private newsletterService:NewsletterService) { 19 | 20 | } 21 | 22 | ngOnInit() { 23 | this.firstName$ = this.userService.user$.map(user => user.firstName); 24 | } 25 | 26 | subscribeToNewsletter(emailField) { 27 | this.newsletterService.subscribeToNewsletter(emailField.value) 28 | .subscribe( 29 | () => { 30 | emailField.value = ''; 31 | alert('Subscription successful ...'); 32 | }, 33 | console.error 34 | ); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/app/router.config.ts: -------------------------------------------------------------------------------- 1 | import {Routes} from '@angular/router'; 2 | import {HomeComponent} from "./home/home.component"; 3 | import {CourseDetailComponent} from "./course-detail/course-detail.component"; 4 | import {LoginComponent} from "./login/login.component"; 5 | import {AllLessonsComponent} from "./all-lessons/all-lessons.component"; 6 | import {CreateLessonComponent} from "./create-lesson/create-lesson.component"; 7 | import {CourseDetailResolver} from "./course-detail/course-detail.resolver"; 8 | 9 | export const routerConfig: Routes = [ 10 | { 11 | path: 'home', 12 | component: HomeComponent 13 | }, 14 | { 15 | path: 'login', 16 | component: LoginComponent 17 | }, 18 | { 19 | path: 'all-lessons', 20 | component: AllLessonsComponent 21 | }, 22 | { 23 | path: 'course/:id', 24 | component: CourseDetailComponent, 25 | resolve: { 26 | detail: CourseDetailResolver 27 | } 28 | }, 29 | { 30 | path: 'lesson/new', 31 | component: CreateLessonComponent 32 | }, 33 | { 34 | path: '', 35 | pathMatch: 'full', 36 | redirectTo: '/home' 37 | }, 38 | { 39 | path: '**', 40 | pathMatch: 'full', 41 | redirectTo: '/home' 42 | } 43 | ]; -------------------------------------------------------------------------------- /src/app/services/courses-http.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { CoursesHttpService } from './courses-http.service'; 4 | 5 | describe('CoursesHttpService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [CoursesHttpService] 9 | }); 10 | }); 11 | 12 | it('should ...', inject([CoursesHttpService], (service: CoursesHttpService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/services/courses-http.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Http} from "@angular/http"; 3 | import {Observable} from "rxjs"; 4 | import {Course} from "../shared/model/course"; 5 | import {Lesson} from "../shared/model/lesson"; 6 | 7 | 8 | 9 | @Injectable() 10 | export class CoursesHttpService { 11 | 12 | constructor(private http: Http) { 13 | 14 | 15 | } 16 | 17 | findCourseById(courseId: number): Observable { 18 | return this.http.get(`/api/courses/${courseId}`) 19 | .map(res => res.json()); 20 | } 21 | 22 | 23 | findLessonDetailById(lessonId): Observable { 24 | return this.http.get(`/api/lessons/${lessonId}`) 25 | .map(res => res.json()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/app/services/courses.service.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async, inject } from '@angular/core/testing'; 4 | import { CoursesService } from './courses.service'; 5 | 6 | describe('CoursesService', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | providers: [CoursesService] 10 | }); 11 | }); 12 | 13 | it('should ...', inject([CoursesService], (service: CoursesService) => { 14 | expect(service).toBeTruthy(); 15 | })); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/services/courses.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import {AngularFireDatabase} from "@angular/fire/database"; 3 | import {Observable} from "rxjs/Observable"; 4 | import {Course} from "../shared/model/course"; 5 | import {Lesson} from "../shared/model/lesson"; 6 | import {first, map} from 'rxjs/operators'; 7 | 8 | @Injectable() 9 | export class CoursesService { 10 | 11 | constructor(private db: AngularFireDatabase) { 12 | 13 | } 14 | 15 | findAllCourses(): Observable { 16 | return this.db.list('courses') 17 | .valueChanges() 18 | .first() 19 | .do(console.log); 20 | } 21 | 22 | 23 | findLatestLessons(): Observable { 24 | return this.db.list('lessons', ref => ref.orderByKey().limitToLast(10)) 25 | .valueChanges() 26 | .first() 27 | .do(console.log); 28 | } 29 | 30 | findCourseByUrl(courseUrl:string): Observable { 31 | return this.db.list('courses', ref => ref.orderByChild('url').equalTo(courseUrl)) 32 | .snapshotChanges() 33 | .pipe( 34 | map( changes => { 35 | 36 | const snap = changes[0]; 37 | 38 | return { 39 | id:snap.payload.key, 40 | ...snap.payload.val() 41 | }; 42 | 43 | }), 44 | first() 45 | ) 46 | } 47 | 48 | findLessonsForCourse(courseId:string): Observable { 49 | return this.db.list('lessons', ref => ref.orderByChild('courseId').equalTo(courseId)) 50 | .valueChanges() 51 | .first(); 52 | } 53 | 54 | 55 | } 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/app/services/lessons-pager.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { LessonsPagerService } from './lessons-pager.service'; 4 | 5 | describe('LessonsPagerService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [LessonsPagerService] 9 | }); 10 | }); 11 | 12 | it('should ...', inject([LessonsPagerService], (service: LessonsPagerService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/services/lessons-pager.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Observable, BehaviorSubject} from "rxjs"; 3 | import {Lesson} from "../shared/model/lesson"; 4 | import {Http} from "@angular/http"; 5 | 6 | @Injectable() 7 | export class LessonsPagerService { 8 | 9 | private static readonly PAGE_SIZE = 2; 10 | 11 | private subject = new BehaviorSubject([]); 12 | 13 | lessonsPage$: Observable = this.subject.asObservable(); 14 | 15 | currentPageNumber = 1; 16 | 17 | private courseId: number; 18 | 19 | 20 | constructor(private http:Http) { 21 | console.log('LessonsPagerService instance created ..'); 22 | } 23 | 24 | 25 | loadFirstPage(courseId: number): Observable { 26 | this.courseId = courseId; 27 | this.currentPageNumber = 1; 28 | return this.loadPage(this.currentPageNumber); 29 | } 30 | 31 | previous(): Observable { 32 | if (this.currentPageNumber - 1 >= 1) { 33 | this.currentPageNumber -= 1; 34 | } 35 | return this.loadPage(this.currentPageNumber); 36 | } 37 | 38 | next(): Observable { 39 | this.currentPageNumber += 1; 40 | return this.loadPage(this.currentPageNumber); 41 | } 42 | 43 | 44 | loadPage(pageNumber:number): Observable { 45 | return this.http.get('/api/lessons', { 46 | params: { 47 | courseId: this.courseId, 48 | pageNumber, 49 | pageSize: LessonsPagerService.PAGE_SIZE 50 | } 51 | }) 52 | .map(res => res.json().payload) 53 | .do(lessons => this.subject.next(lessons)) 54 | .publishLast().refCount(); 55 | } 56 | 57 | } 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/app/services/messages.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { MessagesService } from './messages.service'; 4 | 5 | describe('MessagesService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [MessagesService] 9 | }); 10 | }); 11 | 12 | it('should ...', inject([MessagesService], (service: MessagesService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/services/messages.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Observable, BehaviorSubject} from "rxjs"; 3 | 4 | @Injectable() 5 | export class MessagesService { 6 | 7 | 8 | private errorsSubject = new BehaviorSubject([]); 9 | 10 | errors$: Observable = this.errorsSubject.asObservable(); 11 | 12 | 13 | constructor() { 14 | console.log('created MessagesService...'); 15 | } 16 | 17 | error(...errors:string[]) { 18 | this.errorsSubject.next(errors); 19 | } 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/app/services/newsletter.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { NewsletterService } from './newsletter.service'; 4 | 5 | describe('NewsletterService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [NewsletterService] 9 | }); 10 | }); 11 | 12 | it('should ...', inject([NewsletterService], (service: NewsletterService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/services/newsletter.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Http} from "@angular/http"; 3 | import {Observable} from "rxjs"; 4 | 5 | @Injectable() 6 | export class NewsletterService { 7 | 8 | 9 | constructor(private http: Http) { 10 | 11 | } 12 | 13 | subscribeToNewsletter(email: string): Observable { 14 | return this.http.post('/api/newsletter', {email}); 15 | } 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/app/services/user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { UserService } from './user.service'; 4 | 5 | describe('UserService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [UserService] 9 | }); 10 | }); 11 | 12 | it('should ...', inject([UserService], (service: UserService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/services/user.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {User} from "../shared/model/user"; 3 | import {Observable, BehaviorSubject} from "rxjs"; 4 | import {Http, Headers} from "@angular/http"; 5 | 6 | 7 | export const UNKNOWN_USER : User = { 8 | firstName: 'Unknown' 9 | }; 10 | 11 | 12 | @Injectable() 13 | export class UserService { 14 | 15 | private subject = new BehaviorSubject(UNKNOWN_USER); 16 | 17 | user$: Observable = this.subject.asObservable(); 18 | 19 | constructor(private http:Http) { 20 | 21 | 22 | } 23 | 24 | login(email:string, password:string): Observable { 25 | 26 | const headers = new Headers(); 27 | headers.append('Content-Type', 'application/json'); 28 | 29 | return this.http.post('/api/login', {email,password}, {headers}) 30 | .map(res => res.json()) 31 | .do(user => console.log(user)) 32 | .do(user => this.subject.next(user)) 33 | .publishLast().refCount(); 34 | 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/app/shared/model/course.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface Course { 3 | id: string; 4 | url: string; 5 | description: string; 6 | iconUrl: string; 7 | courseListIcon: string; 8 | longDescription: string; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/app/shared/model/lesson.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export interface Lesson { 4 | id: string; 5 | description: string; 6 | seqNo: number; 7 | duration: string; 8 | url?: string; 9 | tags?: string; 10 | pro?: boolean; 11 | longDescription?: string; 12 | courseId?: string; 13 | videoUrl?: string; 14 | } -------------------------------------------------------------------------------- /src/app/shared/model/test-lessons.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import {Lesson} from "./lesson"; 4 | 5 | export const testLessons = [ 6 | { 7 | id: 1, 8 | description: ' Setting Up an Angular Development Environment', 9 | duration: '5:00' 10 | }, 11 | { 12 | id: 2, 13 | description: ' Running the the Lessons Code', 14 | duration: '7:00' 15 | }, 16 | { 17 | id: 3, 18 | description: 'Build Your First App - Hello World Step By Step', 19 | duration: '9:00' 20 | } 21 | ]; 22 | -------------------------------------------------------------------------------- /src/app/shared/model/user.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | export interface User { 6 | firstName: string; 7 | lastName?:string; 8 | } 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/app/shared/pipes/safe-url.pipe.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Pipe, PipeTransform } from '@angular/core'; 3 | import {DomSanitizer} from "@angular/platform-browser"; 4 | 5 | @Pipe({ 6 | name: 'safeUrl' 7 | }) 8 | export class SafeUrlPipe implements PipeTransform { 9 | 10 | constructor(private sanitizer: DomSanitizer) { 11 | 12 | } 13 | 14 | transform(url) { 15 | return this.sanitizer.bypassSecurityTrustResourceUrl(url); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/app/top-menu/top-menu.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/reactive-patterns-course/4ad84f61ea8f6433eea934e7bb95b00d0b8b83ee/src/app/top-menu/top-menu.component.css -------------------------------------------------------------------------------- /src/app/top-menu/top-menu.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/top-menu/top-menu.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TopMenuComponent } from './top-menu.component'; 4 | 5 | describe('TopMenuComponent', () => { 6 | let component: TopMenuComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ TopMenuComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(TopMenuComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/top-menu/top-menu.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {UserService, UNKNOWN_USER} from "../services/user.service"; 3 | import {Observable} from "rxjs"; 4 | 5 | @Component({ 6 | selector: 'top-menu', 7 | templateUrl: './top-menu.component.html', 8 | styleUrls: ['./top-menu.component.css'] 9 | }) 10 | export class TopMenuComponent implements OnInit { 11 | 12 | isLoggedIn$: Observable; 13 | 14 | 15 | constructor(private userService: UserService) { 16 | 17 | 18 | } 19 | 20 | 21 | ngOnInit() { 22 | 23 | this.isLoggedIn$ = this.userService.user$.map(user => user !== UNKNOWN_USER); 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/reactive-patterns-course/4ad84f61ea8f6433eea934e7bb95b00d0b8b83ee/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/common.css: -------------------------------------------------------------------------------- 1 | 2 | .course-container, .screen-container { 3 | max-width: 400px; 4 | margin: 0 auto; 5 | text-align: center; 6 | } 7 | 8 | .hover-me { 9 | border: 1px solid black; 10 | width: 200px; 11 | height: 200px; 12 | margin: 0 auto 30px auto; 13 | } 14 | 15 | .courses-list { 16 | margin: 0 auto 50px auto; 17 | min-width: 350px; 18 | } 19 | 20 | .screen-container { 21 | padding-top: 100px; 22 | } 23 | 24 | .screen-container h2 { 25 | max-width: 300px; 26 | margin: 0 auto 25px auto; 27 | } 28 | 29 | .courses-list tr { 30 | height: 35px; 31 | line-height: 35px; 32 | } 33 | 34 | .courses-list button { 35 | font-size: 16px; 36 | line-height: 16px; 37 | } 38 | 39 | .lessons-list { 40 | max-width: 200px; 41 | margin: 20px auto; 42 | } 43 | 44 | .newsletter { 45 | max-width: 300px; 46 | margin: 0 auto 20px auto; 47 | } 48 | 49 | .loading-indicator { 50 | width: 100%; 51 | position: absolute; 52 | text-align: center; 53 | } 54 | 55 | .loading-indicator img { 56 | width: 75px; 57 | margin-top: 100px; 58 | } 59 | -------------------------------------------------------------------------------- /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/environments/firebase.config.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export const firebaseConfig = { 4 | apiKey: "AIzaSyA0BcUcu4V8aHT_gM-32BhRcmqji4z-lts", 5 | authDomain: "final-project-recording.firebaseapp.com", 6 | databaseURL: "https://final-project-recording.firebaseio.com", 7 | storageBucket: "final-project-recording.appspot.com", 8 | messagingSenderId: "290354329688" 9 | }; -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular-university/reactive-patterns-course/4ad84f61ea8f6433eea934e7bb95b00d0b8b83ee/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ReactivePatternsCourse 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Loading... 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { enableProdMode } from '@angular/core'; 3 | import { environment } from './environments/environment'; 4 | import { AppModule } from './app/app.module'; 5 | 6 | import 'rxjs/add/operator/map'; 7 | import 'rxjs/add/operator/first'; 8 | import 'rxjs/add/operator/do'; 9 | import 'rxjs/add/operator/publishLast'; 10 | import 'rxjs/add/operator/switchMap'; 11 | import 'rxjs/add/operator/filter'; 12 | import 'rxjs/add/operator/withLatestFrom'; 13 | 14 | 15 | if (environment.production) { 16 | enableProdMode(); 17 | } 18 | 19 | platformBrowserDynamic().bootstrapModule(AppModule); 20 | 21 | -------------------------------------------------------------------------------- /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/set'; 35 | 36 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 37 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 38 | 39 | /** IE10 and IE11 requires the following to support `@angular/animation`. */ 40 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 41 | 42 | 43 | /** Evergreen browsers require these. **/ 44 | import 'core-js/es6/reflect'; 45 | 46 | 47 | 48 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/ 49 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 50 | 51 | 52 | 53 | /*************************************************************************************************** 54 | * Zone JS is required by Angular itself. 55 | */ 56 | import 'zone.js/dist/zone'; // Included with Angular-CLI. 57 | 58 | 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | 64 | /** 65 | * Date, currency, decimal and percent pipes. 66 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 67 | */ 68 | // import 'intl'; // Run `npm install --save intl`. 69 | 70 | // Add global to window, assigning the value of window itself. 71 | (window as any).global = window; 72 | -------------------------------------------------------------------------------- /src/server/courseRoute.ts: -------------------------------------------------------------------------------- 1 | 2 | import {dbData} from "./db-data"; 3 | 4 | 5 | 6 | export function courseRoute(req, res) { 7 | 8 | const courseId = parseInt(req.params['id']) - 1; 9 | 10 | const course = dbData[courseId]; 11 | 12 | res.status(200).json({ 13 | "id": course.id, 14 | "url": course.url, 15 | "description": course.description 16 | }); 17 | 18 | } -------------------------------------------------------------------------------- /src/server/db-data.ts: -------------------------------------------------------------------------------- 1 | export const dbData = [ 2 | { 3 | "id": 1, 4 | "url": "getting-started-with-angular2", 5 | "description": "Angular Tutorial For Beginners", 6 | "iconUrl": "https://angular-academy.s3.amazonaws.com/thumbnails/angular2-for-beginners.jpg", 7 | "courseListIcon": "https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png", 8 | "longDescription": "Establish a solid layer of fundamentals, learn what's under the hood of Angular", 9 | "lessons": [ 10 | { 11 | "url": "angular2-hello-world-write-first-application", 12 | "description": "Angular Tutorial For Beginners - Build Your First App - Hello World Step By Step", 13 | "duration": "2:49", 14 | "tags": "BEGINNER", 15 | videoUrl: "https://www.youtube.com/embed/du6sKwEFrhQ", 16 | "longDescription": "This is step by step guide to create your first Angular application. Its aimed at beginners just starting out with the framework.This lesson will show how to create a component, and how to link the component to a given custom HTML tag. It will show how to give the component a given template." 17 | }, 18 | { 19 | "url": "angular2-build-your-first-component", 20 | "description": "Building Your First Angular Component - Component Composition", 21 | "duration": "2:07", 22 | "tags": "BEGINNER", 23 | videoUrl: "https://www.youtube.com/embed/VES1eTNxi1s", 24 | "longDescription": "In this lesson we are going to see how to include a component inside another component. We are going to create a simple search box component and include it in our main application." 25 | }, 26 | { 27 | "url": "angular2-passing-data-to-component-using-input", 28 | "description": "Component @Input - How To Pass Input Data To an Angular Component", 29 | "duration": "2:33", 30 | "tags": "BEGINNER", 31 | "videoUrl": "https://www.youtube.com/embed/Yfebo2mFrTU", 32 | "longDescription": "In this lesson we are going to learn how to use the Angular template syntax for properties, and learn how we can use it to pass input data to a component. We are going to see also a simplified input syntax for passing constant strings as component inputs." 33 | }, 34 | { 35 | "url": "angular2-component-events", 36 | "description": "Angular Component Events - Using @Output to create custom events", 37 | "duration": "4:44", 38 | "tags": "BEGINNER", 39 | videoUrl: "https://www.youtube.com/embed/dgyVrJ2XCq4", 40 | "longDescription": "In this lesson we are going to see how components can emit custom events via EventEmitter and the @Output decorator. We are going to see how we can subscribe to standard browser events, and how the syntax for that is exactly the same as in the case of custom component events. We will also learn how Typescript literals can be used to output variables inside template strings." 41 | }, 42 | { 43 | "url": "angular2-component-templates-internal-vs-external", 44 | "description": "Angular Component Templates - Inline Vs External", 45 | "duration": "2:55", 46 | "tags": "BEGINNER", 47 | "pro": true, 48 | "longDescription": "In this lesson we are going to learn how a component template can be defined both inline and in an external file. We are going to learn how to configure the component so that Angular can find the template at the correct location, using the module commonjs variable. We are going to learn also some best practices for component naming, from the official Angular Style Guide." 49 | }, 50 | { 51 | "url": "angular2-components-styling-component-isolation", 52 | "description": "Styling Angular Components - Learn About Component Style Isolation", 53 | "duration": "3:27", 54 | "tags": "BEGINNER", 55 | "pro": true, 56 | "longDescription": "In this lesson we are going to learn how components can be styled using both inline styles and an external css file. We will learn some more best practices on file naming. We will learn how the mechanism for style isolation works in Angular." 57 | }, 58 | { 59 | "url": "angular2-components-component-interaction", 60 | "description": "Angular Component Interaction - Extended Components Example", 61 | "duration": "9:22", 62 | "pro": true, 63 | "tags": "BEGINNER", 64 | "longDescription": "In this lesson we are going to put together all that we have learned so far about components to create a more complex example. We are going to create two components: a color picker and a color previewer and see how they can interact." 65 | }, 66 | { 67 | "url": "angular2-components-exercise", 68 | "description": "Angular Components Tutorial For Beginners - Components Exercise !", 69 | "duration": "1:26", 70 | "tags": "BEGINNER", 71 | "pro": true, 72 | "longDescription": "In this video we are going to present an exercise for putting in practice the several concepts that we have learned so far about components." 73 | }, 74 | { 75 | "url": "angular2-components-exercise-solution", 76 | "description": "Angular Components Tutorial For Beginners - Components Exercise Solution Inside", 77 | "duration": "2:08", 78 | "tags": "BEGINNER", 79 | "pro": true, 80 | "longDescription": "This video contains the solution for the introduction to components exercise." 81 | }, 82 | { 83 | "url": "angular2-directives-inputs-outputs-event-emitters", 84 | "description": "Angular Directives - Inputs, Output Event Emitters and How To Export Template References", 85 | "duration": "4:01", 86 | "tags": "BEGINNER", 87 | "pro": true, 88 | "longDescription": "Angular Components are actually simply just Directives. All the functionality that we have learned so far about Components also applies to Directives. In this lesson we are going to learn how to Directives can also have inputs and outputs, and how the use of the decorators @Input and @Output also applies to directives. We are also learn a new functionality for exporting a template reference for the directive itself into the template on which the directive is being used. " 89 | }, 90 | { 91 | "description": "Angular Core Directives - ngFor", 92 | "duration": "3:46", 93 | "url": "angular2-core-directives-ngfor", 94 | "tags": "BEGINNER", 95 | "pro": true, 96 | "longDescription": "This is an overview on the ngFor core directive, how it works and some common use cases on how it should be used to build templates. It demonstrates how ngFor can be used with any iterable and not only arrays, and how to use together with other mechanisms of the framework like @ContentChildren." 97 | }, 98 | { 99 | "description": "Angular Core Directives - ngClass and ngStyle", 100 | "duration": "3:15", 101 | "url": "angular2-core-directives-ngclass-ngstyle", 102 | "tags": "BEGINNER", 103 | "pro": true, 104 | "longDescription": "This lesson is an overview on how to use the ngClass and ngStyle Directives, when to use which, and alternative syntax in case we only to modify one property/style." 105 | }, 106 | { 107 | "description": "Angular Core Directives - ngIf", 108 | "duration": "3:56", 109 | "url": "angular2-core-directives-ngIf", 110 | "tags": "BEGINNER", 111 | "pro": true, 112 | "longDescription": "This lesson covers the use of the core directive ngIf, as well as two other alternative way of showing or hiding elements from the DOM: the hidden property and the visibility CSS property." 113 | }, 114 | { 115 | "description": "Directives Guided Tour - Learn Why Directives Might be a Better Choice Than Components", 116 | "url": "angular2-guided-tour-directives", 117 | "duration": "7:58", 118 | "tags": "BEGINNER", 119 | "pro": true, 120 | "longDescription": "This lesson is an extended tour on Angular directives. This is an extremely powerful feature of Angular that often remains underused. Its super powerful and and if used correctly can be used to create functionality that is much more reusable than components themselves." 121 | }, 122 | { 123 | "description": "Introduction to Angular Directives - Exercise - Improve the Collapsible Directive", 124 | "duration": "1:30", 125 | "url": "angular2-directives-exercise-improve-collapsible-directive", 126 | "tags": "BEGINNER", 127 | "pro": true, 128 | "longDescription": "In this video we are going to present the exercise for the Introduction To Directives section. The goal of the exercise is to take the learned functionality about how to build a custom directive and how to use the standard Angular Core directives to build an improved version of the collapse-on-click directive." 129 | }, 130 | { 131 | "description": "Introduction to Angular Directives - Exercise Solution", 132 | "duration": "2:40", 133 | "url": "angular2-directives-exercise-solution-improve-collapsible-directive", 134 | "tags": "BEGINNER", 135 | "pro": true, 136 | "longDescription": "In this video we are going to present the exercise solution for the Introduction To Directives section." 137 | } 138 | ] 139 | }, 140 | { 141 | "id": 2, 142 | "url": "angular2-http", 143 | "description": "Angular HTTP and Services", 144 | "longDescription": "Build Services using Observables, learn to use the HTTP module effectively.", 145 | "iconUrl": "https://angular-academy.s3.amazonaws.com/thumbnails/services-and-http.jpg", 146 | "courseListIcon": "https://angular-academy.s3.amazonaws.com/course-logos/observables_rxjs.png", 147 | "lessons": [ 148 | { 149 | "description": "What is an Observable ? Introduction to Streams and RxJs Observables", 150 | "duration": "5:41", 151 | "url": "angular2-what-is-an-observable", 152 | "tags": "BEGINNER", 153 | "videoUrl": "https://www.youtube.com/embed/Sol2uLolmUM", 154 | "longDescription": "In this lesson we are going to present a couple of baseline concepts that are essential for being able to build service layers in Angular: we will introduce the notions of stream and Observable. We are going to understand that these are two different concepts: an Observable is not a stream. During the lesson we will write our first Observable and we will learn one of the main properties of Observables: that they are inherently inert, and that we need to subscribe to them in order for them to work. We are also going to introduce our first RxJs operator: the do operator which should only be used for debugging purposes as it introduces side effects." 155 | }, 156 | { 157 | "description": "Observables Error Handling and Completion - How do Observables handle Errors?", 158 | "duration": "5:28", 159 | "url": "angular2-observables-error-handling-and-completion-network-calls-as-observables", 160 | "tags": "BEGINNER", 161 | "videoUrl": "https://www.youtube.com/embed/ot_FrQbmEmU", 162 | "longDescription": "In this lesson we are going to present two other foundation concepts of Observables: error handling and completion. We are going to initially call our backend server using the browser Fetch API, which is promises based. We will then learn how to create an Observable from a promise, and see how and why an observable is a good way to model a network call. We will learn about some advantages of Observables vs Promises." 163 | }, 164 | { 165 | "description": "How does Angular HTTP use Observables ? The HTTP Response object", 166 | "duration": "4:32", 167 | "url": "how-does-angular2-use-observables-http-response-object", 168 | "tags": "BEGINNER", 169 | "longDescription": "In this lesson we are going to learn the relation between the Angular HTTP module and Observables, and how its essential to understand Observables in order to do even the most common backend-communication tasks using the HTTP module. We are going to learn how Angular HTTP models network responses using Observables, and how completion is handled. " 170 | }, 171 | { 172 | "description": "How to use Observables and HTTP to build a Service Layer", 173 | "duration": "4:32", 174 | "url": "angular2-how-to-use-observables-and-http-to-build-a-servicelayer", 175 | "tags": "BEGINNER", 176 | "videoUrl": "", 177 | "longDescription": "In this lesson we are going to learn how to use Angular HTTP to build the service layer of our application. We are going to learn how to build strongly typed services and we are going to learn how the service layer can be designed around the notion of Observables." 178 | }, 179 | { 180 | "description": "Introduction to Functional Reactive Programming - Using the Async Pipe - Pitfalls to Avoid", 181 | "duration": "4:36", 182 | "url": "angular2-how-to-use-the-async-pipe-to-pass-observables-into-a-template", 183 | "tags": "BEGINNER", 184 | "pro": true, 185 | "longDescription": "In this lesson we are going to do an introduction to Functional Reactive Programming, and we are going to see how an application can be built around the notion of Observables. We are going to see how programs can be build with very little state variables, and how data can be passed on from Observables directly to templates by using the Async Pipe. We are going to learn also why in some cases its not a good idea to call the service layer directly from a template expression - this is a pitfall to avoid." 186 | }, 187 | { 188 | "description": "The RxJs Map Operator - How to create an Observable from another Observable", 189 | "duration": "3:04", 190 | "url": "angular2-observable-map-operator-how-to-create-an-observable-from-another", 191 | "tags": "BEGINNER", 192 | "pro": true, 193 | "longDescription": "In this lesson we are going to learn one of the key concepts about Observables: we can easily derive new Observables from existing Observables using the many RxJs operators available to us. In this lesson we are going to create an Observable from another Observable by using the RxJs map operator." 194 | }, 195 | { 196 | "description": "Observable Composition - combine multiple Observables Using combineLatest", 197 | "duration": "5:59", 198 | "url": "angular2-observable-composition-combine-latests", 199 | "tags": "BEGINNER", 200 | "pro": true, 201 | "longDescription": "In this lesson we are going to learn that Observables can be combined with other Observables. In this case we are going to create an Observable of mouse moves that only emits if the mouse is bellow a certain region of the page. We are also going to create an Observable of mouse clicks, that emits if the user clicks anywhere on the page - both of these Observables will be created using fromEvent. We will then combine these two Observables to create third Observable using the RxJs combineLatests operator. " 202 | }, 203 | { 204 | "description": "Avoid the Biggest Pitfall of Angular HTTP - Learn the RxJs Cache Operator", 205 | "duration": "5:10", 206 | "url": "angular2-how-to--aAvoid-duplicate-http-requests-rxjs-cache-operator", 207 | "tags": "INTERMEDIATE", 208 | "pro": true, 209 | "longDescription": "In this lesson we are going to use the HTTP module to implement a modification operation: we are going to add a lesson to a lessons list via an HTTP POST call, and then reload the data from the server. While implementing this simple use case, we are going to come across something that might be surprising at first: its really simple to do duplicate network calls accidentally while using Angular HTTP. We are going to learn the reason why that is the case, and learn how we can avoid that using the RxJs Cache Operator." 210 | }, 211 | { 212 | "description": "How to do multiple HTTP requests using the RxJs Concat Operator", 213 | "duration": "4:19", 214 | "url": "angular2-how-to-do-multiple-http-requests-using-the-rxjs-concat-operator", 215 | "tags": "INTERMEDIATE", 216 | "pro": true, 217 | "longDescription": "In this lesson we are going to learn how we make multiple sequential requests to the server by using the RxJs Concat operator. This is another example of how from the point of view of the Angular HTTP module network requests are really just Observables that can be easily combined using the many RxJs operators available. We are going to implement the following concrete example: do a delete on the server, then a second delete and finally reload the new list from the server and display it on the screen." 218 | }, 219 | { 220 | "description": "How to do two HTTP Requests in Parallel using the RxJs combineLatest Operator", 221 | "duration": "3:58", 222 | "url": "angular2-how-to-do-two-http-requests-in-parallel-using-the-rxjs-combinelatest-operator", 223 | "tags": "INTERMEDIATE", 224 | "pro": true, 225 | "longDescription": "In this lesson we are going to learn how to do two HTTP requests in parallel, wait for each to return and produce a result that contains the combination of the two HTTP calls. For that we are going to use an operator that we presented before, the combineLatest Operator which will in this time be used in a completely different context. This is a good example of the power of the approach that the Angular HTTP module gives us, by modeling network calls as Observables; any RxJs operator can potentially by used to process network calls." 226 | }, 227 | { 228 | "description": "How to setup an HTTP request sequence using the RxJs switchMap Operator", 229 | "duration": "4:33", 230 | "url": "angular2-how-to-setup-an-http-request-sequence-using-the-rxjs-switchmap-operator", 231 | "tags": "INTERMEDIATE", 232 | "pro": true, 233 | "longDescription": "In this lesson we are going to learn how we can build a chain of HTTP requests, but now we will be able to take the result of one request and then use it to build the next request. For this we are going to introduce a new RxJs Operator for combining Observables, the switchMap Operator. This lesson will give us a first contact with the more general Switch strategy of combining Observables." 234 | }, 235 | { 236 | "description": "Retry HTTP requests in Error using the retry and retryWhen RxJs Operators", 237 | "duration": "3:42", 238 | "url": "angular2-retry-http-requests-in-error-using-the-retry-and-retrywhen-rxjs-operators", 239 | "tags": "INTERMEDIATE", 240 | "pro": true, 241 | "longDescription": "In this lesson we are going to learn how RxJs and Observables make it very simple to deal with certain uses cases that before might be challenging. For example, we are going to learn how to retry a network call in case of error. This is very useful in situations when the backend occasionally returns errors that are of an intermittent nature. In those scenarios a good strategy is to try to send the network call again a second time, usually after a certain delay has elapsed. In this lesson we are going to learn how we can use the RxJs Operators retry and retryWhen to implement service layers that are resilient to temporary errors." 242 | }, 243 | { 244 | "description": "How to Cancel an HTTP Request using an RxJs Subscription", 245 | "duration": "2:56", 246 | "url": "angular2-how-to-cancel-an-http-request-using-an-rxjs-subscription", 247 | "tags": "INTERMEDIATE", 248 | "pro": true, 249 | "longDescription": "In this lesson we are going to learn how to implement a use case using RxJs and Observables that was very hard to do previously: the cancellation of an ongoing HTTP request. We are going to learn about the RxJs subscription object and how to use it to implement cancellation." 250 | }, 251 | { 252 | "description": "Exercise - Improve a Search Service and Build a Typeahead", 253 | "duration": "3:15", 254 | "url": "angular2-exercise-improve-a-search-service-and-build-a-typeahead", 255 | "tags": "INTERMEDIATE", 256 | "pro": true, 257 | "longDescription": "This lesson is the setup for the exercise of the Services and HTTP series. We are going to implement a Typeahead that continuously retrieves from the backend new search results depending on what the use is typing. We are going to show how to use the Angular HTTP API to pass a GET parameter request to the backend." 258 | }, 259 | { 260 | "description": "Exercise Solution - Learn How to build a Typeahead that cancels obsolete search requests", 261 | "duration": "5:07", 262 | "url": "angular2-exercise-solution-learn-how-to-build-a-typeahead-that-cancels-obsolete-search-requests", 263 | "tags": "INTERMEDIATE", 264 | "pro": true, 265 | "longDescription": "This is the solution for the HTTP and Services exercise, where we will build a Typeahead. For that we are going to use a couple of RxJs Operators that we have previously presented in this course. We are going to see how results from a previous search can be implicitly canceled." 266 | } 267 | ] 268 | } 269 | ]; 270 | -------------------------------------------------------------------------------- /src/server/lessonDetailRoute.ts: -------------------------------------------------------------------------------- 1 | import {dbData} from "./db-data"; 2 | import * as _ from 'lodash'; 3 | 4 | 5 | export function lessonDetailRoute(req, res) { 6 | 7 | const lessonUrl = req.params['id']; 8 | 9 | console.log("Looking for lesson with Url ", lessonUrl); 10 | 11 | let allLessons = []; 12 | 13 | dbData.forEach(course => allLessons = allLessons.concat(course.lessons)); 14 | 15 | const lesson = _.find(allLessons, lesson => lesson.url === lessonUrl); 16 | 17 | console.log("lesson",lesson); 18 | 19 | res.status(200).json(lesson); 20 | } -------------------------------------------------------------------------------- /src/server/lessonsRoute.ts: -------------------------------------------------------------------------------- 1 | 2 | import {dbData} from "./db-data"; 3 | import * as _ from 'lodash'; 4 | 5 | 6 | export function lessonsRoute(req, res) { 7 | 8 | console.log(req.query); 9 | 10 | const courseId = parseInt(req.query['courseId']) - 1, 11 | pageNumber = parseInt(req.query['pageNumber']), 12 | pageSize = parseInt(req.query['pageSize']); 13 | 14 | const lessons = dbData[courseId].lessons; 15 | 16 | const start = ( pageNumber - 1 ) * pageSize, 17 | end = start + pageSize; 18 | 19 | const lessonsPage = _.slice(lessons, start, end ); 20 | 21 | res.status(200).json({payload: lessonsPage.map(buildLessonSummary)}); 22 | 23 | } 24 | 25 | 26 | function buildLessonSummary({url,description,duration},index) { 27 | return { 28 | url, 29 | description, 30 | seqNo: index, 31 | duration 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/server/loginRoute.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import {User} from "../app/shared/model/user"; 4 | 5 | const auth = { 6 | 'john@gmail.com': 'test123', 7 | 'bill@gmail.com': 'test456' 8 | 9 | }; 10 | 11 | const users: {[key:string]: User} = { 12 | 'john@gmail.com': { 13 | firstName: 'John' 14 | }, 15 | 'bill@gmail.com': { 16 | firstName: 'Bill' 17 | } 18 | }; 19 | 20 | export function loginRoute(req, res) { 21 | 22 | 23 | const payload = req.body; 24 | 25 | console.log('verifying password ...', payload); 26 | 27 | 28 | if (auth[payload.email] && auth[payload.email] === payload.password) { 29 | res.status(200).json(users[payload.email]); 30 | } 31 | else { 32 | res.sendStatus(500); 33 | } 34 | 35 | 36 | 37 | 38 | } -------------------------------------------------------------------------------- /src/server/newsletterRoute.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export function newsletterRoute(req, res) { 4 | 5 | const payload = req.body; 6 | 7 | console.log('subscribing to newsletter ...', payload.email); 8 | 9 | res.status(200).send(); 10 | } -------------------------------------------------------------------------------- /src/server/server.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import * as express from 'express'; 4 | import {Application} from 'express'; 5 | import {newsletterRoute} from "./newsletterRoute"; 6 | import {loginRoute} from "./loginRoute"; 7 | import {courseRoute} from "./courseRoute"; 8 | import {lessonsRoute} from "./lessonsRoute"; 9 | import {lessonDetailRoute} from "./lessonDetailRoute"; 10 | const bodyParser = require('body-parser'); 11 | 12 | const app: Application = express(); 13 | 14 | app.use(bodyParser.json()); 15 | 16 | 17 | console.log('Starting server ...'); 18 | 19 | 20 | app.route('/api/newsletter').post(newsletterRoute); 21 | app.route('/api/login').post(loginRoute); 22 | 23 | app.route('/api/courses/:id').get(courseRoute); 24 | app.route('/api/lessons').get(lessonsRoute); 25 | app.route('/api/lessons/:id').get(lessonDetailRoute); 26 | 27 | 28 | app.listen(8090, () => { 29 | console.log('Server is now running on port 8090 ...'); 30 | }); 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /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 var __karma__: any; 17 | declare var require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "", 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "es2016", 9 | "dom" 10 | ], 11 | "mapRoot": "./", 12 | "module": "es2015", 13 | "moduleResolution": "node", 14 | "outDir": "../dist/out-tsc", 15 | "sourceMap": true, 16 | "target": "es5", 17 | "typeRoots": [ 18 | "../node_modules/@types" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "callable-types": true, 7 | "class-name": true, 8 | "comment-format": [ 9 | true, 10 | "check-space" 11 | ], 12 | "curly": true, 13 | "eofline": true, 14 | "forin": true, 15 | "import-blacklist": [true], 16 | "import-spacing": true, 17 | "indent": [ 18 | true, 19 | "spaces" 20 | ], 21 | "interface-over-type-literal": true, 22 | "label-position": true, 23 | "max-line-length": [ 24 | true, 25 | 140 26 | ], 27 | "member-access": false, 28 | "member-ordering": [ 29 | true, 30 | "static-before-instance", 31 | "variables-before-functions" 32 | ], 33 | "no-arg": true, 34 | "no-bitwise": true, 35 | "no-console": [ 36 | true, 37 | "debug", 38 | "info", 39 | "time", 40 | "timeEnd", 41 | "trace" 42 | ], 43 | "no-construct": true, 44 | "no-debugger": true, 45 | "no-duplicate-variable": true, 46 | "no-empty": false, 47 | "no-empty-interface": true, 48 | "no-eval": true, 49 | "no-inferrable-types": true, 50 | "no-shadowed-variable": true, 51 | "no-string-literal": false, 52 | "no-string-throw": true, 53 | "no-switch-case-fall-through": true, 54 | "no-trailing-whitespace": true, 55 | "no-unused-expression": true, 56 | "no-use-before-declare": true, 57 | "no-var-keyword": true, 58 | "object-literal-sort-keys": false, 59 | "one-line": [ 60 | true, 61 | "check-open-brace", 62 | "check-catch", 63 | "check-else", 64 | "check-whitespace" 65 | ], 66 | "prefer-const": true, 67 | "quotemark": [ 68 | true, 69 | "single" 70 | ], 71 | "radix": true, 72 | "semicolon": [ 73 | "always" 74 | ], 75 | "triple-equals": [ 76 | true, 77 | "allow-null-check" 78 | ], 79 | "typedef-whitespace": [ 80 | true, 81 | { 82 | "call-signature": "nospace", 83 | "index-signature": "nospace", 84 | "parameter": "nospace", 85 | "property-declaration": "nospace", 86 | "variable-declaration": "nospace" 87 | } 88 | ], 89 | "typeof-compare": true, 90 | "unified-signatures": true, 91 | "variable-name": false, 92 | "whitespace": [ 93 | true, 94 | "check-branch", 95 | "check-decl", 96 | "check-operator", 97 | "check-separator", 98 | "check-type" 99 | ], 100 | 101 | "directive-selector": [true, "attribute", "app", "camelCase"], 102 | "component-selector": [true, "element", "app", "kebab-case"], 103 | "use-input-property-decorator": true, 104 | "use-output-property-decorator": true, 105 | "use-host-property-decorator": true, 106 | "no-input-rename": true, 107 | "no-output-rename": true, 108 | "use-life-cycle-interface": true, 109 | "use-pipe-transform-interface": true, 110 | "component-class-suffix": true, 111 | "directive-class-suffix": true, 112 | "no-access-missing-member": true, 113 | "templates-use-public": true, 114 | "invoke-injectable": true 115 | } 116 | } 117 | --------------------------------------------------------------------------------