├── .editorconfig ├── .firebase └── hosting.ZGlzdC9sZWFybi1hbmd1bGFyLWZyb20tc2NyYXRjaA.cache ├── .firebaserc ├── .gitignore ├── LICENSE ├── README.md ├── angular.json ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.e2e.json ├── firebase.json ├── package-lock.json ├── package.json ├── sdk ├── index.ts ├── lb.config.ts ├── models │ ├── Answer.ts │ ├── BaseModels.ts │ ├── Question.ts │ ├── User.ts │ └── index.ts ├── services │ ├── core │ │ ├── auth.service.ts │ │ ├── base.service.ts │ │ ├── error.service.ts │ │ ├── index.ts │ │ └── search.params.ts │ ├── custom │ │ ├── Answer.ts │ │ ├── Question.ts │ │ ├── SDKModels.ts │ │ ├── User.ts │ │ ├── index.ts │ │ └── logger.service.ts │ └── index.ts └── storage │ ├── cookie.browser.ts │ ├── storage.browser.ts │ └── storage.swaps.ts ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── categories │ │ ├── categories.component.html │ │ ├── categories.component.ts │ │ ├── categories.resolver.ts │ │ ├── categories.scss │ │ └── category.model.ts │ ├── category-questions │ │ ├── category-questions.component.html │ │ ├── category-questions.component.ts │ │ ├── category-questions.resolver.ts │ │ ├── category-questions.scss │ │ ├── delete-question │ │ │ ├── delete-question-modal.component.html │ │ │ └── delete-question-modal.component.ts │ │ └── new-question │ │ │ ├── new-question-modal.component.html │ │ │ └── new-question-modal.component.ts │ ├── question-answers │ │ ├── delete-answer │ │ │ ├── delete-answer-modal.component.html │ │ │ └── delete-answer-modal.component.ts │ │ ├── new-answer │ │ │ ├── new-answer-modal.component.html │ │ │ └── new-answer-modal.component.ts │ │ ├── question-answers.component.html │ │ ├── question-answers.component.ts │ │ ├── question-answers.resolver.ts │ │ ├── question-answers.scss │ │ └── update-answer │ │ │ ├── update-answer-modal.component.html │ │ │ └── update-answer-modal.component.ts │ ├── services │ │ ├── answers.service.ts │ │ ├── categories.service.ts │ │ └── questions.service.ts │ ├── shared │ │ ├── breadcrumb │ │ │ ├── breadcrumb.component.html │ │ │ ├── breadcrumb.component.ts │ │ │ └── styles │ │ │ │ └── breadcrumb.scss │ │ ├── shared.module.ts │ │ └── slugify.pipe.ts │ └── styles │ │ ├── _variables.scss │ │ ├── app.scss │ │ └── modals.scss ├── assets │ ├── .gitkeep │ └── categories.json ├── browserslist ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── karma.conf.js ├── main.ts ├── polyfills.ts ├── styles.scss ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── tslint.json ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.firebase/hosting.ZGlzdC9sZWFybi1hbmd1bGFyLWZyb20tc2NyYXRjaA.cache: -------------------------------------------------------------------------------- 1 | 3rdpartylicenses.txt,1555615471248,719b0a21c77440404828870ded740fe783bfe01d572ab0537836971530ca8520 2 | favicon.ico,1555615471249,3c8f1dca744007357393e9228949f1d36e0865c21c06cb504be0fa21e397df5f 3 | index.html,1555615471250,d8ca1fb5df8d148b3b10de69d80d4cfa71ef6a4d26bd5e6c3809e3a8ecb4cc83 4 | runtime.26209474bfa8dc87a77c.js,1555615471249,52d4a5aadcedd90b5828211d17e6ec3110bac1d8d48a7e82f58b7fa8b25b5f0d 5 | assets/categories.json,1555615471251,7a280ddedfe61aabf0e06e1ddfa75bdb640cdf2c59ab76ef6da93ba739abe172 6 | polyfills.8bbb231b43165d65d357.js,1555615471249,45a04c7445707e98f5fabc690ed532fc3285d15496495ccf8f2cb4ffcdcd9014 7 | es2015-polyfills.c5dd28b362270c767b34.js,1555615471249,0da6ba47e82051120ed73ccd2d15c79a68e29ea675de283faf9cc4ad312c283a 8 | fontawesome-webfont.af7ae505a9eed503f8b8.woff2,1555615471248,156e171dae6239bcd8304d42163d8b1e6bfd029759d46be88a4e446a51249ba2 9 | fontawesome-webfont.fee66e712a8a08eef580.woff,1555615471248,9cb84ef5f51a04cf0dd3a9aec98c43ad4d44df442b7454c6b448913e36fb0c43 10 | styles.0800ae2f50afa6efad98.css,1555615471249,20f045fc96d930ea05c8dca2783905d449157df512d48d790267d75c8afe2e1f 11 | fontawesome-webfont.b06871f281fee6b241d6.ttf,1555615471248,225dc03087123b26cb71296d418705ec2e89e826acd04bfe6fc6353a0f5220c8 12 | fontawesome-webfont.674f50d287a8c48dc19b.eot,1555615471248,9acdf34fe486fad3cfa8ef4aa925ebbe9e955c8a8d127bff4d427620b0eeb643 13 | main.0d6768537509e6f8b04d.js,1555615471249,1c63fb00e8b095fc35f105eb814285aad245b8bec2a7091350428451bcf26126 14 | fontawesome-webfont.912ec66d7572ff821749.svg,1555615471249,303fc405939cc4d296906102b8047b71a4ec14a7b51aa7a2abfaad416ec8fb3c 15 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "tutorial": "learn-angular-from-scratch" 4 | } 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 AngularTemplates 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 | # Angular Tutorial: Learn Angular from scratch step by step 2 | 3 | This project is part of Learn Angular from scratch step by step tutorial where we explore from the basic concepts and why’s of Angular Framework to building a complete Angular app using Angular Material components. 4 | 5 | In this Angular Tutorial we explain one by one the main building blocks of an Angular application as well as the best practices for building a complete mobile app with Angular. Also this tutorial shows how to setup your dev environment so you can start developing Angular apps in your computer right now. 6 | 7 | Get the step by step free tutorial in https://angular-templates.io/tutorials/about/learn-angular-from-scratch-step-by-step 8 | 9 | **Please support this project by simply putting a Github star ⭐. Share this library with friends on Twitter and everywhere else you can. 🙏. Thanks** 10 | 11 | 12 | ## Installation 13 | 14 | Run `npm install` to install all the required dependencies 15 | 16 | Then run `ng serve` to start a dev server. 17 | Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 18 | 19 | 20 | ![](https://s3-us-west-2.amazonaws.com/angular-templates/tutorials/learn-angular-from-scratch-step-by-step/learn-angular-from-scratch-step-by-step-categories.png) 21 | 22 | ## Demo 23 | 24 | https://learn-angular-from-scratch.firebaseapp.com 25 | 26 | 27 | ## Premium Angular Starter Apps 28 | 29 | If you want to build a complex and robust Angular app you should check [Fully - Angular Admin Template](https://angular-templates.io/product/fully-angular-admin-template) which is a super complete application to build your next angular project. 30 | 31 | It includes lots of Angular Material Components. 32 | 33 | Created with performance and ease of development in mind, this Angular 12 web template is something you will love. It includes all the components that you might need inside a project and a detailed documentation on how to get started. 34 | 35 | 36 | ![](https://angular-templates.s3-us-west-2.amazonaws.com/fully-angular-admin-template/fully-angular-admin-template-cover.jpg) 37 | 38 | ## Free Angular examples 39 | 40 | Find more Angular tutorials and starter apps in https://angular-templates.io 41 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "learn-angular-from-scratch": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "style": "scss" 14 | } 15 | }, 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/learn-angular-from-scratch", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "src/tsconfig.app.json", 25 | "assets": [ 26 | "src/favicon.ico", 27 | "src/assets" 28 | ], 29 | "styles": [ 30 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 31 | "./node_modules/font-awesome/css/font-awesome.css", 32 | "src/styles.scss" 33 | ], 34 | "scripts": [], 35 | "es5BrowserSupport": true 36 | }, 37 | "configurations": { 38 | "production": { 39 | "fileReplacements": [ 40 | { 41 | "replace": "src/environments/environment.ts", 42 | "with": "src/environments/environment.prod.ts" 43 | } 44 | ], 45 | "optimization": true, 46 | "outputHashing": "all", 47 | "sourceMap": false, 48 | "extractCss": true, 49 | "namedChunks": false, 50 | "aot": true, 51 | "extractLicenses": true, 52 | "vendorChunk": false, 53 | "buildOptimizer": true, 54 | "budgets": [ 55 | { 56 | "type": "initial", 57 | "maximumWarning": "2mb", 58 | "maximumError": "5mb" 59 | } 60 | ] 61 | } 62 | } 63 | }, 64 | "serve": { 65 | "builder": "@angular-devkit/build-angular:dev-server", 66 | "options": { 67 | "browserTarget": "learn-angular-from-scratch:build" 68 | }, 69 | "configurations": { 70 | "production": { 71 | "browserTarget": "learn-angular-from-scratch:build:production" 72 | } 73 | } 74 | }, 75 | "extract-i18n": { 76 | "builder": "@angular-devkit/build-angular:extract-i18n", 77 | "options": { 78 | "browserTarget": "learn-angular-from-scratch:build" 79 | } 80 | }, 81 | "test": { 82 | "builder": "@angular-devkit/build-angular:karma", 83 | "options": { 84 | "main": "src/test.ts", 85 | "polyfills": "src/polyfills.ts", 86 | "tsConfig": "src/tsconfig.spec.json", 87 | "karmaConfig": "src/karma.conf.js", 88 | "styles": [ 89 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 90 | "src/styles.scss" 91 | ], 92 | "scripts": [], 93 | "assets": [ 94 | "src/favicon.ico", 95 | "src/assets" 96 | ] 97 | } 98 | }, 99 | "lint": { 100 | "builder": "@angular-devkit/build-angular:tslint", 101 | "options": { 102 | "tsConfig": [ 103 | "src/tsconfig.app.json", 104 | "src/tsconfig.spec.json" 105 | ], 106 | "exclude": [ 107 | "**/node_modules/**" 108 | ] 109 | } 110 | } 111 | } 112 | }, 113 | "learn-angular-from-scratch-e2e": { 114 | "root": "e2e/", 115 | "projectType": "application", 116 | "prefix": "", 117 | "architect": { 118 | "e2e": { 119 | "builder": "@angular-devkit/build-angular:protractor", 120 | "options": { 121 | "protractorConfig": "e2e/protractor.conf.js", 122 | "devServerTarget": "learn-angular-from-scratch:serve" 123 | }, 124 | "configurations": { 125 | "production": { 126 | "devServerTarget": "learn-angular-from-scratch:serve:production" 127 | } 128 | } 129 | }, 130 | "lint": { 131 | "builder": "@angular-devkit/build-angular:tslint", 132 | "options": { 133 | "tsConfig": "e2e/tsconfig.e2e.json", 134 | "exclude": [ 135 | "**/node_modules/**" 136 | ] 137 | } 138 | } 139 | } 140 | } 141 | }, 142 | "defaultProject": "learn-angular-from-scratch" 143 | } 144 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to learn-angular-from-scratch!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "dist/learn-angular-from-scratch", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "learn-angular-from-scratch", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~7.2.0", 15 | "@angular/cdk": "~7.3.7", 16 | "@angular/common": "~7.2.0", 17 | "@angular/compiler": "~7.2.0", 18 | "@angular/core": "~7.2.0", 19 | "@angular/forms": "~7.2.0", 20 | "@angular/http": "^7.2.13", 21 | "@angular/material": "^7.3.7", 22 | "@angular/platform-browser": "~7.2.0", 23 | "@angular/platform-browser-dynamic": "~7.2.0", 24 | "@angular/router": "~7.2.0", 25 | "bootstrap-sass": "^3.4.1", 26 | "core-js": "^2.5.4", 27 | "font-awesome": "^4.7.0", 28 | "hammerjs": "^2.0.8", 29 | "rxjs": "~6.3.3", 30 | "rxjs-compat": "~6.3.3", 31 | "tslib": "^1.9.0", 32 | "zone.js": "~0.8.26" 33 | }, 34 | "devDependencies": { 35 | "@angular-devkit/build-angular": "~0.13.0", 36 | "@angular/cli": "~7.3.8", 37 | "@angular/compiler-cli": "~7.2.0", 38 | "@angular/language-service": "~7.2.0", 39 | "@types/node": "~8.9.4", 40 | "@types/jasmine": "~2.8.8", 41 | "@types/jasminewd2": "~2.0.3", 42 | "codelyzer": "~4.5.0", 43 | "jasmine-core": "~2.99.1", 44 | "jasmine-spec-reporter": "~4.2.1", 45 | "karma": "~4.0.0", 46 | "karma-chrome-launcher": "~2.2.0", 47 | "karma-coverage-istanbul-reporter": "~2.0.1", 48 | "karma-jasmine": "~1.1.2", 49 | "karma-jasmine-html-reporter": "^0.2.2", 50 | "protractor": "~5.4.0", 51 | "ts-node": "~7.0.0", 52 | "tslint": "~5.11.0", 53 | "typescript": "~3.2.2" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /sdk/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /** 3 | * @module SDKModule 4 | * @author Jonathan Casarrubias 5 | * @license MIT 2016 Jonathan Casarrubias 6 | * @version 2.1.0 7 | * @description 8 | * The SDKModule is a generated Software Development Kit automatically built by 9 | * the LoopBack SDK Builder open source module. 10 | * 11 | * The SDKModule provides Angular 2 >= RC.5 support, which means that NgModules 12 | * can import this Software Development Kit as follows: 13 | * 14 | * 15 | * APP Route Module Context 16 | * ============================================================================ 17 | * import { NgModule } from '@angular/core'; 18 | * import { BrowserModule } from '@angular/platform-browser'; 19 | * // App Root 20 | * import { AppComponent } from './app.component'; 21 | * // Feature Modules 22 | * import { SDK[Browser|Node|Native]Module } from './shared/sdk/sdk.module'; 23 | * // Import Routing 24 | * import { routing } from './app.routing'; 25 | * @NgModule({ 26 | * imports: [ 27 | * BrowserModule, 28 | * routing, 29 | * SDK[Browser|Node|Native]Module.forRoot() 30 | * ], 31 | * declarations: [ AppComponent ], 32 | * bootstrap: [ AppComponent ] 33 | * }) 34 | * export class AppModule { } 35 | * 36 | **/ 37 | import { JSONSearchParams } from './services/core/search.params'; 38 | import { ErrorHandler } from './services/core/error.service'; 39 | import { LoopBackAuth } from './services/core/auth.service'; 40 | import { LoggerService } from './services/custom/logger.service'; 41 | import { SDKModels } from './services/custom/SDKModels'; 42 | import { InternalStorage, SDKStorage } from './storage/storage.swaps'; 43 | import { HttpModule } from '@angular/http'; 44 | import { CommonModule } from '@angular/common'; 45 | import { NgModule, ModuleWithProviders } from '@angular/core'; 46 | import { CookieBrowser } from './storage/cookie.browser'; 47 | import { StorageBrowser } from './storage/storage.browser'; 48 | import { UserApi } from './services/custom/User'; 49 | import { QuestionApi } from './services/custom/Question'; 50 | import { AnswerApi } from './services/custom/Answer'; 51 | /** 52 | * @module SDKBrowserModule 53 | * @description 54 | * This module should be imported when building a Web Application in the following scenarios: 55 | * 56 | * 1.- Regular web application 57 | * 2.- Angular universal application (Browser Portion) 58 | * 3.- Progressive applications (Angular Mobile, Ionic, WebViews, etc) 59 | **/ 60 | @NgModule({ 61 | imports: [ CommonModule, HttpModule ], 62 | declarations: [ ], 63 | exports: [ ], 64 | providers: [ 65 | ErrorHandler 66 | ] 67 | }) 68 | export class SDKBrowserModule { 69 | static forRoot(internalStorageProvider: any = { 70 | provide: InternalStorage, 71 | useClass: CookieBrowser 72 | }): ModuleWithProviders { 73 | return { 74 | ngModule : SDKBrowserModule, 75 | providers : [ 76 | LoopBackAuth, 77 | LoggerService, 78 | JSONSearchParams, 79 | SDKModels, 80 | UserApi, 81 | QuestionApi, 82 | AnswerApi, 83 | internalStorageProvider, 84 | { provide: SDKStorage, useClass: StorageBrowser } 85 | ] 86 | }; 87 | } 88 | } 89 | /** 90 | * Have Fun!!! 91 | * - Jon 92 | **/ 93 | export * from './models/index'; 94 | export * from './services/index'; 95 | export * from './lb.config'; 96 | export * from './storage/storage.swaps'; 97 | export { CookieBrowser } from './storage/cookie.browser'; 98 | export { StorageBrowser } from './storage/storage.browser'; 99 | 100 | -------------------------------------------------------------------------------- /sdk/lb.config.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /** 3 | * @module LoopBackConfig 4 | * @description 5 | * 6 | * The LoopBackConfig module help developers to externally 7 | * configure the base url and api version for loopback.io 8 | * 9 | * Example 10 | * 11 | * import { LoopBackConfig } from './sdk'; 12 | * 13 | * @Component() // No metadata needed for this module 14 | * 15 | * export class MyApp { 16 | * constructor() { 17 | * LoopBackConfig.setBaseURL('http://localhost:3000'); 18 | * LoopBackConfig.setApiVersion('api'); 19 | * } 20 | * } 21 | **/ 22 | export class LoopBackConfig { 23 | private static path: string = '//0.0.0.0:3000'; 24 | private static version: string | number = 'api'; 25 | private static authPrefix: string = ''; 26 | private static debug: boolean = true; 27 | private static filterOn: string = 'headers'; 28 | private static secure: boolean = false; 29 | private static withCredentials: boolean = false; 30 | 31 | public static setApiVersion(version: string = 'api'): void { 32 | LoopBackConfig.version = version; 33 | } 34 | 35 | public static getApiVersion(): string | number { 36 | return LoopBackConfig.version; 37 | } 38 | 39 | public static setBaseURL(url: string = '/'): void { 40 | LoopBackConfig.path = url; 41 | } 42 | 43 | public static getPath(): string { 44 | return LoopBackConfig.path; 45 | } 46 | 47 | public static setAuthPrefix(authPrefix: string = ''): void { 48 | LoopBackConfig.authPrefix = authPrefix; 49 | } 50 | 51 | public static getAuthPrefix(): string { 52 | return LoopBackConfig.authPrefix; 53 | } 54 | 55 | public static setDebugMode(isEnabled: boolean): void { 56 | LoopBackConfig.debug = isEnabled; 57 | } 58 | 59 | public static debuggable(): boolean { 60 | return LoopBackConfig.debug; 61 | } 62 | 63 | public static filterOnUrl(): void { 64 | LoopBackConfig.filterOn = 'url'; 65 | } 66 | 67 | public static filterOnHeaders(): void { 68 | LoopBackConfig.filterOn = 'headers'; 69 | } 70 | 71 | public static isHeadersFilteringSet(): boolean { 72 | return (LoopBackConfig.filterOn === 'headers'); 73 | } 74 | 75 | public static setSecureWebSockets(): void { 76 | LoopBackConfig.secure = true; 77 | } 78 | 79 | public static unsetSecureWebSockets(): void { 80 | LoopBackConfig.secure = false; 81 | } 82 | 83 | public static isSecureWebSocketsSet(): boolean { 84 | return LoopBackConfig.secure; 85 | } 86 | 87 | public static setRequestOptionsCredentials(withCredentials: boolean = false): void { 88 | LoopBackConfig.withCredentials = withCredentials; 89 | } 90 | 91 | public static getRequestOptionsCredentials(): boolean { 92 | return LoopBackConfig.withCredentials; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /sdk/models/Answer.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { 3 | Question 4 | } from '../index'; 5 | 6 | declare var Object: any; 7 | export interface AnswerInterface { 8 | "answer": string; 9 | "negativeVotes"?: number; 10 | "positiveVotes"?: number; 11 | "id"?: any; 12 | "questionId"?: any; 13 | question?: Question; 14 | } 15 | 16 | export class Answer implements AnswerInterface { 17 | "answer": string; 18 | "negativeVotes": number; 19 | "positiveVotes": number; 20 | "id": any; 21 | "questionId": any; 22 | question: Question; 23 | constructor(data?: AnswerInterface) { 24 | Object.assign(this, data); 25 | } 26 | /** 27 | * The name of the model represented by this $resource, 28 | * i.e. `Answer`. 29 | */ 30 | public static getModelName() { 31 | return "Answer"; 32 | } 33 | /** 34 | * @method factory 35 | * @author Jonathan Casarrubias 36 | * @license MIT 37 | * This method creates an instance of Answer for dynamic purposes. 38 | **/ 39 | public static factory(data: AnswerInterface): Answer{ 40 | return new Answer(data); 41 | } 42 | /** 43 | * @method getModelDefinition 44 | * @author Julien Ledun 45 | * @license MIT 46 | * This method returns an object that represents some of the model 47 | * definitions. 48 | **/ 49 | public static getModelDefinition() { 50 | return { 51 | name: 'Answer', 52 | plural: 'Answers', 53 | path: 'Answers', 54 | idName: 'id', 55 | properties: { 56 | "answer": { 57 | name: 'answer', 58 | type: 'string' 59 | }, 60 | "negativeVotes": { 61 | name: 'negativeVotes', 62 | type: 'number', 63 | default: 0 64 | }, 65 | "positiveVotes": { 66 | name: 'positiveVotes', 67 | type: 'number', 68 | default: 0 69 | }, 70 | "id": { 71 | name: 'id', 72 | type: 'any' 73 | }, 74 | "questionId": { 75 | name: 'questionId', 76 | type: 'any' 77 | }, 78 | }, 79 | relations: { 80 | question: { 81 | name: 'question', 82 | type: 'Question', 83 | model: 'Question', 84 | relationType: 'belongsTo', 85 | keyFrom: 'questionId', 86 | keyTo: 'id' 87 | }, 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /sdk/models/BaseModels.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | 3 | 4 | 5 | declare var Object: any; 6 | export interface LoopBackFilter { 7 | fields?: any; 8 | include?: any; 9 | limit?: any; 10 | order?: any; 11 | skip?: any; 12 | offset?: any; 13 | where?: any; 14 | } 15 | 16 | export interface AccessTokenInterface { 17 | "id"?: string; 18 | "ttl"?: number; 19 | "scopes"?: ["string"]; 20 | "created"?: Date; 21 | "userId"?: string; 22 | "user"?: any; 23 | } 24 | 25 | export class AccessToken implements AccessTokenInterface { 26 | "id": string; 27 | "ttl": number; 28 | "scopes": ["string"]; 29 | "created": Date; 30 | "userId": string; 31 | "user": any; 32 | constructor(data?: AccessTokenInterface) { 33 | Object.assign(this, data); 34 | } 35 | /** 36 | * The name of the model represented by this $resource, 37 | * i.e. `AccessToken`. 38 | */ 39 | public static getModelName() { 40 | return "AccessToken"; 41 | } 42 | /** 43 | * @method factory 44 | * @author Jonathan Casarrubias 45 | * @license MIT 46 | * This method creates an instance of AccessToken for dynamic purposes. 47 | **/ 48 | public static factory(data: AccessTokenInterface): AccessToken{ 49 | return new AccessToken(data); 50 | } 51 | /** 52 | * @method getModelDefinition 53 | * @author Julien Ledun 54 | * @license MIT 55 | * This method returns an object that represents some of the model 56 | * definitions. 57 | **/ 58 | public static getModelDefinition() { 59 | return { 60 | name: 'AccessToken', 61 | plural: 'AccessTokens', 62 | properties: { 63 | "id": { 64 | name: 'id', 65 | type: 'string' 66 | }, 67 | "ttl": { 68 | name: 'ttl', 69 | type: 'number', 70 | default: 1209600 71 | }, 72 | "scopes": { 73 | name: 'scopes', 74 | type: '["string"]' 75 | }, 76 | "created": { 77 | name: 'created', 78 | type: 'Date' 79 | }, 80 | "userId": { 81 | name: 'userId', 82 | type: 'string' 83 | }, 84 | }, 85 | relations: { 86 | user: { 87 | name: 'user', 88 | type: 'User', 89 | model: 'User' 90 | }, 91 | } 92 | } 93 | } 94 | } 95 | 96 | export class SDKToken implements AccessTokenInterface { 97 | id: any = null; 98 | ttl: number = null; 99 | scopes: any = null; 100 | created: any = null; 101 | userId: any = null; 102 | user: any = null; 103 | rememberMe: boolean = null; 104 | constructor(data?: AccessTokenInterface) { 105 | Object.assign(this, data); 106 | } 107 | } 108 | /** 109 | * This GeoPoint represents both, LoopBack and MongoDB GeoPoint 110 | **/ 111 | export interface GeoPoint { 112 | lat?: number; 113 | lng?: number; 114 | type?: string; 115 | coordinates?: number[]; 116 | } 117 | 118 | export interface StatFilter { 119 | range: string, 120 | custom?: { 121 | start: string, 122 | end: string 123 | }, 124 | where?: {}, 125 | groupBy?: string 126 | } 127 | -------------------------------------------------------------------------------- /sdk/models/Question.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { 3 | Answer 4 | } from '../index'; 5 | 6 | declare var Object: any; 7 | export interface QuestionInterface { 8 | "categorySlug"?: string; 9 | "questionSlug": string; 10 | "question": string; 11 | "negativeVotes"?: number; 12 | "positiveVotes"?: number; 13 | "id"?: any; 14 | answers?: Answer[]; 15 | } 16 | 17 | export class Question implements QuestionInterface { 18 | "categorySlug": string; 19 | "questionSlug": string; 20 | "question": string; 21 | "negativeVotes": number; 22 | "positiveVotes": number; 23 | "id": any; 24 | answers: Answer[]; 25 | constructor(data?: QuestionInterface) { 26 | Object.assign(this, data); 27 | } 28 | /** 29 | * The name of the model represented by this $resource, 30 | * i.e. `Question`. 31 | */ 32 | public static getModelName() { 33 | return "Question"; 34 | } 35 | /** 36 | * @method factory 37 | * @author Jonathan Casarrubias 38 | * @license MIT 39 | * This method creates an instance of Question for dynamic purposes. 40 | **/ 41 | public static factory(data: QuestionInterface): Question{ 42 | return new Question(data); 43 | } 44 | /** 45 | * @method getModelDefinition 46 | * @author Julien Ledun 47 | * @license MIT 48 | * This method returns an object that represents some of the model 49 | * definitions. 50 | **/ 51 | public static getModelDefinition() { 52 | return { 53 | name: 'Question', 54 | plural: 'Questions', 55 | path: 'Questions', 56 | idName: 'id', 57 | properties: { 58 | "categorySlug": { 59 | name: 'categorySlug', 60 | type: 'string' 61 | }, 62 | "questionSlug": { 63 | name: 'questionSlug', 64 | type: 'string' 65 | }, 66 | "question": { 67 | name: 'question', 68 | type: 'string' 69 | }, 70 | "negativeVotes": { 71 | name: 'negativeVotes', 72 | type: 'number', 73 | default: 0 74 | }, 75 | "positiveVotes": { 76 | name: 'positiveVotes', 77 | type: 'number', 78 | default: 0 79 | }, 80 | "id": { 81 | name: 'id', 82 | type: 'any' 83 | }, 84 | }, 85 | relations: { 86 | answers: { 87 | name: 'answers', 88 | type: 'Answer[]', 89 | model: 'Answer', 90 | relationType: 'hasMany', 91 | keyFrom: 'id', 92 | keyTo: 'questionId' 93 | }, 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /sdk/models/User.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | 3 | declare var Object: any; 4 | export interface UserInterface { 5 | "realm"?: string; 6 | "username"?: string; 7 | "email": string; 8 | "emailVerified"?: boolean; 9 | "id"?: number; 10 | "password"?: string; 11 | accessTokens?: any[]; 12 | } 13 | 14 | export class User implements UserInterface { 15 | "realm": string; 16 | "username": string; 17 | "email": string; 18 | "emailVerified": boolean; 19 | "id": number; 20 | "password": string; 21 | accessTokens: any[]; 22 | constructor(data?: UserInterface) { 23 | Object.assign(this, data); 24 | } 25 | /** 26 | * The name of the model represented by this $resource, 27 | * i.e. `User`. 28 | */ 29 | public static getModelName() { 30 | return "User"; 31 | } 32 | /** 33 | * @method factory 34 | * @author Jonathan Casarrubias 35 | * @license MIT 36 | * This method creates an instance of User for dynamic purposes. 37 | **/ 38 | public static factory(data: UserInterface): User{ 39 | return new User(data); 40 | } 41 | /** 42 | * @method getModelDefinition 43 | * @author Julien Ledun 44 | * @license MIT 45 | * This method returns an object that represents some of the model 46 | * definitions. 47 | **/ 48 | public static getModelDefinition() { 49 | return { 50 | name: 'User', 51 | plural: 'Users', 52 | path: 'Users', 53 | idName: 'id', 54 | properties: { 55 | "realm": { 56 | name: 'realm', 57 | type: 'string' 58 | }, 59 | "username": { 60 | name: 'username', 61 | type: 'string' 62 | }, 63 | "email": { 64 | name: 'email', 65 | type: 'string' 66 | }, 67 | "emailVerified": { 68 | name: 'emailVerified', 69 | type: 'boolean' 70 | }, 71 | "id": { 72 | name: 'id', 73 | type: 'number' 74 | }, 75 | "password": { 76 | name: 'password', 77 | type: 'string' 78 | }, 79 | }, 80 | relations: { 81 | accessTokens: { 82 | name: 'accessTokens', 83 | type: 'any[]', 84 | model: '', 85 | relationType: 'hasMany', 86 | keyFrom: 'id', 87 | keyTo: 'userId' 88 | }, 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /sdk/models/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | export * from './User'; 3 | export * from './Question'; 4 | export * from './Answer'; 5 | export * from './BaseModels'; 6 | 7 | -------------------------------------------------------------------------------- /sdk/services/core/auth.service.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | declare var Object: any; 3 | import { Injectable, Inject } from '@angular/core'; 4 | import { InternalStorage } from '../../storage/storage.swaps'; 5 | import { SDKToken } from '../../models/BaseModels'; 6 | /** 7 | * @author Jonathan Casarrubias 8 | * @module SocketConnection 9 | * @license MIT 10 | * @description 11 | * This module handle socket connections and return singleton instances for each 12 | * connection, it will use the SDK Socket Driver Available currently supporting 13 | * Angular 2 for web, NativeScript 2 and Angular Universal. 14 | **/ 15 | @Injectable() 16 | export class LoopBackAuth { 17 | /** 18 | * @type {SDKToken} 19 | **/ 20 | private token: SDKToken = new SDKToken(); 21 | /** 22 | * @type {string} 23 | **/ 24 | protected prefix: string = '$LoopBackSDK$'; 25 | /** 26 | * @method constructor 27 | * @param {InternalStorage} storage Internal Storage Driver 28 | * @description 29 | * The constructor will initialize the token loading data from storage 30 | **/ 31 | constructor(@Inject(InternalStorage) protected storage: InternalStorage) { 32 | this.token.id = this.load('id'); 33 | this.token.user = this.load('user'); 34 | this.token.userId = this.load('userId'); 35 | this.token.created = this.load('created'); 36 | this.token.ttl = this.load('ttl'); 37 | this.token.rememberMe = this.load('rememberMe'); 38 | } 39 | /** 40 | * @method setRememberMe 41 | * @param {boolean} value Flag to remember credentials 42 | * @return {void} 43 | * @description 44 | * This method will set a flag in order to remember the current credentials 45 | **/ 46 | public setRememberMe(value: boolean): void { 47 | this.token.rememberMe = value; 48 | } 49 | /** 50 | * @method setUser 51 | * @param {any} user Any type of user model 52 | * @return {void} 53 | * @description 54 | * This method will update the user information and persist it if the 55 | * rememberMe flag is set. 56 | **/ 57 | public setUser(user: any) { 58 | this.token.user = user; 59 | this.save(); 60 | } 61 | /** 62 | * @method setToken 63 | * @param {SDKToken} token SDKToken or casted AccessToken instance 64 | * @return {void} 65 | * @description 66 | * This method will set a flag in order to remember the current credentials 67 | **/ 68 | public setToken(token: SDKToken): void { 69 | this.token = Object.assign({}, this.token, token); 70 | this.save(); 71 | } 72 | /** 73 | * @method getToken 74 | * @return {void} 75 | * @description 76 | * This method will set a flag in order to remember the current credentials. 77 | **/ 78 | public getToken(): SDKToken { 79 | return this.token; 80 | } 81 | /** 82 | * @method getAccessTokenId 83 | * @return {string} 84 | * @description 85 | * This method will return the actual token string, not the object instance. 86 | **/ 87 | public getAccessTokenId(): string { 88 | return this.token.id; 89 | } 90 | /** 91 | * @method getCurrentUserId 92 | * @return {any} 93 | * @description 94 | * This method will return the current user id, it can be number or string. 95 | **/ 96 | public getCurrentUserId(): any { 97 | return this.token.userId; 98 | } 99 | /** 100 | * @method getCurrentUserData 101 | * @return {any} 102 | * @description 103 | * This method will return the current user instance. 104 | **/ 105 | public getCurrentUserData(): any { 106 | return (typeof this.token.user === 'string') ? JSON.parse(this.token.user) : this.token.user; 107 | } 108 | /** 109 | * @method save 110 | * @return {boolean} Whether or not the information was saved 111 | * @description 112 | * This method will save in either local storage or cookies the current credentials. 113 | * But only if rememberMe is enabled. 114 | **/ 115 | public save(): boolean { 116 | let today = new Date(); 117 | let expires = new Date(today.getTime() + (this.token.ttl * 1000)); 118 | this.persist('id', this.token.id, expires); 119 | this.persist('user', this.token.user, expires); 120 | this.persist('userId', this.token.userId, expires); 121 | this.persist('created', this.token.created, expires); 122 | this.persist('ttl', this.token.ttl, expires); 123 | this.persist('rememberMe', this.token.rememberMe, expires); 124 | return true; 125 | }; 126 | /** 127 | * @method load 128 | * @param {string} prop Property name 129 | * @return {any} Any information persisted in storage 130 | * @description 131 | * This method will load either from local storage or cookies the provided property. 132 | **/ 133 | protected load(prop: string): any { 134 | return this.storage.get(`${this.prefix}${prop}`); 135 | } 136 | /** 137 | * @method clear 138 | * @return {void} 139 | * @description 140 | * This method will clear cookies or the local storage. 141 | **/ 142 | public clear(): void { 143 | Object.keys(this.token).forEach((prop: string) => this.storage.remove(`${this.prefix}${prop}`)); 144 | this.token = new SDKToken(); 145 | } 146 | /** 147 | * @method persist 148 | * @return {void} 149 | * @description 150 | * This method saves values to storage 151 | **/ 152 | protected persist(prop: string, value: any, expires?: Date): void { 153 | try { 154 | this.storage.set( 155 | `${this.prefix}${prop}`, 156 | (typeof value === 'object') ? JSON.stringify(value) : value, 157 | this.token.rememberMe?expires:null 158 | ); 159 | } 160 | catch (err) { 161 | console.error('Cannot access local/session storage:', err); 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /sdk/services/core/base.service.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { Injectable, Inject, Optional } from '@angular/core'; 3 | import { Http, Headers, Request, RequestOptions } from '@angular/http'; 4 | import { NgModule, ModuleWithProviders } from '@angular/core'; 5 | import { JSONSearchParams } from './search.params'; 6 | import { ErrorHandler } from './error.service'; 7 | import { LoopBackAuth } from './auth.service'; 8 | import { LoopBackConfig } from '../../lb.config'; 9 | import { LoopBackFilter, AccessToken } from '../../models/BaseModels'; 10 | import { SDKModels } from '../custom/SDKModels'; 11 | import { Observable } from 'rxjs/Observable'; 12 | import { Subject } from 'rxjs/Subject'; 13 | import { ErrorObservable } from 'rxjs/observable/ErrorObservable'; 14 | import 'rxjs/add/operator/catch'; 15 | import 'rxjs/add/operator/map'; 16 | // Making Sure EventSource Type is available to avoid compilation issues. 17 | declare var EventSource: any; 18 | /** 19 | * @module BaseLoopBackApi 20 | * @author Jonathan Casarrubias <@johncasarrubias> 21 | * @author Nikolay Matiushenkov 22 | * @license MIT 23 | * @description 24 | * Abstract class that will be implemented in every custom service automatically built 25 | * by the sdk builder. 26 | * It provides the core functionallity for every API call, either by HTTP Calls or by 27 | * WebSockets. 28 | **/ 29 | @Injectable() 30 | export abstract class BaseLoopBackApi { 31 | 32 | protected path: string; 33 | protected model: any; 34 | 35 | constructor( 36 | @Inject(Http) protected http: Http, 37 | @Inject(SDKModels) protected models: SDKModels, 38 | @Inject(LoopBackAuth) protected auth: LoopBackAuth, 39 | @Inject(JSONSearchParams) protected searchParams: JSONSearchParams, 40 | @Optional() @Inject(ErrorHandler) protected errorHandler: ErrorHandler 41 | ) { 42 | this.model = this.models.get(this.getModelName()); 43 | } 44 | /** 45 | * @method request 46 | * @param {string} method Request method (GET, POST, PUT) 47 | * @param {string} url Request url (my-host/my-url/:id) 48 | * @param {any} routeParams Values of url parameters 49 | * @param {any} urlParams Parameters for building url (filter and other) 50 | * @param {any} postBody Request postBody 51 | * @return {Observable} 52 | * @description 53 | * This is a core method, every HTTP Call will be done from here, every API Service will 54 | * extend this class and use this method to get RESTful communication. 55 | **/ 56 | public request( 57 | method : string, 58 | url : string, 59 | routeParams : any = {}, 60 | urlParams : any = {}, 61 | postBody : any = {}, 62 | pubsub : boolean = false, 63 | customHeaders? : Function 64 | ): Observable { 65 | // Transpile route variables to the actual request Values 66 | Object.keys(routeParams).forEach((key: string) => { 67 | url = url.replace(new RegExp(":" + key + "(\/|$)", "g"), routeParams[key] + "$1") 68 | }); 69 | if (pubsub) { 70 | console.info('SDK: PubSub functionality is disabled, generate SDK using -io enabled'); 71 | } else { 72 | // Headers to be sent 73 | let headers: Headers = new Headers(); 74 | headers.append('Content-Type', 'application/json'); 75 | // Authenticate request 76 | this.authenticate(url, headers); 77 | // Body fix for built in remote methods using "data", "options" or "credentials 78 | // that are the actual body, Custom remote method properties are different and need 79 | // to be wrapped into a body object 80 | let body: any; 81 | let postBodyKeys = typeof postBody === 'object' ? Object.keys(postBody) : [] 82 | if (postBodyKeys.length === 1) { 83 | body = postBody[postBodyKeys.shift()]; 84 | } else { 85 | body = postBody; 86 | } 87 | let filter: string = ''; 88 | // Separate filter object from url params and add to search query 89 | if (urlParams.filter) { 90 | if (LoopBackConfig.isHeadersFilteringSet()) { 91 | headers.append('filter', JSON.stringify(urlParams.filter)); 92 | } else { 93 | filter = `?filter=${ encodeURIComponent(JSON.stringify(urlParams.filter))}`; 94 | } 95 | delete urlParams.filter; 96 | } 97 | // Separate where object from url params and add to search query 98 | /** 99 | CODE BELOW WILL GENERATE THE FOLLOWING ISSUES: 100 | - https://github.com/mean-expert-official/loopback-sdk-builder/issues/356 101 | - https://github.com/mean-expert-official/loopback-sdk-builder/issues/328 102 | if (urlParams.where) { 103 | headers.append('where', JSON.stringify(urlParams.where)); 104 | delete urlParams.where; 105 | } 106 | **/ 107 | if (typeof customHeaders === 'function') { 108 | headers = customHeaders(headers); 109 | } 110 | this.searchParams.setJSON(urlParams); 111 | let request: Request = new Request( 112 | new RequestOptions({ 113 | headers : headers, 114 | method : method, 115 | url : `${url}${filter}`, 116 | search : Object.keys(urlParams).length > 0 ? this.searchParams.getURLSearchParams() : null, 117 | body : body ? JSON.stringify(body) : undefined, 118 | withCredentials: LoopBackConfig.getRequestOptionsCredentials() 119 | }) 120 | ); 121 | return this.http.request(request) 122 | .map((res: any) => (res.text() != "" ? res.json() : {})) 123 | .catch((e) => this.errorHandler.handleError(e)); 124 | } 125 | } 126 | /** 127 | * @method authenticate 128 | * @author Jonathan Casarrubias 129 | * @license MIT 130 | * @param {string} url Server URL 131 | * @param {Headers} headers HTTP Headers 132 | * @return {void} 133 | * @description 134 | * This method will try to authenticate using either an access_token or basic http auth 135 | */ 136 | public authenticate(url: string, headers: Headers): void { 137 | if (this.auth.getAccessTokenId()) { 138 | headers.append( 139 | 'Authorization', 140 | LoopBackConfig.getAuthPrefix() + this.auth.getAccessTokenId() 141 | ); 142 | } 143 | } 144 | /** 145 | * @method create 146 | * @author Jonathan Casarrubias 147 | * @license MIT 148 | * @param {T} data Generic data type 149 | * @return {Observable} 150 | * @description 151 | * Generic create method 152 | */ 153 | public create(data: T, customHeaders?: Function): Observable { 154 | return this.request('POST', [ 155 | LoopBackConfig.getPath(), 156 | LoopBackConfig.getApiVersion(), 157 | this.model.getModelDefinition().path 158 | ].join('/'), undefined, undefined, { data }, null, customHeaders).map((data: T) => this.model.factory(data)); 159 | } 160 | /** 161 | * @method createMany 162 | * @author Jonathan Casarrubias 163 | * @license MIT 164 | * @param {T[]} data Generic data type array 165 | * @return {Observable} 166 | * @description 167 | * Generic create many method 168 | */ 169 | public createMany(data: T[], customHeaders?: Function): Observable { 170 | return this.request('POST', [ 171 | LoopBackConfig.getPath(), 172 | LoopBackConfig.getApiVersion(), 173 | this.model.getModelDefinition().path 174 | ].join('/'), undefined, undefined, { data }, null, customHeaders) 175 | .map((datum: T[]) => datum.map((data: T) => this.model.factory(data))); 176 | } 177 | /** 178 | * @method findById 179 | * @author Jonathan Casarrubias 180 | * @license MIT 181 | * @param {any} data Generic data type 182 | * @return {Observable} 183 | * @description 184 | * Generic findById method 185 | */ 186 | public findById(id: any, filter: LoopBackFilter = {}, customHeaders?: Function): Observable { 187 | let _urlParams: any = {}; 188 | if (filter) _urlParams.filter = filter; 189 | return this.request('GET', [ 190 | LoopBackConfig.getPath(), 191 | LoopBackConfig.getApiVersion(), 192 | this.model.getModelDefinition().path, 193 | ':id' 194 | ].join('/'), { id }, _urlParams, undefined, null, customHeaders) 195 | .map((data: T) => this.model.factory(data)); 196 | } 197 | /** 198 | * @method find 199 | * @author Jonathan Casarrubias 200 | * @license MIT 201 | * @return {Observable} 202 | * @description 203 | * Generic find method 204 | */ 205 | public find(filter: LoopBackFilter = {}, customHeaders?: Function): Observable { 206 | return this.request('GET', [ 207 | LoopBackConfig.getPath(), 208 | LoopBackConfig.getApiVersion(), 209 | this.model.getModelDefinition().path 210 | ].join('/'), undefined, { filter }, undefined, null, customHeaders) 211 | .map((datum: T[]) => datum.map((data: T) => this.model.factory(data))); 212 | } 213 | /** 214 | * @method exists 215 | * @author Jonathan Casarrubias 216 | * @license MIT 217 | * @return {Observable} 218 | * @description 219 | * Generic exists method 220 | */ 221 | public exists(id: any, customHeaders?: Function): Observable { 222 | return this.request('GET', [ 223 | LoopBackConfig.getPath(), 224 | LoopBackConfig.getApiVersion(), 225 | this.model.getModelDefinition().path, 226 | ':id/exists' 227 | ].join('/'), { id }, undefined, undefined, null, customHeaders); 228 | } 229 | /** 230 | * @method findOne 231 | * @author Jonathan Casarrubias 232 | * @license MIT 233 | * @return {Observable} 234 | * @description 235 | * Generic findOne method 236 | */ 237 | public findOne(filter: LoopBackFilter = {}, customHeaders?: Function): Observable { 238 | return this.request('GET', [ 239 | LoopBackConfig.getPath(), 240 | LoopBackConfig.getApiVersion(), 241 | this.model.getModelDefinition().path, 242 | 'findOne' 243 | ].join('/'), undefined, { filter }, undefined, null, customHeaders) 244 | .map((data: T) => this.model.factory(data)); 245 | } 246 | /** 247 | * @method updateAll 248 | * @author Jonathan Casarrubias 249 | * @license MIT 250 | * @return {Observable} 251 | * @description 252 | * Generic updateAll method 253 | */ 254 | public updateAll(where: any = {}, data: T, customHeaders?: Function): Observable<{ count: 'number' }> { 255 | let _urlParams: any = {}; 256 | if (where) _urlParams.where = where; 257 | return this.request('POST', [ 258 | LoopBackConfig.getPath(), 259 | LoopBackConfig.getApiVersion(), 260 | this.model.getModelDefinition().path, 261 | 'update' 262 | ].join('/'), undefined, _urlParams, { data }, null, customHeaders); 263 | } 264 | /** 265 | * @method deleteById 266 | * @author Jonathan Casarrubias 267 | * @license MIT 268 | * @return {Observable} 269 | * @description 270 | * Generic deleteById method 271 | */ 272 | public deleteById(id: any, customHeaders?: Function): Observable { 273 | return this.request('DELETE', [ 274 | LoopBackConfig.getPath(), 275 | LoopBackConfig.getApiVersion(), 276 | this.model.getModelDefinition().path, 277 | ':id' 278 | ].join('/'), { id }, undefined, undefined, null, customHeaders) 279 | .map((data: T) => this.model.factory(data)); 280 | } 281 | /** 282 | * @method count 283 | * @author Jonathan Casarrubias 284 | * @license MIT 285 | * @return {Observable<{ count: number }>} 286 | * @description 287 | * Generic count method 288 | */ 289 | public count(where: any = {}, customHeaders?: Function): Observable<{ count: number }> { 290 | let _urlParams: any = {}; 291 | if (where) _urlParams.where = where; 292 | return this.request('GET', [ 293 | LoopBackConfig.getPath(), 294 | LoopBackConfig.getApiVersion(), 295 | this.model.getModelDefinition().path, 296 | 'count' 297 | ].join('/'), undefined, _urlParams, undefined, null, customHeaders); 298 | } 299 | /** 300 | * @method updateAttributes 301 | * @author Jonathan Casarrubias 302 | * @license MIT 303 | * @return {Observable} 304 | * @description 305 | * Generic updateAttributes method 306 | */ 307 | public updateAttributes(id: any, data: T, customHeaders?: Function): Observable { 308 | return this.request('PUT', [ 309 | LoopBackConfig.getPath(), 310 | LoopBackConfig.getApiVersion(), 311 | this.model.getModelDefinition().path, 312 | ':id' 313 | ].join('/'), { id }, undefined, { data }, null, customHeaders) 314 | .map((data: T) => this.model.factory(data)); 315 | } 316 | /** 317 | * @method upsert 318 | * @author Jonathan Casarrubias 319 | * @license MIT 320 | * @return {Observable} 321 | * @description 322 | * Generic upsert method 323 | */ 324 | public upsert(data: any = {}, customHeaders?: Function): Observable { 325 | return this.request('PUT', [ 326 | LoopBackConfig.getPath(), 327 | LoopBackConfig.getApiVersion(), 328 | this.model.getModelDefinition().path, 329 | ].join('/'), undefined, undefined, { data }, null, customHeaders) 330 | .map((data: T) => this.model.factory(data)); 331 | } 332 | /** 333 | * @method upsertPatch 334 | * @author Jonathan Casarrubias 335 | * @license MIT 336 | * @return {Observable} 337 | * @description 338 | * Generic upsert method using patch http method 339 | */ 340 | public upsertPatch(data: any = {}, customHeaders?: Function): Observable { 341 | return this.request('PATCH', [ 342 | LoopBackConfig.getPath(), 343 | LoopBackConfig.getApiVersion(), 344 | this.model.getModelDefinition().path, 345 | ].join('/'), undefined, undefined, { data }, null, customHeaders) 346 | .map((data: T) => this.model.factory(data)); 347 | } 348 | /** 349 | * @method upsertWithWhere 350 | * @author Jonathan Casarrubias 351 | * @license MIT 352 | * @return {Observable} 353 | * @description 354 | * Generic upsertWithWhere method 355 | */ 356 | public upsertWithWhere(where: any = {}, data: any = {}, customHeaders?: Function): Observable { 357 | let _urlParams: any = {}; 358 | if (where) _urlParams.where = where; 359 | return this.request('POST', [ 360 | LoopBackConfig.getPath(), 361 | LoopBackConfig.getApiVersion(), 362 | this.model.getModelDefinition().path, 363 | 'upsertWithWhere' 364 | ].join('/'), undefined, _urlParams, { data }, null, customHeaders) 365 | .map((data: T) => this.model.factory(data)); 366 | } 367 | /** 368 | * @method replaceOrCreate 369 | * @author Jonathan Casarrubias 370 | * @license MIT 371 | * @return {Observable} 372 | * @description 373 | * Generic replaceOrCreate method 374 | */ 375 | public replaceOrCreate(data: any = {}, customHeaders?: Function): Observable { 376 | return this.request('POST', [ 377 | LoopBackConfig.getPath(), 378 | LoopBackConfig.getApiVersion(), 379 | this.model.getModelDefinition().path, 380 | 'replaceOrCreate' 381 | ].join('/'), undefined, undefined, { data }, null, customHeaders) 382 | .map((data: T) => this.model.factory(data)); 383 | } 384 | /** 385 | * @method replaceById 386 | * @author Jonathan Casarrubias 387 | * @license MIT 388 | * @return {Observable} 389 | * @description 390 | * Generic replaceById method 391 | */ 392 | public replaceById(id: any, data: any = {}, customHeaders?: Function): Observable { 393 | return this.request('POST', [ 394 | LoopBackConfig.getPath(), 395 | LoopBackConfig.getApiVersion(), 396 | this.model.getModelDefinition().path, 397 | ':id', 'replace' 398 | ].join('/'), { id }, undefined, { data }, null, customHeaders) 399 | .map((data: T) => this.model.factory(data)); 400 | } 401 | /** 402 | * @method createChangeStream 403 | * @author Jonathan Casarrubias 404 | * @license MIT 405 | * @return {Observable} 406 | * @description 407 | * Generic createChangeStream method 408 | */ 409 | public createChangeStream(): Observable { 410 | let subject = new Subject(); 411 | if (typeof EventSource !== 'undefined') { 412 | let emit = (msg: any) => subject.next(JSON.parse(msg.data)); 413 | var source = new EventSource([ 414 | LoopBackConfig.getPath(), 415 | LoopBackConfig.getApiVersion(), 416 | this.model.getModelDefinition().path, 417 | 'change-stream' 418 | ].join('/')); 419 | source.addEventListener('data', emit); 420 | source.onerror = emit; 421 | } else { 422 | console.warn('SDK Builder: EventSource is not supported'); 423 | } 424 | return subject.asObservable(); 425 | } 426 | /** 427 | * @method getModelName 428 | * @author Jonathan Casarrubias 429 | * @license MIT 430 | * @return {string} 431 | * @description 432 | * Abstract getModelName method 433 | */ 434 | abstract getModelName(): string; 435 | } 436 | -------------------------------------------------------------------------------- /sdk/services/core/error.service.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { Injectable } from '@angular/core'; 3 | import { Response } from '@angular/http'; 4 | import { Observable } from 'rxjs/Observable'; 5 | //import { ErrorObservable } from 'rxjs/observable/ErrorObservable'; 6 | import 'rxjs/add/observable/throw'; 7 | /** 8 | * Default error handler 9 | */ 10 | @Injectable() 11 | export class ErrorHandler { 12 | // ErrorObservable when rxjs version < rc.5 13 | // ErrorObservable when rxjs version = rc.5 14 | // I'm leaving any for now to avoid breaking apps using both versions 15 | public handleError(error: Response): any { 16 | return Observable.throw(error.json().error || 'Server error'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sdk/services/core/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | export * from './auth.service'; 3 | export * from './error.service'; 4 | export * from './search.params'; 5 | export * from './base.service'; 6 | 7 | -------------------------------------------------------------------------------- /sdk/services/core/search.params.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { Injectable } from '@angular/core'; 3 | import { URLSearchParams } from '@angular/http'; 4 | /** 5 | * @author Jonathan Casarrubias 6 | * @module JSONSearchParams 7 | * @license MIT 8 | * @description 9 | * JSON Parser and Wrapper for the Angular2 URLSearchParams 10 | * This module correctly encodes a json object into a query string and then creates 11 | * an instance of the URLSearchParams component for later use in HTTP Calls 12 | **/ 13 | @Injectable() 14 | export class JSONSearchParams { 15 | 16 | private _usp: URLSearchParams; 17 | 18 | public setJSON(obj: any) { 19 | this._usp = new URLSearchParams(this._JSON2URL(obj, false)); 20 | } 21 | 22 | public getURLSearchParams(): URLSearchParams { 23 | return this._usp; 24 | } 25 | 26 | private _JSON2URL(obj: any, parent: any) { 27 | var parts: any = []; 28 | for (var key in obj) 29 | parts.push(this._parseParam(key, obj[key], parent)); 30 | return parts.join('&'); 31 | } 32 | 33 | private _parseParam(key: string, value: any, parent: string) { 34 | let processedKey = parent ? parent + '[' + key + ']' : key; 35 | if (value && ((typeof value) === 'object' || Array.isArray(value))) { 36 | return this._JSON2URL(value, processedKey); 37 | } 38 | return processedKey + '=' + value; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /sdk/services/custom/Answer.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { Injectable, Inject, Optional } from '@angular/core'; 3 | import { Http, Response } from '@angular/http'; 4 | import { SDKModels } from './SDKModels'; 5 | import { BaseLoopBackApi } from '../core/base.service'; 6 | import { LoopBackConfig } from '../../lb.config'; 7 | import { LoopBackAuth } from '../core/auth.service'; 8 | import { LoopBackFilter, } from '../../models/BaseModels'; 9 | import { JSONSearchParams } from '../core/search.params'; 10 | import { ErrorHandler } from '../core/error.service'; 11 | import { Subject } from 'rxjs/Subject'; 12 | import { Observable } from 'rxjs/Rx'; 13 | import { Answer } from '../../models/Answer'; 14 | import { Question } from '../../models/Question'; 15 | 16 | 17 | /** 18 | * Api services for the `Answer` model. 19 | */ 20 | @Injectable() 21 | export class AnswerApi extends BaseLoopBackApi { 22 | 23 | constructor( 24 | @Inject(Http) protected http: Http, 25 | @Inject(SDKModels) protected models: SDKModels, 26 | @Inject(LoopBackAuth) protected auth: LoopBackAuth, 27 | @Inject(JSONSearchParams) protected searchParams: JSONSearchParams, 28 | @Optional() @Inject(ErrorHandler) protected errorHandler: ErrorHandler 29 | ) { 30 | super(http, models, auth, searchParams, errorHandler); 31 | } 32 | 33 | /** 34 | * Fetches belongsTo relation question. 35 | * 36 | * @param {any} id answer id 37 | * 38 | * @param {boolean} refresh 39 | * 40 | * @returns {object} An empty reference that will be 41 | * populated with the actual data once the response is returned 42 | * from the server. 43 | * 44 | * 45 | * (The remote method definition does not provide any description. 46 | * This usually means the response is a `Answer` object.) 47 | * 48 | */ 49 | public getQuestion(id: any, refresh: any = {}, customHeaders?: Function): Observable { 50 | let _method: string = "GET"; 51 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 52 | "/answers/:id/question"; 53 | let _routeParams: any = { 54 | id: id 55 | }; 56 | let _postBody: any = {}; 57 | let _urlParams: any = {}; 58 | if (typeof refresh !== 'undefined' && refresh !== null) _urlParams.refresh = refresh; 59 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 60 | return result; 61 | } 62 | 63 | /** 64 | * Patch an existing model instance or insert a new one into the data source. 65 | * 66 | * @param {object} data Request data. 67 | * 68 | * - `data` – `{object}` - Model instance data 69 | * 70 | * @returns {object} An empty reference that will be 71 | * populated with the actual data once the response is returned 72 | * from the server. 73 | * 74 | * 75 | * (The remote method definition does not provide any description. 76 | * This usually means the response is a `Answer` object.) 77 | * 78 | */ 79 | public patchOrCreate(data: any = {}, customHeaders?: Function): Observable { 80 | let _method: string = "PATCH"; 81 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 82 | "/answers"; 83 | let _routeParams: any = {}; 84 | let _postBody: any = { 85 | data: data 86 | }; 87 | let _urlParams: any = {}; 88 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 89 | return result; 90 | } 91 | 92 | /** 93 | * Patch attributes for a model instance and persist it into the data source. 94 | * 95 | * @param {any} id answer id 96 | * 97 | * @param {object} data Request data. 98 | * 99 | * - `data` – `{object}` - An object of model property name/value pairs 100 | * 101 | * @returns {object} An empty reference that will be 102 | * populated with the actual data once the response is returned 103 | * from the server. 104 | * 105 | * 106 | * (The remote method definition does not provide any description. 107 | * This usually means the response is a `Answer` object.) 108 | * 109 | */ 110 | public patchAttributes(id: any, data: any = {}, customHeaders?: Function): Observable { 111 | let _method: string = "PATCH"; 112 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 113 | "/answers/:id"; 114 | let _routeParams: any = { 115 | id: id 116 | }; 117 | let _postBody: any = { 118 | data: data 119 | }; 120 | let _urlParams: any = {}; 121 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 122 | return result; 123 | } 124 | 125 | /** 126 | * The name of the model represented by this $resource, 127 | * i.e. `Answer`. 128 | */ 129 | public getModelName() { 130 | return "Answer"; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /sdk/services/custom/Question.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { Injectable, Inject, Optional } from '@angular/core'; 3 | import { Http, Response } from '@angular/http'; 4 | import { SDKModels } from './SDKModels'; 5 | import { BaseLoopBackApi } from '../core/base.service'; 6 | import { LoopBackConfig } from '../../lb.config'; 7 | import { LoopBackAuth } from '../core/auth.service'; 8 | import { LoopBackFilter, } from '../../models/BaseModels'; 9 | import { JSONSearchParams } from '../core/search.params'; 10 | import { ErrorHandler } from '../core/error.service'; 11 | import { Subject } from 'rxjs/Subject'; 12 | import { Observable } from 'rxjs/Rx'; 13 | import { Question } from '../../models/Question'; 14 | import { Answer } from '../../models/Answer'; 15 | 16 | 17 | /** 18 | * Api services for the `Question` model. 19 | */ 20 | @Injectable() 21 | export class QuestionApi extends BaseLoopBackApi { 22 | 23 | constructor( 24 | @Inject(Http) protected http: Http, 25 | @Inject(SDKModels) protected models: SDKModels, 26 | @Inject(LoopBackAuth) protected auth: LoopBackAuth, 27 | @Inject(JSONSearchParams) protected searchParams: JSONSearchParams, 28 | @Optional() @Inject(ErrorHandler) protected errorHandler: ErrorHandler 29 | ) { 30 | super(http, models, auth, searchParams, errorHandler); 31 | } 32 | 33 | /** 34 | * Find a related item by id for answers. 35 | * 36 | * @param {any} id question id 37 | * 38 | * @param {any} fk Foreign key for answers 39 | * 40 | * @returns {object} An empty reference that will be 41 | * populated with the actual data once the response is returned 42 | * from the server. 43 | * 44 | * 45 | * (The remote method definition does not provide any description. 46 | * This usually means the response is a `Question` object.) 47 | * 48 | */ 49 | public findByIdAnswers(id: any, fk: any, customHeaders?: Function): Observable { 50 | let _method: string = "GET"; 51 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 52 | "/questions/:id/answers/:fk"; 53 | let _routeParams: any = { 54 | id: id, 55 | fk: fk 56 | }; 57 | let _postBody: any = {}; 58 | let _urlParams: any = {}; 59 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 60 | return result; 61 | } 62 | 63 | /** 64 | * Delete a related item by id for answers. 65 | * 66 | * @param {any} id question id 67 | * 68 | * @param {any} fk Foreign key for answers 69 | * 70 | * @returns {object} An empty reference that will be 71 | * populated with the actual data once the response is returned 72 | * from the server. 73 | * 74 | * This method returns no data. 75 | */ 76 | public destroyByIdAnswers(id: any, fk: any, customHeaders?: Function): Observable { 77 | let _method: string = "DELETE"; 78 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 79 | "/questions/:id/answers/:fk"; 80 | let _routeParams: any = { 81 | id: id, 82 | fk: fk 83 | }; 84 | let _postBody: any = {}; 85 | let _urlParams: any = {}; 86 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 87 | return result; 88 | } 89 | 90 | /** 91 | * Update a related item by id for answers. 92 | * 93 | * @param {any} id question id 94 | * 95 | * @param {any} fk Foreign key for answers 96 | * 97 | * @param {object} data Request data. 98 | * 99 | * This method expects a subset of model properties as request parameters. 100 | * 101 | * @returns {object} An empty reference that will be 102 | * populated with the actual data once the response is returned 103 | * from the server. 104 | * 105 | * 106 | * (The remote method definition does not provide any description. 107 | * This usually means the response is a `Question` object.) 108 | * 109 | */ 110 | public updateByIdAnswers(id: any, fk: any, data: any = {}, customHeaders?: Function): Observable { 111 | let _method: string = "PUT"; 112 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 113 | "/questions/:id/answers/:fk"; 114 | let _routeParams: any = { 115 | id: id, 116 | fk: fk 117 | }; 118 | let _postBody: any = { 119 | data: data 120 | }; 121 | let _urlParams: any = {}; 122 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 123 | return result; 124 | } 125 | 126 | /** 127 | * Queries answers of question. 128 | * 129 | * @param {any} id question id 130 | * 131 | * @param {object} filter 132 | * 133 | * @returns {object[]} An empty reference that will be 134 | * populated with the actual data once the response is returned 135 | * from the server. 136 | * 137 | * 138 | * (The remote method definition does not provide any description. 139 | * This usually means the response is a `Question` object.) 140 | * 141 | */ 142 | public getAnswers(id: any, filter: LoopBackFilter = {}, customHeaders?: Function): Observable { 143 | let _method: string = "GET"; 144 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 145 | "/questions/:id/answers"; 146 | let _routeParams: any = { 147 | id: id 148 | }; 149 | let _postBody: any = {}; 150 | let _urlParams: any = {}; 151 | if (typeof filter !== 'undefined' && filter !== null) _urlParams.filter = filter; 152 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 153 | return result; 154 | } 155 | 156 | /** 157 | * Creates a new instance in answers of this model. 158 | * 159 | * @param {any} id question id 160 | * 161 | * @param {object} data Request data. 162 | * 163 | * This method expects a subset of model properties as request parameters. 164 | * 165 | * @returns {object} An empty reference that will be 166 | * populated with the actual data once the response is returned 167 | * from the server. 168 | * 169 | * 170 | * (The remote method definition does not provide any description. 171 | * This usually means the response is a `Question` object.) 172 | * 173 | */ 174 | public createAnswers(id: any, data: any = {}, customHeaders?: Function): Observable { 175 | let _method: string = "POST"; 176 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 177 | "/questions/:id/answers"; 178 | let _routeParams: any = { 179 | id: id 180 | }; 181 | let _postBody: any = { 182 | data: data 183 | }; 184 | let _urlParams: any = {}; 185 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 186 | return result; 187 | } 188 | 189 | /** 190 | * Deletes all answers of this model. 191 | * 192 | * @param {any} id question id 193 | * 194 | * @returns {object} An empty reference that will be 195 | * populated with the actual data once the response is returned 196 | * from the server. 197 | * 198 | * This method returns no data. 199 | */ 200 | public deleteAnswers(id: any, customHeaders?: Function): Observable { 201 | let _method: string = "DELETE"; 202 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 203 | "/questions/:id/answers"; 204 | let _routeParams: any = { 205 | id: id 206 | }; 207 | let _postBody: any = {}; 208 | let _urlParams: any = {}; 209 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 210 | return result; 211 | } 212 | 213 | /** 214 | * Counts answers of question. 215 | * 216 | * @param {any} id question id 217 | * 218 | * @param {object} where Criteria to match model instances 219 | * 220 | * @returns {object} An empty reference that will be 221 | * populated with the actual data once the response is returned 222 | * from the server. 223 | * 224 | * Data properties: 225 | * 226 | * - `count` – `{number}` - 227 | */ 228 | public countAnswers(id: any, where: any = {}, customHeaders?: Function): Observable { 229 | let _method: string = "GET"; 230 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 231 | "/questions/:id/answers/count"; 232 | let _routeParams: any = { 233 | id: id 234 | }; 235 | let _postBody: any = {}; 236 | let _urlParams: any = {}; 237 | if (typeof where !== 'undefined' && where !== null) _urlParams.where = where; 238 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 239 | return result; 240 | } 241 | 242 | /** 243 | * Patch an existing model instance or insert a new one into the data source. 244 | * 245 | * @param {object} data Request data. 246 | * 247 | * - `data` – `{object}` - Model instance data 248 | * 249 | * @returns {object} An empty reference that will be 250 | * populated with the actual data once the response is returned 251 | * from the server. 252 | * 253 | * 254 | * (The remote method definition does not provide any description. 255 | * This usually means the response is a `Question` object.) 256 | * 257 | */ 258 | public patchOrCreate(data: any = {}, customHeaders?: Function): Observable { 259 | let _method: string = "PATCH"; 260 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 261 | "/questions"; 262 | let _routeParams: any = {}; 263 | let _postBody: any = { 264 | data: data 265 | }; 266 | let _urlParams: any = {}; 267 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 268 | return result; 269 | } 270 | 271 | /** 272 | * Patch attributes for a model instance and persist it into the data source. 273 | * 274 | * @param {any} id question id 275 | * 276 | * @param {object} data Request data. 277 | * 278 | * - `data` – `{object}` - An object of model property name/value pairs 279 | * 280 | * @returns {object} An empty reference that will be 281 | * populated with the actual data once the response is returned 282 | * from the server. 283 | * 284 | * 285 | * (The remote method definition does not provide any description. 286 | * This usually means the response is a `Question` object.) 287 | * 288 | */ 289 | public patchAttributes(id: any, data: any = {}, customHeaders?: Function): Observable { 290 | let _method: string = "PATCH"; 291 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 292 | "/questions/:id"; 293 | let _routeParams: any = { 294 | id: id 295 | }; 296 | let _postBody: any = { 297 | data: data 298 | }; 299 | let _urlParams: any = {}; 300 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 301 | return result; 302 | } 303 | 304 | /** 305 | * Creates a new instance in answers of this model. 306 | * 307 | * @param {any} id question id 308 | * 309 | * @param {object} data Request data. 310 | * 311 | * This method expects a subset of model properties as request parameters. 312 | * 313 | * @returns {object[]} An empty reference that will be 314 | * populated with the actual data once the response is returned 315 | * from the server. 316 | * 317 | * 318 | * (The remote method definition does not provide any description. 319 | * This usually means the response is a `Question` object.) 320 | * 321 | */ 322 | public createManyAnswers(id: any, data: any[] = [], customHeaders?: Function): Observable { 323 | let _method: string = "POST"; 324 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 325 | "/questions/:id/answers"; 326 | let _routeParams: any = { 327 | id: id 328 | }; 329 | let _postBody: any = { 330 | data: data 331 | }; 332 | let _urlParams: any = {}; 333 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 334 | return result; 335 | } 336 | 337 | /** 338 | * The name of the model represented by this $resource, 339 | * i.e. `Question`. 340 | */ 341 | public getModelName() { 342 | return "Question"; 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /sdk/services/custom/SDKModels.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { Injectable } from '@angular/core'; 3 | import { User } from '../../models/User'; 4 | import { Question } from '../../models/Question'; 5 | import { Answer } from '../../models/Answer'; 6 | 7 | export interface Models { [name: string]: any } 8 | 9 | @Injectable() 10 | export class SDKModels { 11 | 12 | private models: Models = { 13 | User: User, 14 | Question: Question, 15 | Answer: Answer, 16 | 17 | }; 18 | 19 | public get(modelName: string): any { 20 | return this.models[modelName]; 21 | } 22 | 23 | public getAll(): Models { 24 | return this.models; 25 | } 26 | 27 | public getModelNames(): string[] { 28 | return Object.keys(this.models); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sdk/services/custom/User.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { Injectable, Inject, Optional } from '@angular/core'; 3 | import { Http, Response } from '@angular/http'; 4 | import { SDKModels } from './SDKModels'; 5 | import { BaseLoopBackApi } from '../core/base.service'; 6 | import { LoopBackConfig } from '../../lb.config'; 7 | import { LoopBackAuth } from '../core/auth.service'; 8 | import { LoopBackFilter, SDKToken, AccessToken } from '../../models/BaseModels'; 9 | import { JSONSearchParams } from '../core/search.params'; 10 | import { ErrorHandler } from '../core/error.service'; 11 | import { Subject } from 'rxjs/Subject'; 12 | import { Observable } from 'rxjs/Rx'; 13 | import { User } from '../../models/User'; 14 | 15 | 16 | /** 17 | * Api services for the `User` model. 18 | */ 19 | @Injectable() 20 | export class UserApi extends BaseLoopBackApi { 21 | 22 | constructor( 23 | @Inject(Http) protected http: Http, 24 | @Inject(SDKModels) protected models: SDKModels, 25 | @Inject(LoopBackAuth) protected auth: LoopBackAuth, 26 | @Inject(JSONSearchParams) protected searchParams: JSONSearchParams, 27 | @Optional() @Inject(ErrorHandler) protected errorHandler: ErrorHandler 28 | ) { 29 | super(http, models, auth, searchParams, errorHandler); 30 | } 31 | 32 | /** 33 | * Find a related item by id for accessTokens. 34 | * 35 | * @param {any} id User id 36 | * 37 | * @param {any} fk Foreign key for accessTokens 38 | * 39 | * @returns {object} An empty reference that will be 40 | * populated with the actual data once the response is returned 41 | * from the server. 42 | * 43 | * 44 | * (The remote method definition does not provide any description. 45 | * This usually means the response is a `User` object.) 46 | * 47 | */ 48 | public findByIdAccessTokens(id: any, fk: any, customHeaders?: Function): Observable { 49 | let _method: string = "GET"; 50 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 51 | "/Users/:id/accessTokens/:fk"; 52 | let _routeParams: any = { 53 | id: id, 54 | fk: fk 55 | }; 56 | let _postBody: any = {}; 57 | let _urlParams: any = {}; 58 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 59 | return result; 60 | } 61 | 62 | /** 63 | * Delete a related item by id for accessTokens. 64 | * 65 | * @param {any} id User id 66 | * 67 | * @param {any} fk Foreign key for accessTokens 68 | * 69 | * @returns {object} An empty reference that will be 70 | * populated with the actual data once the response is returned 71 | * from the server. 72 | * 73 | * This method returns no data. 74 | */ 75 | public destroyByIdAccessTokens(id: any, fk: any, customHeaders?: Function): Observable { 76 | let _method: string = "DELETE"; 77 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 78 | "/Users/:id/accessTokens/:fk"; 79 | let _routeParams: any = { 80 | id: id, 81 | fk: fk 82 | }; 83 | let _postBody: any = {}; 84 | let _urlParams: any = {}; 85 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 86 | return result; 87 | } 88 | 89 | /** 90 | * Update a related item by id for accessTokens. 91 | * 92 | * @param {any} id User id 93 | * 94 | * @param {any} fk Foreign key for accessTokens 95 | * 96 | * @param {object} data Request data. 97 | * 98 | * This method expects a subset of model properties as request parameters. 99 | * 100 | * @returns {object} An empty reference that will be 101 | * populated with the actual data once the response is returned 102 | * from the server. 103 | * 104 | * 105 | * (The remote method definition does not provide any description. 106 | * This usually means the response is a `User` object.) 107 | * 108 | */ 109 | public updateByIdAccessTokens(id: any, fk: any, data: any = {}, customHeaders?: Function): Observable { 110 | let _method: string = "PUT"; 111 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 112 | "/Users/:id/accessTokens/:fk"; 113 | let _routeParams: any = { 114 | id: id, 115 | fk: fk 116 | }; 117 | let _postBody: any = { 118 | data: data 119 | }; 120 | let _urlParams: any = {}; 121 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 122 | return result; 123 | } 124 | 125 | /** 126 | * Queries accessTokens of User. 127 | * 128 | * @param {any} id User id 129 | * 130 | * @param {object} filter 131 | * 132 | * @returns {object[]} An empty reference that will be 133 | * populated with the actual data once the response is returned 134 | * from the server. 135 | * 136 | * 137 | * (The remote method definition does not provide any description. 138 | * This usually means the response is a `User` object.) 139 | * 140 | */ 141 | public getAccessTokens(id: any, filter: LoopBackFilter = {}, customHeaders?: Function): Observable { 142 | let _method: string = "GET"; 143 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 144 | "/Users/:id/accessTokens"; 145 | let _routeParams: any = { 146 | id: id 147 | }; 148 | let _postBody: any = {}; 149 | let _urlParams: any = {}; 150 | if (typeof filter !== 'undefined' && filter !== null) _urlParams.filter = filter; 151 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 152 | return result; 153 | } 154 | 155 | /** 156 | * Creates a new instance in accessTokens of this model. 157 | * 158 | * @param {any} id User id 159 | * 160 | * @param {object} data Request data. 161 | * 162 | * This method expects a subset of model properties as request parameters. 163 | * 164 | * @returns {object} An empty reference that will be 165 | * populated with the actual data once the response is returned 166 | * from the server. 167 | * 168 | * 169 | * (The remote method definition does not provide any description. 170 | * This usually means the response is a `User` object.) 171 | * 172 | */ 173 | public createAccessTokens(id: any, data: any = {}, customHeaders?: Function): Observable { 174 | let _method: string = "POST"; 175 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 176 | "/Users/:id/accessTokens"; 177 | let _routeParams: any = { 178 | id: id 179 | }; 180 | let _postBody: any = { 181 | data: data 182 | }; 183 | let _urlParams: any = {}; 184 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 185 | return result; 186 | } 187 | 188 | /** 189 | * Deletes all accessTokens of this model. 190 | * 191 | * @param {any} id User id 192 | * 193 | * @returns {object} An empty reference that will be 194 | * populated with the actual data once the response is returned 195 | * from the server. 196 | * 197 | * This method returns no data. 198 | */ 199 | public deleteAccessTokens(id: any, customHeaders?: Function): Observable { 200 | let _method: string = "DELETE"; 201 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 202 | "/Users/:id/accessTokens"; 203 | let _routeParams: any = { 204 | id: id 205 | }; 206 | let _postBody: any = {}; 207 | let _urlParams: any = {}; 208 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 209 | return result; 210 | } 211 | 212 | /** 213 | * Counts accessTokens of User. 214 | * 215 | * @param {any} id User id 216 | * 217 | * @param {object} where Criteria to match model instances 218 | * 219 | * @returns {object} An empty reference that will be 220 | * populated with the actual data once the response is returned 221 | * from the server. 222 | * 223 | * Data properties: 224 | * 225 | * - `count` – `{number}` - 226 | */ 227 | public countAccessTokens(id: any, where: any = {}, customHeaders?: Function): Observable { 228 | let _method: string = "GET"; 229 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 230 | "/Users/:id/accessTokens/count"; 231 | let _routeParams: any = { 232 | id: id 233 | }; 234 | let _postBody: any = {}; 235 | let _urlParams: any = {}; 236 | if (typeof where !== 'undefined' && where !== null) _urlParams.where = where; 237 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 238 | return result; 239 | } 240 | 241 | /** 242 | * Patch an existing model instance or insert a new one into the data source. 243 | * 244 | * @param {object} data Request data. 245 | * 246 | * - `data` – `{object}` - Model instance data 247 | * 248 | * @returns {object} An empty reference that will be 249 | * populated with the actual data once the response is returned 250 | * from the server. 251 | * 252 | * 253 | * (The remote method definition does not provide any description. 254 | * This usually means the response is a `User` object.) 255 | * 256 | */ 257 | public patchOrCreate(data: any = {}, customHeaders?: Function): Observable { 258 | let _method: string = "PATCH"; 259 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 260 | "/Users"; 261 | let _routeParams: any = {}; 262 | let _postBody: any = { 263 | data: data 264 | }; 265 | let _urlParams: any = {}; 266 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 267 | return result; 268 | } 269 | 270 | /** 271 | * Patch attributes for a model instance and persist it into the data source. 272 | * 273 | * @param {any} id User id 274 | * 275 | * @param {object} data Request data. 276 | * 277 | * - `data` – `{object}` - An object of model property name/value pairs 278 | * 279 | * @returns {object} An empty reference that will be 280 | * populated with the actual data once the response is returned 281 | * from the server. 282 | * 283 | * 284 | * (The remote method definition does not provide any description. 285 | * This usually means the response is a `User` object.) 286 | * 287 | */ 288 | public patchAttributes(id: any, data: any = {}, customHeaders?: Function): Observable { 289 | let _method: string = "PATCH"; 290 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 291 | "/Users/:id"; 292 | let _routeParams: any = { 293 | id: id 294 | }; 295 | let _postBody: any = { 296 | data: data 297 | }; 298 | let _urlParams: any = {}; 299 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 300 | return result; 301 | } 302 | 303 | /** 304 | * Login a user with username/email and password. 305 | * 306 | * @param {string} include Related objects to include in the response. See the description of return value for more details. 307 | * Default value: `user`. 308 | * 309 | * - `rememberMe` - `boolean` - Whether the authentication credentials 310 | * should be remembered in localStorage across app/browser restarts. 311 | * Default: `true`. 312 | * 313 | * @param {object} data Request data. 314 | * 315 | * This method expects a subset of model properties as request parameters. 316 | * 317 | * @returns {object} An empty reference that will be 318 | * populated with the actual data once the response is returned 319 | * from the server. 320 | * 321 | * The response body contains properties of the AccessToken created on login. 322 | * Depending on the value of `include` parameter, the body may contain additional properties: 323 | * 324 | * - `user` - `U+007BUserU+007D` - Data of the currently logged in user. (`include=user`) 325 | * 326 | * 327 | */ 328 | public login(credentials: any, include: any = 'user', rememberMe: boolean = true, customHeaders?: Function): Observable { 329 | let _method: string = "POST"; 330 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 331 | "/Users/login"; 332 | let _routeParams: any = {}; 333 | let _postBody: any = { 334 | credentials: credentials 335 | }; 336 | let _urlParams: any = {}; 337 | if (typeof include !== 'undefined' && include !== null) _urlParams.include = include; 338 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders) 339 | .map( 340 | (response: any) => { 341 | response.ttl = parseInt(response.ttl); 342 | response.rememberMe = rememberMe; 343 | this.auth.setToken(response); 344 | return response; 345 | } 346 | ); 347 | return result; 348 | 349 | } 350 | 351 | /** 352 | * Logout a user with access token. 353 | * 354 | * @param {object} data Request data. 355 | * 356 | * This method does not accept any data. Supply an empty object. 357 | * 358 | * @returns {object} An empty reference that will be 359 | * populated with the actual data once the response is returned 360 | * from the server. 361 | * 362 | * This method returns no data. 363 | */ 364 | public logout(customHeaders?: Function): Observable { 365 | let _method: string = "POST"; 366 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 367 | "/Users/logout"; 368 | let _routeParams: any = {}; 369 | let _postBody: any = {}; 370 | let _urlParams: any = {}; 371 | _urlParams.access_token = this.auth.getAccessTokenId(); 372 | this.auth.clear(); 373 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 374 | return result; 375 | } 376 | 377 | /** 378 | * Trigger user's identity verification with configured verifyOptions 379 | * 380 | * @param {any} id User id 381 | * 382 | * @param {object} data Request data. 383 | * 384 | * This method does not accept any data. Supply an empty object. 385 | * 386 | * @returns {object} An empty reference that will be 387 | * populated with the actual data once the response is returned 388 | * from the server. 389 | * 390 | * This method returns no data. 391 | */ 392 | public verify(id: any, customHeaders?: Function): Observable { 393 | let _method: string = "POST"; 394 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 395 | "/Users/:id/verify"; 396 | let _routeParams: any = { 397 | id: id 398 | }; 399 | let _postBody: any = {}; 400 | let _urlParams: any = {}; 401 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 402 | return result; 403 | } 404 | 405 | /** 406 | * Confirm a user registration with identity verification token. 407 | * 408 | * @param {string} uid 409 | * 410 | * @param {string} token 411 | * 412 | * @param {string} redirect 413 | * 414 | * @returns {object} An empty reference that will be 415 | * populated with the actual data once the response is returned 416 | * from the server. 417 | * 418 | * This method returns no data. 419 | */ 420 | public confirm(uid: any, token: any, redirect: any = {}, customHeaders?: Function): Observable { 421 | let _method: string = "GET"; 422 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 423 | "/Users/confirm"; 424 | let _routeParams: any = {}; 425 | let _postBody: any = {}; 426 | let _urlParams: any = {}; 427 | if (typeof uid !== 'undefined' && uid !== null) _urlParams.uid = uid; 428 | if (typeof token !== 'undefined' && token !== null) _urlParams.token = token; 429 | if (typeof redirect !== 'undefined' && redirect !== null) _urlParams.redirect = redirect; 430 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 431 | return result; 432 | } 433 | 434 | /** 435 | * Reset password for a user with email. 436 | * 437 | * @param {object} data Request data. 438 | * 439 | * This method expects a subset of model properties as request parameters. 440 | * 441 | * @returns {object} An empty reference that will be 442 | * populated with the actual data once the response is returned 443 | * from the server. 444 | * 445 | * This method returns no data. 446 | */ 447 | public resetPassword(options: any, customHeaders?: Function): Observable { 448 | let _method: string = "POST"; 449 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 450 | "/Users/reset"; 451 | let _routeParams: any = {}; 452 | let _postBody: any = { 453 | options: options 454 | }; 455 | let _urlParams: any = {}; 456 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 457 | return result; 458 | } 459 | 460 | /** 461 | * Change a user's password. 462 | * 463 | * @param {object} data Request data. 464 | * 465 | * - `oldPassword` – `{string}` - 466 | * 467 | * - `newPassword` – `{string}` - 468 | * 469 | * @returns {object} An empty reference that will be 470 | * populated with the actual data once the response is returned 471 | * from the server. 472 | * 473 | * This method returns no data. 474 | */ 475 | public changePassword(oldPassword: any, newPassword: any, customHeaders?: Function): Observable { 476 | let _method: string = "POST"; 477 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 478 | "/Users/change-password"; 479 | let _routeParams: any = {}; 480 | let _postBody: any = { 481 | data: { 482 | oldPassword: oldPassword, 483 | newPassword: newPassword 484 | } 485 | }; 486 | let _urlParams: any = {}; 487 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 488 | return result; 489 | } 490 | 491 | /** 492 | * Reset user's password via a password-reset token. 493 | * 494 | * @param {object} data Request data. 495 | * 496 | * - `newPassword` – `{string}` - 497 | * 498 | * @returns {object} An empty reference that will be 499 | * populated with the actual data once the response is returned 500 | * from the server. 501 | * 502 | * This method returns no data. 503 | */ 504 | public setPassword(newPassword: any, customHeaders?: Function): Observable { 505 | let _method: string = "POST"; 506 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 507 | "/Users/reset-password"; 508 | let _routeParams: any = {}; 509 | let _postBody: any = { 510 | data: { 511 | newPassword: newPassword 512 | } 513 | }; 514 | let _urlParams: any = {}; 515 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 516 | return result; 517 | } 518 | 519 | /** 520 | * Creates a new instance in accessTokens of this model. 521 | * 522 | * @param {any} id User id 523 | * 524 | * @param {object} data Request data. 525 | * 526 | * This method expects a subset of model properties as request parameters. 527 | * 528 | * @returns {object[]} An empty reference that will be 529 | * populated with the actual data once the response is returned 530 | * from the server. 531 | * 532 | * 533 | * (The remote method definition does not provide any description. 534 | * This usually means the response is a `User` object.) 535 | * 536 | */ 537 | public createManyAccessTokens(id: any, data: any[] = [], customHeaders?: Function): Observable { 538 | let _method: string = "POST"; 539 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 540 | "/Users/:id/accessTokens"; 541 | let _routeParams: any = { 542 | id: id 543 | }; 544 | let _postBody: any = { 545 | data: data 546 | }; 547 | let _urlParams: any = {}; 548 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody, null, customHeaders); 549 | return result; 550 | } 551 | /** 552 | * @ngdoc method 553 | * @name sdk.User#getCurrent 554 | * @methodOf sdk.User 555 | * 556 | * @description 557 | * 558 | * Get data of the currently logged user. Fail with HTTP result 401 559 | * when there is no user logged in. 560 | * 561 | * @returns object An empty reference that will be 562 | * populated with the actual data once the response is returned 563 | * from the server. 564 | */ 565 | public getCurrent(filter: LoopBackFilter = {}): Observable { 566 | let _method: string = "GET"; 567 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + "/Users" + "/:id"; 568 | let id: any = this.auth.getCurrentUserId(); 569 | if (id == null) 570 | id = '__anonymous__'; 571 | let _routeParams: any = { id: id }; 572 | let _urlParams: any = {}; 573 | let _postBody: any = {}; 574 | if (filter) _urlParams.filter = filter; 575 | return this.request(_method, _url, _routeParams, _urlParams, _postBody); 576 | } 577 | /** 578 | * Get data of the currently logged user that was returned by the last 579 | * call to {@link sdk.User#login} or 580 | * {@link sdk.User#getCurrent}. Return null when there 581 | * is no user logged in or the data of the current user were not fetched 582 | * yet. 583 | * 584 | * @returns object An Account instance. 585 | */ 586 | public getCachedCurrent() { 587 | return this.auth.getCurrentUserData(); 588 | } 589 | /** 590 | * Get data of the currently logged access tokern that was returned by the last 591 | * call to {@link sdk.User#login} 592 | * 593 | * @returns object An AccessToken instance. 594 | */ 595 | public getCurrentToken(): AccessToken { 596 | return this.auth.getToken(); 597 | } 598 | /** 599 | * @name sdk.User#isAuthenticated 600 | * 601 | * @returns {boolean} True if the current user is authenticated (logged in). 602 | */ 603 | public isAuthenticated() { 604 | return !(this.getCurrentId() === '' || this.getCurrentId() == null || this.getCurrentId() == 'null'); 605 | } 606 | 607 | /** 608 | * @name sdk.User#getCurrentId 609 | * 610 | * @returns object Id of the currently logged-in user or null. 611 | */ 612 | public getCurrentId() { 613 | return this.auth.getCurrentUserId(); 614 | } 615 | 616 | /** 617 | * The name of the model represented by this $resource, 618 | * i.e. `User`. 619 | */ 620 | public getModelName() { 621 | return "User"; 622 | } 623 | } 624 | -------------------------------------------------------------------------------- /sdk/services/custom/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | export * from './User'; 3 | export * from './Question'; 4 | export * from './Answer'; 5 | export * from './SDKModels'; 6 | export * from './logger.service'; 7 | -------------------------------------------------------------------------------- /sdk/services/custom/logger.service.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { Injectable } from '@angular/core'; 3 | import { LoopBackConfig } from '../../lb.config'; 4 | /** 5 | * @author Jonathan Casarrubias 6 | * @module LoggerService 7 | * @license MIT 8 | * @description 9 | * Console Log wrapper that can be disabled in production mode 10 | **/ 11 | @Injectable() 12 | export class LoggerService { 13 | 14 | log(...args: any[]) { 15 | if (LoopBackConfig.debuggable()) 16 | console.log.apply(console, args); 17 | } 18 | 19 | info(...args: any[]) { 20 | if (LoopBackConfig.debuggable()) 21 | console.info.apply(console, args); 22 | } 23 | 24 | error(...args: any[]) { 25 | if (LoopBackConfig.debuggable()) 26 | console.error.apply(console, args); 27 | } 28 | 29 | count(arg: string) { 30 | if (LoopBackConfig.debuggable()) 31 | console.count(arg); 32 | } 33 | 34 | group(arg: string) { 35 | if (LoopBackConfig.debuggable()) 36 | console.count(arg); 37 | } 38 | 39 | groupEnd() { 40 | if (LoopBackConfig.debuggable()) 41 | console.groupEnd(); 42 | } 43 | 44 | profile(arg: string) { 45 | if (LoopBackConfig.debuggable()) 46 | console.count(arg); 47 | } 48 | 49 | profileEnd() { 50 | if (LoopBackConfig.debuggable()) 51 | console.profileEnd(); 52 | } 53 | 54 | time(arg: string) { 55 | if (LoopBackConfig.debuggable()) 56 | console.time(arg); 57 | } 58 | 59 | timeEnd(arg: string) { 60 | if (LoopBackConfig.debuggable()) 61 | console.timeEnd(arg); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /sdk/services/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | export * from './core/index'; 3 | export * from './custom/index'; 4 | -------------------------------------------------------------------------------- /sdk/storage/cookie.browser.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { Injectable } from '@angular/core'; 3 | export interface CookieInterface { [key: string]: any } 4 | /** 5 | * @author Jonathan Casarrubias 6 | * @module CookieBrowser 7 | * @license MIT 8 | * @description 9 | * This module handle cookies, it will be provided using DI Swapping according the 10 | * SDK Socket Driver Available currently supporting Angular 2 for web and NativeScript 2. 11 | **/ 12 | @Injectable() 13 | export class CookieBrowser { 14 | /** 15 | * @type {CookieInterface} 16 | **/ 17 | private cookies: CookieInterface = {}; 18 | /** 19 | * @method get 20 | * @param {string} key Cookie key name 21 | * @return {any} 22 | * @description 23 | * The getter will return any type of data persisted in cookies. 24 | **/ 25 | get(key: string): any { 26 | if (!this.cookies[key]) { 27 | let cookie = window.document 28 | .cookie.split('; ') 29 | .filter((item: any) => item.split('=')[0] === key).pop(); 30 | if (!cookie) { 31 | return null; 32 | } 33 | 34 | this.cookies[key] = this.parse(cookie.split('=').slice(1).join('=')); 35 | } 36 | 37 | return this.cookies[key]; 38 | } 39 | /** 40 | * @method set 41 | * @param {string} key Cookie key name 42 | * @param {any} value Any value 43 | * @param {Date=} expires The date of expiration (Optional) 44 | * @return {void} 45 | * @description 46 | * The setter will return any type of data persisted in cookies. 47 | **/ 48 | set(key: string, value: any, expires?: Date): void { 49 | this.cookies[key] = value; 50 | let cookie = `${key}=${value}; path=/${expires ? `; expires=${ expires.toUTCString() }` : ''}`; 51 | window.document.cookie = cookie; 52 | } 53 | /** 54 | * @method remove 55 | * @param {string} key Cookie key name 56 | * @return {void} 57 | * @description 58 | * This method will remove a cookie from the client. 59 | **/ 60 | remove(key: string) { 61 | document.cookie = key + '=; path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; 62 | delete this.cookies[key]; 63 | } 64 | /** 65 | * @method parse 66 | * @param {any} value Input data expected to be JSON 67 | * @return {void} 68 | * @description 69 | * This method will parse the string as JSON if possible, otherwise will 70 | * return the value itself. 71 | **/ 72 | private parse(value: any) { 73 | try { 74 | return JSON.parse(value); 75 | } catch (e) { 76 | return value; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /sdk/storage/storage.browser.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { Injectable } from '@angular/core'; 3 | /** 4 | * @author Jonathan Casarrubias 5 | * @module StorageBrowser 6 | * @license MIT 7 | * @description 8 | * This module handle localStorage, it will be provided using DI Swapping according the 9 | * SDK Socket Driver Available currently supporting Angular 2 for web and NativeScript 2. 10 | **/ 11 | @Injectable() 12 | export class StorageBrowser { 13 | /** 14 | * @method get 15 | * @param {string} key Storage key name 16 | * @return {any} 17 | * @description 18 | * The getter will return any type of data persisted in localStorage. 19 | **/ 20 | get(key: string): any { 21 | let data: string = localStorage.getItem(key); 22 | return this.parse(data); 23 | } 24 | /** 25 | * @method set 26 | * @param {string} key Storage key name 27 | * @param {any} value Any value 28 | * @return {void} 29 | * @description 30 | * The setter will return any type of data persisted in localStorage. 31 | **/ 32 | set(key: string, value: any, expires?: Date): void { 33 | localStorage.setItem( 34 | key, 35 | typeof value === 'object' ? JSON.stringify(value) : value 36 | ); 37 | } 38 | /** 39 | * @method remove 40 | * @param {string} key Storage key name 41 | * @return {void} 42 | * @description 43 | * This method will remove a localStorage item from the client. 44 | **/ 45 | remove(key: string): void { 46 | if (localStorage[key]) { 47 | localStorage.removeItem(key); 48 | } else { 49 | console.log('Trying to remove unexisting key: ', key); 50 | } 51 | } 52 | /** 53 | * @method parse 54 | * @param {any} value Input data expected to be JSON 55 | * @return {void} 56 | * @description 57 | * This method will parse the string as JSON if possible, otherwise will 58 | * return the value itself. 59 | **/ 60 | private parse(value: any) { 61 | try { 62 | return JSON.parse(value); 63 | } catch (e) { 64 | return value; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /sdk/storage/storage.swaps.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /** 3 | * @module Storage 4 | * @author Jonathan Casarrubias 5 | * @license MIT 6 | * @description 7 | * The InternalStorage class is used for dependency injection swapping. 8 | * It will be provided using factory method from different sources. 9 | **/ 10 | export class BaseStorage { 11 | /** 12 | * @method get 13 | * @param {string} key Storage key name 14 | * @return {any} 15 | * @description 16 | * The getter will return any type of data persisted in storage. 17 | **/ 18 | get(key: string): any {} 19 | /** 20 | * @method set 21 | * @param {string} key Storage key name 22 | * @param {any} value Any value 23 | * @return {void} 24 | * @description 25 | * The setter will return any type of data persisted in localStorage. 26 | **/ 27 | set(key: string, value: any, expires?: Date): void {} 28 | /** 29 | * @method remove 30 | * @param {string} key Storage key name 31 | * @return {void} 32 | * @description 33 | * This method will remove a localStorage item from the client. 34 | **/ 35 | remove(key: string): void {} 36 | } 37 | /** 38 | * @module InternalStorage 39 | * @author Jonathan Casarrubias 40 | * @license MIT 41 | * @description 42 | * The InternalStorage class is used for dependency injection swapping. 43 | * It will be provided using factory method from different sources. 44 | * This is mainly required because Angular Universal integration. 45 | * It does inject a CookieStorage instead of LocalStorage. 46 | **/ 47 | export class InternalStorage extends BaseStorage {} 48 | /** 49 | * @module SDKStorage 50 | * @author Jonathan Casarrubias 51 | * @license MIT 52 | * @description 53 | * The SDKStorage class is used for dependency injection swapping. 54 | * It will be provided using factory method according the right environment. 55 | * This is created for public usage, to allow persisting custom data 56 | * Into the local storage API. 57 | **/ 58 | export class SDKStorage extends BaseStorage {} 59 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { CategoriesComponent } from './categories/categories.component'; 5 | import { CategoriesResolver } from './categories/categories.resolver'; 6 | 7 | import { CategoryQuestionsComponent } from './category-questions/category-questions.component'; 8 | import { CategoryQuestionsResolver } from './category-questions/category-questions.resolver'; 9 | 10 | import { QuestionAnswersResolver } from './question-answers/question-answers.resolver'; 11 | import { QuestionAnswersComponent } from './question-answers/question-answers.component'; 12 | 13 | 14 | const routes: Routes = [ 15 | { 16 | path: '', 17 | component: CategoriesComponent, 18 | resolve: { 19 | data: CategoriesResolver 20 | } 21 | }, 22 | { 23 | path: 'questions/about/:categorySlug', 24 | component: CategoryQuestionsComponent, 25 | resolve: { 26 | data: CategoryQuestionsResolver 27 | } 28 | }, 29 | { 30 | path: 'question/:questionSlug', 31 | component: QuestionAnswersComponent, 32 | resolve: { 33 | data: QuestionAnswersResolver 34 | } 35 | } 36 | ]; 37 | 38 | @NgModule({ 39 | imports: [RouterModule.forRoot(routes)], 40 | exports: [RouterModule] 41 | }) 42 | export class AppRoutingModule { } 43 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 |
7 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngularTemplates/learn-angular-from-scratch-step-by-step/3e0406309c3d0954d2abdcc5d016fffcad3e9060/src/app/app.component.scss -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | })); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.debugElement.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'learn-angular-from-scratch'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.debugElement.componentInstance; 26 | expect(app.title).toEqual('learn-angular-from-scratch'); 27 | }); 28 | 29 | it('should render title in a h1 tag', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.debugElement.nativeElement; 33 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to learn-angular-from-scratch!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { MatToolbarModule } from '@angular/material/toolbar'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.scss'] 8 | }) 9 | export class AppComponent { 10 | title = 'learn-angular-from-scratch'; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { AppRoutingModule } from './app-routing.module'; 4 | 5 | import { CategoriesComponent } from './categories/categories.component'; 6 | import { CategoriesResolver } from './categories/categories.resolver'; 7 | import { CategoriesService } from './services/categories.service'; 8 | 9 | import { CategoryQuestionsResolver } from './category-questions/category-questions.resolver'; 10 | import { CategoryQuestionsComponent } from './category-questions/category-questions.component'; 11 | import { NewQuestionModalComponent } from './category-questions/new-question/new-question-modal.component'; 12 | import { DeleteQuestionModalComponent } from './category-questions/delete-question/delete-question-modal.component'; 13 | import { QuestionsService } from './services/questions.service'; 14 | 15 | import { NewAnswerModalComponent } from './question-answers/new-answer/new-answer-modal.component'; 16 | import { UpdateAnswerModalComponent } from './question-answers/update-answer/update-answer-modal.component'; 17 | import { DeleteAnswerModalComponent } from './question-answers/delete-answer/delete-answer-modal.component'; 18 | import { QuestionAnswersComponent } from './question-answers/question-answers.component'; 19 | import { QuestionAnswersResolver } from './question-answers/question-answers.resolver'; 20 | import { AnswersService } from './services/answers.service'; 21 | 22 | import { AppComponent } from './app.component'; 23 | import { SharedModule } from './shared/shared.module'; 24 | 25 | @NgModule({ 26 | declarations: [ 27 | AppComponent, 28 | CategoriesComponent, 29 | CategoryQuestionsComponent, 30 | NewQuestionModalComponent, 31 | NewAnswerModalComponent, 32 | UpdateAnswerModalComponent, 33 | QuestionAnswersComponent, 34 | DeleteQuestionModalComponent, 35 | DeleteAnswerModalComponent 36 | ], 37 | imports: [ 38 | BrowserModule, 39 | AppRoutingModule, 40 | SharedModule 41 | ], 42 | entryComponents: [ 43 | DeleteQuestionModalComponent, 44 | DeleteAnswerModalComponent, 45 | NewQuestionModalComponent, 46 | NewAnswerModalComponent, 47 | UpdateAnswerModalComponent 48 | ], 49 | providers: [ 50 | CategoriesService, 51 | QuestionsService, 52 | AnswersService, 53 | CategoryQuestionsResolver, 54 | CategoriesResolver, 55 | QuestionAnswersResolver 56 | ], 57 | bootstrap: [AppComponent] 58 | }) 59 | export class AppModule { } 60 | -------------------------------------------------------------------------------- /src/app/categories/categories.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Learn all about Angular

4 | 5 | 6 | 7 |

8 | {{category.title}} 9 |

10 |

{{category.description}}

11 |
12 | 13 | {{tag.name}} 14 | 15 |
16 |
17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /src/app/categories/categories.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { CategoryModel } from "./category.model"; 4 | 5 | @Component({ 6 | selector: 'categories', 7 | styleUrls: ['./categories.scss'], 8 | templateUrl: './categories.component.html' 9 | }) 10 | 11 | export class CategoriesComponent implements OnInit{ 12 | 13 | categories: CategoryModel[]; 14 | 15 | constructor(private route: ActivatedRoute){} 16 | 17 | ngOnInit(): void { 18 | this.route.data.subscribe(routeData => { 19 | let data = routeData['data']; 20 | if (data) { 21 | this.categories = data.categories; 22 | } 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/app/categories/categories.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Resolve } from "@angular/router"; 3 | import { CategoriesService } from "../services/categories.service"; 4 | 5 | @Injectable() 6 | export class CategoriesResolver implements Resolve { 7 | 8 | constructor( 9 | private categoriesService: CategoriesService 10 | ) { } 11 | 12 | resolve() { 13 | return new Promise((resolve, reject) => { 14 | 15 | let breadcrumbs = [ 16 | { url: '/', label: 'Categories' } 17 | ]; 18 | 19 | //get categories from local json file 20 | this.categoriesService.getCategories() 21 | .then( 22 | categories => { 23 | return resolve({ 24 | categories: categories, 25 | breadcrumbs: breadcrumbs 26 | }); 27 | }, 28 | err => { 29 | return resolve(null); 30 | } 31 | ) 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/categories/categories.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/_variables'; 2 | 3 | .categories { 4 | 5 | .list { 6 | margin-top: 30px; 7 | } 8 | 9 | .list-item { 10 | height: 120px; 11 | 12 | .avatar { 13 | width: 100px; 14 | height: 100px; 15 | } 16 | 17 | &:not(:first-child) { 18 | border-top: 1px solid $grey4; 19 | } 20 | .list-line { 21 | margin-bottom: 8px; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/categories/category.model.ts: -------------------------------------------------------------------------------- 1 | export class CategoryModel { 2 | slug: string; 3 | title: string; 4 | image: string; 5 | description: string; 6 | tags: Array; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/category-questions/category-questions.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Learn about: {{categoryTitle}}

4 | 5 | 6 | 7 |

8 | {{question.question}} 9 |

10 |

11 | 12 | {{question.positiveVotes}} 13 | 14 | {{question.negativeVotes}} 15 | 16 | 17 | {{question.answers? question.answers.length: 0}} Answers 18 |

19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /src/app/category-questions/category-questions.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { QuestionsService } from '../services/questions.service'; 4 | import { AnswersService } from '../services/answers.service'; 5 | import { Question } from '../../../sdk/models/Question'; 6 | import { MatDialog } from '@angular/material'; 7 | import { DeleteQuestionModalComponent } from './delete-question/delete-question-modal.component'; 8 | import { NewQuestionModalComponent } from './new-question/new-question-modal.component'; 9 | 10 | @Component({ 11 | selector: 'category-questions', 12 | styleUrls: ['./category-questions.scss'], 13 | templateUrl: './category-questions.component.html' 14 | }) 15 | 16 | export class CategoryQuestionsComponent implements OnInit{ 17 | 18 | questions: Array; 19 | categoryTitle: string; 20 | categorySlug: any; 21 | 22 | constructor( 23 | private route: ActivatedRoute, 24 | public questionsService: QuestionsService, 25 | public answersService: AnswersService, 26 | public dialog: MatDialog 27 | ){} 28 | 29 | ngOnInit(): void { 30 | this.route.data.subscribe(routeData => { 31 | let data = routeData['data']; 32 | if (data) { 33 | this.questions = data.questions; 34 | this.categoryTitle = data.category_title; 35 | this.categorySlug = data.category_slug; 36 | } 37 | }) 38 | } 39 | 40 | getQuestions(){ 41 | this.questionsService.getQuestionsByCategory(this.categorySlug) 42 | .then(questions => this.questions = questions); 43 | } 44 | 45 | openNewQuestionModal(categorySlug){ 46 | let dialogRef = this.dialog.open(NewQuestionModalComponent, { 47 | data: { categorySlug: categorySlug } 48 | }); 49 | 50 | dialogRef.afterClosed().subscribe(question => { 51 | if(question){ 52 | this.addQuestionToList(question); 53 | } 54 | }) 55 | } 56 | 57 | delete(questionId){ 58 | let dialogRef = this.dialog.open(DeleteQuestionModalComponent, { 59 | data: { questionId: questionId } 60 | }); 61 | 62 | dialogRef.afterClosed().subscribe(confirm => { 63 | if(confirm){ 64 | // refresh the questions list 65 | var index = this.questions.findIndex((question) => question.id === questionId); 66 | this.questions.splice(index, 1); 67 | 68 | // TODO: evaluar cambiar esto por un operation method en loopback. 69 | this.answersService.getAnswers(questionId) 70 | .then(answers => { 71 | for(let answer of answers){ 72 | this.answersService.deleteAnswer(answer.id); 73 | } 74 | }) 75 | } 76 | }); 77 | } 78 | 79 | addQuestionToList(question){ 80 | this.questions.push(question); 81 | } 82 | 83 | addPositiveVote(question){ 84 | question.positiveVotes += 1; 85 | this.questionsService.updateQuestion(question); 86 | } 87 | 88 | addNegativeVote(question){ 89 | question.negativeVotes += 1; 90 | this.questionsService.updateQuestion(question); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/app/category-questions/category-questions.resolver.ts: -------------------------------------------------------------------------------- 1 | import { forkJoin as observableForkJoin } from 'rxjs'; 2 | import { Injectable } from '@angular/core'; 3 | import { Resolve, ActivatedRouteSnapshot } from "@angular/router"; 4 | import { QuestionsService } from '../services/questions.service'; 5 | import { CategoriesService } from '../services/categories.service'; 6 | 7 | @Injectable() 8 | export class CategoryQuestionsResolver implements Resolve { 9 | 10 | constructor( 11 | private questionsService: QuestionsService, 12 | private categoriesService: CategoriesService 13 | ) { } 14 | 15 | resolve(route: ActivatedRouteSnapshot) { 16 | 17 | return new Promise((resolve, reject) => { 18 | //get current category slug form url 19 | let category_slug = route.paramMap.get('categorySlug'); 20 | 21 | 22 | observableForkJoin( 23 | this.categoriesService.getCategoryBySlug(category_slug), 24 | this.questionsService.getQuestionsByCategory(category_slug) 25 | ).subscribe( 26 | data => { 27 | let breadcrumbs = [ 28 | { url: '/', label: 'Categories' }, 29 | { url: 'questions/about/' + category_slug, label: data[0].title } 30 | ]; 31 | 32 | return resolve({ 33 | questions: data[1], 34 | category_title: data[0].title, 35 | category_slug: category_slug, 36 | breadcrumbs: breadcrumbs 37 | }); 38 | }, 39 | err => { 40 | console.log(err) 41 | return resolve(null) 42 | }); 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/app/category-questions/category-questions.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/_variables'; 2 | 3 | .category-questions { 4 | .list { 5 | margin-top: 30px; 6 | } 7 | .list-item { 8 | margin-bottom: 10px; 9 | &:not(:first-child) { 10 | border-top: 1px solid $grey4; 11 | } 12 | 13 | .icons-line { 14 | margin-top: 10px; 15 | .list-single-icon { 16 | color: $grey5; 17 | font-size: 20px; 18 | } 19 | .list-single-icon-number { 20 | color: $grey5; 21 | vertical-align: top; 22 | 23 | &:not(:last-child) { 24 | margin-right: 15px; 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/category-questions/delete-question/delete-question-modal.component.html: -------------------------------------------------------------------------------- 1 |
2 |

DELETE QUESTION

3 | Are you sure to delete this question? 4 |
5 |
6 | 7 | 8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /src/app/category-questions/delete-question/delete-question-modal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; 3 | import { QuestionsService } from '../../services/questions.service'; 4 | 5 | @Component({ 6 | selector: 'delete-question-modal', 7 | templateUrl: 'delete-question-modal.component.html', 8 | styleUrls: ['../../styles/modals.scss'] 9 | }) 10 | 11 | export class DeleteQuestionModalComponent { 12 | constructor( 13 | public thisDialogRef: MatDialogRef, 14 | @Inject(MAT_DIALOG_DATA) public modalData: any, 15 | public questionsService: QuestionsService 16 | ){} 17 | 18 | onCloseConfirm() { 19 | this.questionsService.deleteQuestion(this.modalData.questionId) 20 | .then(res => { 21 | this.thisDialogRef.close(true); 22 | }) 23 | } 24 | 25 | onCloseCancel() { 26 | this.thisDialogRef.close(false); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/app/category-questions/new-question/new-question-modal.component.html: -------------------------------------------------------------------------------- 1 |
2 |

NEW QUESTION

3 |
4 |
5 | 6 |
7 | 8 | 9 |
10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /src/app/category-questions/new-question/new-question-modal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Inject} from '@angular/core'; 2 | import { Router, ActivatedRoute } from '@angular/router'; 3 | import { Validators, FormGroup, FormControl} from '@angular/forms'; 4 | import { QuestionsService } from '../../services/questions.service'; 5 | import { SlugifyPipe } from '../../shared/slugify.pipe'; 6 | import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; 7 | 8 | @Component({ 9 | selector: 'new-question-modal', 10 | templateUrl: './new-question-modal.component.html', 11 | exportAs: 'newQuestionModal', 12 | styleUrls: ['../../styles/modals.scss'] 13 | }) 14 | 15 | export class NewQuestionModalComponent implements OnInit{ 16 | 17 | questionForm: FormGroup; 18 | 19 | constructor( 20 | public questionsService: QuestionsService, 21 | private slugifyPipe: SlugifyPipe, 22 | public thisDialogRef: MatDialogRef, 23 | @Inject(MAT_DIALOG_DATA) public modalData: any, 24 | ){} 25 | 26 | ngOnInit(): void { 27 | this.questionForm = new FormGroup({ 28 | question: new FormControl('', Validators.required) 29 | }) 30 | } 31 | 32 | onCloseCancel() { 33 | this.thisDialogRef.close(); 34 | } 35 | 36 | onSubmit(values){ 37 | let data: any = {}; 38 | data.question = values.question; 39 | data.questionSlug = this.slugifyPipe.transform(values.question); 40 | data.categorySlug = this.modalData.categorySlug; 41 | 42 | //create new question 43 | this.questionsService.createQuestion(data) 44 | .then(question => { 45 | this.thisDialogRef.close(question); 46 | this.questionForm.reset(); 47 | }); 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/app/question-answers/delete-answer/delete-answer-modal.component.html: -------------------------------------------------------------------------------- 1 |
2 |

DELETE ANSWER

3 | Are you sure to delete this answer? 4 |
5 |
6 | 7 | 8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /src/app/question-answers/delete-answer/delete-answer-modal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; 3 | import { AnswersService } from '../../services/answers.service'; 4 | 5 | @Component({ 6 | selector: 'delete-answer-modal', 7 | templateUrl: 'delete-answer-modal.component.html', 8 | styleUrls: ['../../styles/modals.scss'] 9 | }) 10 | export class DeleteAnswerModalComponent { 11 | 12 | constructor( 13 | public thisDialogRef: MatDialogRef, 14 | @Inject(MAT_DIALOG_DATA) public modalData: any, 15 | public answersService: AnswersService 16 | ){} 17 | 18 | onCloseConfirm() { 19 | this.answersService.deleteAnswer(this.modalData.answerId) 20 | .then( 21 | res => { 22 | this.thisDialogRef.close(true); 23 | }) 24 | } 25 | 26 | onCloseCancel() { 27 | this.thisDialogRef.close(false); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/app/question-answers/new-answer/new-answer-modal.component.html: -------------------------------------------------------------------------------- 1 |
2 |

NEW ANSWER

3 | 4 |
5 |
6 | 7 |
8 | 9 | 10 |
11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /src/app/question-answers/new-answer/new-answer-modal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Inject } from '@angular/core'; 2 | import { Validators, FormGroup, FormControl} from '@angular/forms'; 3 | import { AnswersService } from '../../services/answers.service'; 4 | import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; 5 | 6 | @Component({ 7 | selector: 'new-answer', 8 | templateUrl: './new-answer-modal.component.html', 9 | exportAs: 'newAnswerModal', 10 | styleUrls: ['../../styles/modals.scss'] 11 | }) 12 | 13 | export class NewAnswerModalComponent implements OnInit{ 14 | 15 | answerForm: FormGroup; 16 | 17 | constructor( 18 | public answersService: AnswersService, 19 | public thisDialogRef: MatDialogRef, 20 | @Inject(MAT_DIALOG_DATA) public modalData: any, 21 | ){} 22 | 23 | ngOnInit(): void { 24 | this.answerForm = new FormGroup({ 25 | answer: new FormControl('', Validators.required) 26 | }) 27 | } 28 | 29 | onCloseCancel() { 30 | this.thisDialogRef.close(); 31 | } 32 | 33 | onSubmit(values){ 34 | let data : any = {}; 35 | data.answer = values.answer; 36 | data.questionId = this.modalData.questionId; 37 | this.answersService.createAnswer(data) 38 | .then(answer => { 39 | this.thisDialogRef.close(answer); 40 | this.answerForm.reset(); 41 | }) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/app/question-answers/question-answers.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{question.question}}

4 | 5 | 6 | 7 | {{answer.answer}} 8 |

9 | 10 | {{answer.positiveVotes}} 11 | 12 | {{answer.negativeVotes}} 13 | 14 | 15 | 16 | 17 |

18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /src/app/question-answers/question-answers.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { AnswersService } from '../services/answers.service' 4 | import { Question } from '../../../sdk/models/Question'; 5 | import { MatDialog } from '@angular/material'; 6 | import { DeleteAnswerModalComponent } from './delete-answer/delete-answer-modal.component'; 7 | import { NewAnswerModalComponent } from './new-answer/new-answer-modal.component'; 8 | import { UpdateAnswerModalComponent } from './update-answer/update-answer-modal.component'; 9 | 10 | @Component({ 11 | selector: 'answer', 12 | styleUrls: ['./question-answers.scss'], 13 | templateUrl: './question-answers.component.html' 14 | }) 15 | 16 | export class QuestionAnswersComponent { 17 | 18 | question: Question; 19 | 20 | constructor( 21 | private answersService: AnswersService, 22 | private route: ActivatedRoute, 23 | public dialog: MatDialog 24 | ){} 25 | 26 | ngOnInit(): void { 27 | this.route.data.subscribe(routeData => { 28 | let data = routeData['data']; 29 | if (data) { 30 | this.question = data.question; 31 | } 32 | }) 33 | } 34 | 35 | openNewAnswerModal(questionId){ 36 | let dialogRef = this.dialog.open(NewAnswerModalComponent, { 37 | data: { questionId: questionId } 38 | }); 39 | 40 | dialogRef.afterClosed().subscribe(answer => { 41 | if(answer){ 42 | this.addAnswerToList(answer); 43 | } 44 | }) 45 | } 46 | 47 | openUpdateAnswerModal(answer){ 48 | let dialogRef = this.dialog.open(UpdateAnswerModalComponent, { 49 | data: { answer: answer } 50 | }); 51 | 52 | } 53 | 54 | delete(answerId){ 55 | let dialogRef = this.dialog.open(DeleteAnswerModalComponent, { 56 | data: { answerId: answerId } 57 | }); 58 | 59 | dialogRef.afterClosed().subscribe(confirm => { 60 | if(confirm){ 61 | var index = this.question.answers.findIndex((answer) => answer.id === answerId); 62 | this.question.answers.splice(index, 1); 63 | } 64 | }) 65 | 66 | } 67 | 68 | addPositiveVote(answer){ 69 | answer.positiveVotes += 1; 70 | this.answersService.updateAnswer(answer); 71 | } 72 | 73 | addNegativeVote(answer){ 74 | answer.negativeVotes += 1; 75 | this.answersService.updateAnswer(answer); 76 | } 77 | 78 | addAnswerToList(answer){ 79 | this.question.answers.push(answer); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/app/question-answers/question-answers.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Resolve, ActivatedRouteSnapshot } from "@angular/router"; 3 | import { QuestionsService } from '../services/questions.service'; 4 | 5 | @Injectable() 6 | export class QuestionAnswersResolver implements Resolve { 7 | 8 | constructor( 9 | private questionsService: QuestionsService 10 | ) { } 11 | 12 | resolve(route: ActivatedRouteSnapshot) { 13 | let questionSlug = route.paramMap.get('questionSlug'); 14 | 15 | return new Promise((resolve, reject) => { 16 | this.questionsService.getQuestionBySlug(questionSlug) 17 | .then(question => { 18 | let breadcrumbs = [ 19 | { url: '/', label: 'Categories' }, 20 | { url: 'questions/about/' + question.categorySlug, label: question.categorySlug }, 21 | { url: 'question/' + questionSlug, label: question.question } 22 | ]; 23 | 24 | return resolve({ 25 | question: question, 26 | breadcrumbs: breadcrumbs 27 | }); 28 | }, 29 | err => { 30 | console.log(err); 31 | return resolve(null); 32 | }) 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/question-answers/question-answers.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/_variables'; 2 | 3 | .question-answers { 4 | 5 | .list-item { 6 | margin: 20px 0px; 7 | 8 | &:not(:first-child) { 9 | border-top: 1px solid $grey4; 10 | } 11 | 12 | .answer { 13 | white-space: normal; 14 | } 15 | 16 | .icons-line { 17 | margin-top: 10px; 18 | .list-single-icon { 19 | color: $grey5; 20 | font-size: 20px; 21 | } 22 | .list-single-icon-number { 23 | color: $grey5; 24 | vertical-align: top; 25 | 26 | &:not(:last-child) { 27 | margin-right: 15px; 28 | } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/app/question-answers/update-answer/update-answer-modal.component.html: -------------------------------------------------------------------------------- 1 |
2 |

UPDATE ANSWER

3 |
4 |
5 | 6 |
7 | 8 | 9 |
10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /src/app/question-answers/update-answer/update-answer-modal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Inject } from '@angular/core'; 2 | import { Validators, FormGroup, FormControl} from '@angular/forms'; 3 | import { AnswersService } from '../../services/answers.service'; 4 | import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; 5 | 6 | @Component({ 7 | selector: 'update-answer-modal', 8 | templateUrl: './update-answer-modal.component.html', 9 | exportAs: 'updateAnswerModal', 10 | styleUrls: ['../../styles/modals.scss'] 11 | }) 12 | 13 | export class UpdateAnswerModalComponent implements OnInit{ 14 | 15 | answerForm: FormGroup; 16 | 17 | constructor( 18 | public answersService: AnswersService, 19 | public thisDialogRef: MatDialogRef, 20 | @Inject(MAT_DIALOG_DATA) public modalData: any 21 | ){ 22 | } 23 | 24 | ngOnInit(): void { 25 | console.log(this.modalData.answer); 26 | this.answerForm = new FormGroup({ 27 | answer: new FormControl(this.modalData.answer.answer, Validators.required) 28 | }) 29 | } 30 | 31 | onCloseCancel() { 32 | this.thisDialogRef.close(); 33 | } 34 | 35 | onSubmit(values){ 36 | let newAnswer = this.modalData.answer; 37 | newAnswer.answer = values.answer; 38 | this.answersService.updateAnswer(newAnswer) 39 | .then(answer => { 40 | this.thisDialogRef.close(answer); 41 | this.answerForm.reset(); 42 | }) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/app/services/answers.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { AnswerApi, Answer } from '../../../sdk'; 3 | 4 | 5 | @Injectable() 6 | export class AnswersService { 7 | constructor( 8 | private answerApi: AnswerApi 9 | ){} 10 | 11 | getAnswers(questionId){ 12 | let query = { 13 | questionId: questionId 14 | } 15 | return this.answerApi.find({where: query}) 16 | .toPromise() 17 | } 18 | 19 | getAnswer(anserId){ 20 | let query = { 21 | id: anserId 22 | } 23 | return this.answerApi.find({where: query}) 24 | .toPromise() 25 | } 26 | 27 | deleteAnswer(answerId){ 28 | return this.answerApi.deleteById(answerId).toPromise(); 29 | } 30 | 31 | updateAnswer(values){ 32 | let data = new Answer(); 33 | data.answer = values.answer; 34 | data.positiveVotes = values.positiveVotes; 35 | data.negativeVotes = values.negativeVotes; 36 | data.questionId = values.questionId; 37 | return this.answerApi.updateAttributes(values.id, data) 38 | .toPromise() 39 | } 40 | 41 | createAnswer(values){ 42 | let data = new Answer(); 43 | data.answer = values.answer; 44 | data.questionId = values.questionId; 45 | return this.answerApi.create(data) 46 | .toPromise() 47 | } 48 | 49 | countAnswers(questionId){ 50 | let query = { 51 | questionId: questionId 52 | } 53 | return this.answerApi.count({where: query}) 54 | .toPromise() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/app/services/categories.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http } from '@angular/http'; 3 | import { CategoryModel } from "../categories/category.model"; 4 | 5 | @Injectable() 6 | export class CategoriesService { 7 | 8 | constructor(private http: Http){} 9 | 10 | getCategories(): Promise { 11 | return this.http.get("./assets/categories.json") 12 | .toPromise() 13 | .then(res => res.json() as CategoryModel[]) 14 | } 15 | 16 | getCategoryBySlug(slug: string){ 17 | return this.getCategories() 18 | .then(categories =>{ 19 | return categories.find((category) => { 20 | return category.slug == slug; 21 | }); 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/services/questions.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { QuestionApi, Question, LoopBackFilter } from '../../../sdk'; 3 | 4 | 5 | @Injectable() 6 | export class QuestionsService { 7 | constructor( 8 | private questionApi: QuestionApi 9 | ){} 10 | 11 | getQuestions(){ 12 | let filter: LoopBackFilter = { 13 | "include":{ 14 | "relation": "answers" 15 | } 16 | } 17 | return this.questionApi.find(filter) 18 | .toPromise() 19 | } 20 | 21 | getQuestion(questionId){ 22 | let query = { 23 | id: questionId 24 | } 25 | return this.questionApi.find({where: query}) 26 | .toPromise() 27 | } 28 | 29 | getQuestionsByCategory(category_slug){ 30 | let filter: LoopBackFilter = { 31 | "include":{ 32 | "relation": "answers" 33 | }, 34 | "where": { 35 | "categorySlug": category_slug 36 | } 37 | } 38 | return this.questionApi.find(filter) 39 | .toPromise() 40 | } 41 | 42 | getQuestionBySlug(slug){ 43 | let filter: LoopBackFilter = { 44 | "include":{ 45 | "relation": "answers" 46 | }, 47 | "where": { 48 | "questionSlug": slug 49 | } 50 | } 51 | return this.questionApi.findOne(filter) 52 | .toPromise() 53 | } 54 | 55 | deleteQuestion(questionId){ 56 | return this.questionApi.deleteById(questionId).toPromise() 57 | } 58 | 59 | updateQuestion(question){ 60 | return this.questionApi.updateAttributes(question.id, question).toPromise() 61 | } 62 | 63 | createQuestion(values){ 64 | let data = new Question(); 65 | data.question = values.question; 66 | data.questionSlug = values.questionSlug; 67 | data.categorySlug = values.categorySlug; 68 | 69 | return this.questionApi.create(data).toPromise() 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/app/shared/breadcrumb/breadcrumb.component.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /src/app/shared/breadcrumb/breadcrumb.component.ts: -------------------------------------------------------------------------------- 1 | // Inspired on: http://brianflove.com/2016/10/23/angular2-breadcrumb-using-router/ 2 | 3 | 4 | import {switchMap, map, filter} from 'rxjs/operators'; 5 | import { Component, ViewEncapsulation, OnInit } from '@angular/core'; 6 | import { Router, ActivatedRoute, NavigationEnd, Params, PRIMARY_OUTLET } from "@angular/router"; 7 | 8 | 9 | interface IBreadcrumb { 10 | label: string; 11 | params: Params; 12 | url: string; 13 | } 14 | 15 | @Component({ 16 | selector: 'breadcrumb', 17 | styleUrls: [ './styles/breadcrumb.scss' ], 18 | templateUrl: './breadcrumb.component.html', 19 | encapsulation: ViewEncapsulation.None 20 | }) 21 | 22 | export class BreadcrumbComponent implements OnInit { 23 | 24 | public breadcrumbs: IBreadcrumb[]; 25 | public _routerSubscription: any; 26 | 27 | constructor( 28 | private activatedRoute: ActivatedRoute, 29 | private router: Router 30 | ) { 31 | this.breadcrumbs = []; 32 | } 33 | 34 | ngOnInit() { 35 | this._routerSubscription = this.router.events.pipe( 36 | filter(event => event instanceof NavigationEnd), 37 | map(() => this.activatedRoute), 38 | map(route => { 39 | while (route.firstChild) route = route.firstChild; 40 | return route; 41 | }), 42 | filter(route => route.outlet === 'primary'), 43 | switchMap(route => route.data),) 44 | .subscribe((event) => { 45 | if(event['data'] && event['data'].breadcrumbs){ 46 | this.breadcrumbs = event['data'].breadcrumbs; 47 | } 48 | else 49 | { 50 | // Empty breadcrumbs 51 | this.breadcrumbs = []; 52 | } 53 | }); 54 | } 55 | 56 | ngOnDestroy(): void { 57 | if(this._routerSubscription){ 58 | this._routerSubscription.unsubscribe(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/app/shared/breadcrumb/styles/breadcrumb.scss: -------------------------------------------------------------------------------- 1 | breadcrumb { 2 | .breadcrumb { 3 | background: transparent; 4 | border-radius: 0; 5 | margin: 20px 0; 6 | padding: 0; 7 | 8 | .breadcrumb-item { 9 | color: #FFF; 10 | font-size: 15px; 11 | font-weight: 500; 12 | letter-spacing: 1.9px; 13 | 14 | a { 15 | color: #FFF; 16 | text-decoration: none; 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { HttpModule } from '@angular/http'; 4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 5 | import { RouterModule } from '@angular/router'; 6 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 7 | 8 | import { SDKBrowserModule } from '../../../sdk/index'; 9 | import { SlugifyPipe } from '../shared/slugify.pipe'; 10 | // Material modules 11 | import { 12 | MatDialogModule, 13 | MatToolbarModule, 14 | MatListModule, 15 | MatButtonModule, 16 | MatChipsModule 17 | } from '@angular/material'; 18 | 19 | import { BreadcrumbComponent } from './breadcrumb/breadcrumb.component'; 20 | 21 | 22 | @NgModule({ 23 | declarations: [ 24 | BreadcrumbComponent, 25 | SlugifyPipe 26 | ], 27 | imports: [ 28 | CommonModule, 29 | HttpModule, 30 | ReactiveFormsModule, 31 | FormsModule, 32 | RouterModule, 33 | MatDialogModule, 34 | MatToolbarModule, 35 | MatListModule, 36 | MatButtonModule, 37 | MatChipsModule, 38 | SDKBrowserModule.forRoot(), 39 | BrowserAnimationsModule 40 | ], 41 | providers: [ 42 | SlugifyPipe 43 | ], 44 | exports: [ 45 | BreadcrumbComponent, 46 | CommonModule, 47 | HttpModule, 48 | ReactiveFormsModule, 49 | FormsModule, 50 | RouterModule, 51 | // Material modules 52 | MatDialogModule, 53 | MatToolbarModule, 54 | MatListModule, 55 | MatButtonModule, 56 | MatChipsModule 57 | ] 58 | }) 59 | export class SharedModule { } 60 | -------------------------------------------------------------------------------- /src/app/shared/slugify.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({name: 'slugify'}) 4 | export class SlugifyPipe implements PipeTransform { 5 | transform(input: string): string { 6 | return input.toString().toLowerCase() 7 | .replace(/\s+/g, '-') // Replace spaces with - 8 | .replace(/[^\w\-]+/g, '') // Remove all non-word chars 9 | .replace(/\-\-+/g, '-') // Replace multiple - with single - 10 | .replace(/^-+/, '') // Trim - from start of text 11 | .replace(/-+$/, ''); // Trim - from end of text 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | $primary: #3f51b5; 3 | $grey8: #4a4a4a; 4 | $grey4: #e1e1e1; 5 | $grey5: #555; 6 | -------------------------------------------------------------------------------- /src/app/styles/app.scss: -------------------------------------------------------------------------------- 1 | .top-toolbar{ 2 | margin-bottom: 30px; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/styles/modals.scss: -------------------------------------------------------------------------------- 1 | @import './_variables'; 2 | 3 | .alert-modal { 4 | min-width: 300px; 5 | 6 | .alert-modal-title { 7 | color: $grey8; 8 | font-weight: 300; 9 | text-align: center; 10 | } 11 | 12 | .alert-modal-text { 13 | color: $grey8; 14 | display: block; 15 | text-align: center; 16 | } 17 | 18 | .alert-modal-buttons { 19 | text-align: center; 20 | display: flex; 21 | justify-content: center; 22 | 23 | .alert-modal-button { 24 | 25 | margin-bottom: 10px; 26 | margin-top: 40px; 27 | min-width: 60px; 28 | font-weight: 400; 29 | width: 60%; 30 | 31 | &.alert-modal-button-success { 32 | margin-right: 10px; 33 | } 34 | 35 | &.alert-modal-button-failure { 36 | 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngularTemplates/learn-angular-from-scratch-step-by-step/3e0406309c3d0954d2abdcc5d016fffcad3e9060/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "slug": "angular", 4 | "title": "Angular", 5 | "image": "https://angular.io/assets/images/logos/angular/angular.svg", 6 | "description": "Angular is a platform that makes it easy to build applications with the web.", 7 | "tags": [ 8 | { 9 | "name": "Angular JS" 10 | }, 11 | { 12 | "name": "Angular 2" 13 | }, 14 | { 15 | "name": "Angular 4" 16 | }, 17 | { 18 | "name": "Angular 5" 19 | }, 20 | { 21 | "name": "Angular 6" 22 | }, 23 | { 24 | "name": "Angular 7" 25 | } 26 | ] 27 | }, 28 | { 29 | "slug": "angular-cli", 30 | "title": "Angular Cli", 31 | "image": "https://angular.io/generated/images/marketing/concept-icons/cli.svg", 32 | "description": "Angular cli is a command line interface to scaffold and build angular apps using nodejs style modules.", 33 | "tags":[ 34 | { 35 | "name": "Angular CLI" 36 | }, 37 | { 38 | "name": "Testing" 39 | } 40 | ] 41 | }, 42 | { 43 | "slug": "typescript", 44 | "title": "Typescript", 45 | "image": "https://s.gravatar.com/avatar/3e2b342616822f8eabc9dd393840db4a?size=496&default=retro", 46 | "description": "TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.", 47 | "tags": [ 48 | { 49 | "name": "Javascript" 50 | }, 51 | { 52 | "name": "Webpack" 53 | }, 54 | { 55 | "name": "Typescript 2.x" 56 | } 57 | ] 58 | } 59 | ] 60 | -------------------------------------------------------------------------------- /src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | api_url: 'https://q-a-example-loopback-api.herokuapp.com' 4 | }; 5 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | api_url: 'https://q-a-example-loopback-api.herokuapp.com' 8 | }; 9 | 10 | /* 11 | * For easier debugging in development mode, you can import the following file 12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 13 | * 14 | * This import should be commented out in production mode because it will have a negative impact 15 | * on performance if an error is thrown. 16 | */ 17 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 18 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngularTemplates/learn-angular-from-scratch-step-by-step/3e0406309c3d0954d2abdcc5d016fffcad3e9060/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LearnAngularFromScratch 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage/learn-angular-from-scratch'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import 'hammerjs'; 2 | import { enableProdMode } from '@angular/core'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { LoopBackConfig } from '../sdk'; 5 | 6 | import { AppModule } from './app/app.module'; 7 | import { environment } from './environments/environment'; 8 | 9 | if (environment.production) { 10 | enableProdMode(); 11 | } 12 | 13 | platformBrowserDynamic().bootstrapModule(AppModule) 14 | .catch(err => console.error(err)); 15 | 16 | LoopBackConfig.setBaseURL(environment.api_url); 17 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | html, body { height: 100%; } 4 | body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } 5 | 6 | $icon-font-path: "https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/fonts/" !default; 7 | // Include Bootstrap styles 8 | @import "~bootstrap-sass/assets/stylesheets/_bootstrap"; 9 | @import '~bootstrap-sass/assets/stylesheets/bootstrap/variables'; 10 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rulesDirectory": [ 4 | "codelyzer" 5 | ], 6 | "rules": { 7 | "array-type": false, 8 | "arrow-parens": false, 9 | "deprecation": { 10 | "severity": "warn" 11 | }, 12 | "import-blacklist": [ 13 | true, 14 | "rxjs/Rx" 15 | ], 16 | "interface-name": false, 17 | "max-classes-per-file": false, 18 | "max-line-length": [ 19 | true, 20 | 140 21 | ], 22 | "member-access": false, 23 | "member-ordering": [ 24 | true, 25 | { 26 | "order": [ 27 | "static-field", 28 | "instance-field", 29 | "static-method", 30 | "instance-method" 31 | ] 32 | } 33 | ], 34 | "no-consecutive-blank-lines": false, 35 | "no-console": [ 36 | true, 37 | "debug", 38 | "info", 39 | "time", 40 | "timeEnd", 41 | "trace" 42 | ], 43 | "no-empty": false, 44 | "no-inferrable-types": [ 45 | true, 46 | "ignore-params" 47 | ], 48 | "no-non-null-assertion": true, 49 | "no-redundant-jsdoc": true, 50 | "no-switch-case-fall-through": true, 51 | "no-use-before-declare": true, 52 | "no-var-requires": false, 53 | "object-literal-key-quotes": [ 54 | true, 55 | "as-needed" 56 | ], 57 | "object-literal-sort-keys": false, 58 | "ordered-imports": false, 59 | "quotemark": [ 60 | true, 61 | "single" 62 | ], 63 | "trailing-comma": false, 64 | "no-output-on-prefix": true, 65 | "use-input-property-decorator": true, 66 | "use-output-property-decorator": true, 67 | "use-host-property-decorator": true, 68 | "no-input-rename": true, 69 | "no-output-rename": true, 70 | "use-life-cycle-interface": true, 71 | "use-pipe-transform-interface": true, 72 | "component-class-suffix": true, 73 | "directive-class-suffix": true 74 | } 75 | } 76 | --------------------------------------------------------------------------------