├── .editorconfig ├── .firebaserc ├── .gitignore ├── .prettierrc ├── README.md ├── angular.json ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.e2e.json ├── firebase.json ├── package.json ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── auth │ │ ├── auth.module.ts │ │ ├── containers │ │ │ ├── auth-index │ │ │ │ ├── auth-index.component.spec.ts │ │ │ │ └── auth-index.component.ts │ │ │ ├── auth-login │ │ │ │ ├── auth-login.component.spec.ts │ │ │ │ └── auth-login.component.ts │ │ │ ├── auth-logout │ │ │ │ ├── auth-logout.component.spec.ts │ │ │ │ └── auth-logout.component.ts │ │ │ └── auth-profile │ │ │ │ ├── auth-profile.component.spec.ts │ │ │ │ └── auth-profile.component.ts │ │ ├── guards │ │ │ └── logged-in.guard.ts │ │ ├── resolvers │ │ │ └── profile.resolver.ts │ │ └── services │ │ │ ├── auth.service.spec.ts │ │ │ └── auth.service.ts │ ├── crud │ │ ├── collections │ │ │ ├── beer.model.ts │ │ │ └── note.model.ts │ │ ├── components │ │ │ ├── item-field │ │ │ │ └── item-field.component.ts │ │ │ ├── item-form │ │ │ │ └── item-form.component.ts │ │ │ ├── item-list │ │ │ │ └── item-list.component.ts │ │ │ └── item-modal │ │ │ │ └── item-modal.component.ts │ │ ├── containers │ │ │ └── items-index │ │ │ │ └── items-index.component.ts │ │ ├── crud.module.ts │ │ ├── data.typings.ts │ │ ├── index.ts │ │ ├── resolvers │ │ │ └── collection.resolver.ts │ │ └── services │ │ │ ├── abstract-data.service.ts │ │ │ ├── data.service.ts │ │ │ ├── firebase-data.service.ts │ │ │ └── index.ts │ └── fire │ │ ├── components │ │ ├── message-form │ │ │ ├── message-form.component.spec.ts │ │ │ └── message-form.component.ts │ │ ├── message-list │ │ │ ├── message-list.component.spec.ts │ │ │ └── message-list.component.ts │ │ └── message │ │ │ ├── message.component.spec.ts │ │ │ └── message.component.ts │ │ ├── containers │ │ ├── fire-index │ │ │ ├── fire-index.component.spec.ts │ │ │ └── fire-index.component.ts │ │ ├── fire-sidebar │ │ │ ├── fire-sidebar.component.spec.ts │ │ │ └── fire-sidebar.component.ts │ │ └── guestbook │ │ │ ├── guestbook.component.spec.ts │ │ │ └── guestbook.component.ts │ │ ├── fire.module.ts │ │ └── services │ │ ├── fire.service.spec.ts │ │ └── fire.service.ts ├── assets │ └── tabler.svg ├── 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 └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "tabler-angular-fire" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "semi": false, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tabler for Angular - Firebase 2 | 3 | > Firebase Demo of [Tabler for Angular](https://github.com/tabler/tabler-angular) 4 | 5 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0-rc.7. 6 | 7 | ## Development server 8 | 9 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 10 | 11 | ## Code scaffolding 12 | 13 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 14 | 15 | ## Build 16 | 17 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 18 | 19 | ## Running unit tests 20 | 21 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 22 | 23 | ## Running end-to-end tests 24 | 25 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 26 | 27 | ## Further help 28 | 29 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 30 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "tabler-angular-firebase": { 7 | "root": "", 8 | "sourceRoot": "/src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": {}, 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/tabler-angular-firebase", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "src/tsconfig.app.json", 21 | "assets": [ 22 | { 23 | "glob": "favicon.ico", 24 | "input": "src", 25 | "output": "/" 26 | }, 27 | { 28 | "glob": "**/*", 29 | "input": "src/assets", 30 | "output": "/assets" 31 | } 32 | ], 33 | "styles": [ 34 | "src/styles.scss" 35 | ], 36 | "scripts": [] 37 | }, 38 | "configurations": { 39 | "production": { 40 | "fileReplacements": [ 41 | { 42 | "replace": "src/environments/environment.ts", 43 | "with": "src/environments/environment.prod.ts" 44 | } 45 | ], 46 | "optimization": true, 47 | "outputHashing": "all", 48 | "sourceMap": false, 49 | "extractCss": true, 50 | "namedChunks": false, 51 | "aot": true, 52 | "extractLicenses": true, 53 | "vendorChunk": false, 54 | "buildOptimizer": true 55 | } 56 | } 57 | }, 58 | "serve": { 59 | "builder": "@angular-devkit/build-angular:dev-server", 60 | "options": { 61 | "browserTarget": "tabler-angular-firebase:build" 62 | }, 63 | "configurations": { 64 | "production": { 65 | "browserTarget": "tabler-angular-firebase:build:production" 66 | } 67 | } 68 | }, 69 | "extract-i18n": { 70 | "builder": "@angular-devkit/build-angular:extract-i18n", 71 | "options": { 72 | "browserTarget": "tabler-angular-firebase:build" 73 | } 74 | }, 75 | "test": { 76 | "builder": "@angular-devkit/build-angular:karma", 77 | "options": { 78 | "main": "src/test.ts", 79 | "polyfills": "src/polyfills.ts", 80 | "tsConfig": "src/tsconfig.spec.json", 81 | "karmaConfig": "src/karma.conf.js", 82 | "styles": [ 83 | "styles.scss" 84 | ], 85 | "scripts": [], 86 | "assets": [ 87 | { 88 | "glob": "favicon.ico", 89 | "input": "src/", 90 | "output": "/" 91 | }, 92 | { 93 | "glob": "**/*", 94 | "input": "src/assets", 95 | "output": "/assets" 96 | } 97 | ] 98 | } 99 | }, 100 | "lint": { 101 | "builder": "@angular-devkit/build-angular:tslint", 102 | "options": { 103 | "tsConfig": [ 104 | "src/tsconfig.app.json", 105 | "src/tsconfig.spec.json" 106 | ], 107 | "exclude": [ 108 | "**/node_modules/**" 109 | ] 110 | } 111 | } 112 | } 113 | }, 114 | "tabler-angular-firebase-e2e": { 115 | "root": "e2e/", 116 | "projectType": "application", 117 | "architect": { 118 | "e2e": { 119 | "builder": "@angular-devkit/build-angular:protractor", 120 | "options": { 121 | "protractorConfig": "e2e/protractor.conf.js", 122 | "devServerTarget": "tabler-angular-firebase:serve" 123 | } 124 | }, 125 | "lint": { 126 | "builder": "@angular-devkit/build-angular:tslint", 127 | "options": { 128 | "tsConfig": "e2e/tsconfig.e2e.json", 129 | "exclude": [ 130 | "**/node_modules/**" 131 | ] 132 | } 133 | } 134 | } 135 | } 136 | }, 137 | "defaultProject": "tabler-angular-firebase" 138 | } 139 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('workspace-project App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to app!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/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/tabler-angular-firebase", 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": "tabler-angular-firebase", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve", 8 | "build": "ng build --prod", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "^6.0.0-rc.5", 16 | "@angular/common": "^6.0.0-rc.5", 17 | "@angular/compiler": "^6.0.0-rc.5", 18 | "@angular/core": "^6.0.0-rc.5", 19 | "@angular/forms": "^6.0.0-rc.5", 20 | "@angular/http": "^6.0.0-rc.5", 21 | "@angular/platform-browser": "^6.0.0-rc.5", 22 | "@angular/platform-browser-dynamic": "^6.0.0-rc.5", 23 | "@angular/router": "^6.0.0-rc.5", 24 | "@firebase/app": "^0.1.6", 25 | "@firebase/auth": "^0.4.2", 26 | "@firebase/firestore": "^0.4.1", 27 | "@ngx-formly/bootstrap": "^3.0.0", 28 | "@ngx-formly/core": "^3.0.0", 29 | "@tabler/angular-core": "^0.7.3", 30 | "@tabler/angular-forms": "^0.7.3", 31 | "@tabler/angular-styles": "^0.7.3", 32 | "@tabler/angular-ui": "^0.7.3", 33 | "angularfire2": "^5.0.0-rc.6", 34 | "bootstrap": "^4.1.1", 35 | "core-js": "^2.5.4", 36 | "firebase": "^4.12.1", 37 | "font-awesome": "^4.7.0", 38 | "ngx-bootstrap": "^2.0.4", 39 | "rxjs": "^6.0.0", 40 | "rxjs-compat": "^6.0.0", 41 | "simple-line-icons": "^2.4.1", 42 | "zone.js": "^0.8.26" 43 | }, 44 | "devDependencies": { 45 | "@angular/compiler-cli": "^6.0.0-rc.5", 46 | "@angular-devkit/build-angular": "~0.5.8", 47 | "typescript": "~2.7.2", 48 | "@angular/cli": "~6.0.0-rc.7", 49 | "@angular/language-service": "^6.0.0-rc.5", 50 | "@types/jasmine": "~2.8.6", 51 | "@types/jasminewd2": "~2.0.3", 52 | "@types/node": "~8.9.4", 53 | "codelyzer": "~4.2.1", 54 | "jasmine-core": "~2.99.1", 55 | "jasmine-spec-reporter": "~4.2.1", 56 | "karma": "~1.7.1", 57 | "karma-chrome-launcher": "~2.2.0", 58 | "karma-coverage-istanbul-reporter": "~1.4.2", 59 | "karma-jasmine": "~1.1.1", 60 | "karma-jasmine-html-reporter": "^0.2.2", 61 | "protractor": "~5.3.0", 62 | "ts-node": "~5.0.1", 63 | "tslint": "~5.9.1" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { ExtraOptions, RouterModule, Routes } from '@angular/router' 2 | import { ModuleWithProviders } from '@angular/core' 3 | import { LayoutComponent } from '@tabler/angular-ui' 4 | import { AuthModuleRoutes } from './auth/auth.module' 5 | import { LoggedInGuard } from './auth/guards/logged-in.guard' 6 | 7 | const routes: Routes = [ 8 | { path: '', redirectTo: 'guestbook', pathMatch: 'full' }, 9 | { 10 | path: '', 11 | component: LayoutComponent, 12 | children: [ 13 | { path: 'auth', children: [...AuthModuleRoutes] }, 14 | { path: 'guestbook', loadChildren: './fire/fire.module#FireModule' }, 15 | { 16 | path: 'crud', 17 | loadChildren: './crud/crud.module#CrudModule', 18 | canActivate: [LoggedInGuard], 19 | }, 20 | ], 21 | }, 22 | { path: '**', redirectTo: '/404' }, 23 | ] 24 | 25 | const options: ExtraOptions = { 26 | paramsInheritanceStrategy: 'always', 27 | } 28 | 29 | export const AppRoutingModule: ModuleWithProviders = RouterModule.forRoot(routes, options) 30 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core' 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | template: ` 6 | 7 | `, 8 | }) 9 | export class AppComponent {} 10 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core' 2 | import { BrowserModule } from '@angular/platform-browser' 3 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations' 4 | import { CoreModule, SharedModule } from '@tabler/angular-core' 5 | 6 | import { AngularFireModule } from 'angularfire2' 7 | import { AngularFireAuthModule } from 'angularfire2/auth' 8 | import { AngularFirestore, AngularFirestoreModule } from 'angularfire2/firestore' 9 | 10 | import { environment } from '../environments/environment' 11 | 12 | import { AuthModule } from './auth/auth.module' 13 | 14 | import { AppRoutingModule } from './app-routing.module' 15 | import { AppComponent } from './app.component' 16 | import { AppService } from './app.service' 17 | import { BsDropdownModule } from 'ngx-bootstrap' 18 | 19 | @NgModule({ 20 | imports: [ 21 | // Angular 22 | BrowserModule, 23 | BrowserAnimationsModule, 24 | // Tabler 25 | CoreModule, 26 | SharedModule, 27 | // Firebase 28 | AngularFireModule.initializeApp(environment.firebase), 29 | AngularFirestoreModule, 30 | AngularFireAuthModule, 31 | // Application modules & routing 32 | AuthModule, 33 | AppRoutingModule, 34 | BsDropdownModule.forRoot(), 35 | ], 36 | declarations: [AppComponent], 37 | bootstrap: [AppComponent], 38 | providers: [AppService], 39 | }) 40 | export class AppModule { 41 | constructor(app: AppService, db: AngularFirestore) { 42 | app.init() 43 | db.firestore.settings({ 44 | timestampsInSnapshots: true, 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/app/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | import { UiService } from '@tabler/angular-ui' 3 | import { AuthService } from './auth/services/auth.service' 4 | 5 | const defaultUser = { 6 | name: 'Anonymous', 7 | email: 'Guest account', 8 | avatarText: '?', 9 | } 10 | const headerBaseLinks = [ 11 | { 12 | label: 'Guestbook', 13 | icon: 'fe fe-book', 14 | link: '/guestbook', 15 | }, 16 | ] 17 | const headerLoggedIn = [ 18 | ...headerBaseLinks, 19 | { 20 | label: 'Beers', 21 | icon: 'fa fa-beer', 22 | link: '/crud/beers', 23 | }, 24 | { 25 | label: 'Notes', 26 | icon: 'fa fa-pencil-square-o', 27 | link: '/crud/notes', 28 | }, 29 | ] 30 | const headerLoggedOut = [ 31 | ...headerBaseLinks, 32 | { 33 | label: 'Firebase CRUD', 34 | icon: 'fa fa-database', 35 | link: '/crud/beers', 36 | }, 37 | ] 38 | const profileLoggedIn = [ 39 | { 40 | link: '/auth/profile', 41 | linkClass: 'dropdown-item', 42 | label: 'Profile', 43 | icon: 'fe fe-user dropdown-icon', 44 | }, 45 | { 46 | link: '/auth/logout', 47 | linkClass: 'dropdown-item', 48 | label: 'Log out', 49 | icon: 'fe fe-log-out dropdown-icon', 50 | }, 51 | ] 52 | const profileLoggedOut = [ 53 | { 54 | link: '/auth/login', 55 | linkClass: 'dropdown-item', 56 | label: 'Log in', 57 | icon: 'fe fe-log-in dropdown-icon', 58 | }, 59 | ] 60 | 61 | @Injectable() 62 | export class AppService { 63 | public config$ 64 | constructor(private auth: AuthService, private ui: UiService) { 65 | this.config$ = ui.config$ 66 | this.auth.user$.map(user => (user ? user : defaultUser)).subscribe(user => this.updateUser(user)) 67 | this.init() 68 | } 69 | 70 | public updateUser(user) { 71 | this.ui.profile = user || defaultUser 72 | this.ui.updateLayout({ 73 | headerNav: user.id ? headerLoggedIn : headerLoggedOut, 74 | profileNav: user.id ? profileLoggedIn : profileLoggedOut, 75 | }) 76 | } 77 | 78 | public init() { 79 | this.ui.appName = 'Firebase Demo' 80 | this.ui.appLogo = 'assets/tabler.svg' 81 | this.ui.headerSubNav = [ 82 | { 83 | label: 'Source Code', 84 | labelClass: 'd-none d-lg-inline', 85 | link: 'https://github.com/tabler/tabler-angular-firebase', 86 | linkClass: 'btn btn-sm btn-outline-primary ml-2', 87 | icon: 'fe fe-github mr-1', 88 | }, 89 | ] 90 | this.ui.footerText = ` 91 | Copyright © 2018 tabler-angular-firebase. 92 | Tabler by @codecalm. 93 | Tabler for Angular by @beeman. 94 | MIT Licensed 95 | ` 96 | this.ui.footerNav = [ 97 | { 98 | label: 'Source Code', 99 | link: 'https://github.com/tabler/tabler-angular-firebase', 100 | linkClass: 'btn btn-sm btn-outline-primary ml-2', 101 | icon: 'fe fe-github mr-2', 102 | }, 103 | ] 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/app/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core' 2 | import { Routes } from '@angular/router' 3 | import { SharedModule } from '@tabler/angular-core' 4 | import { LoggedInGuard } from './guards/logged-in.guard' 5 | import { ProfileResolver } from './resolvers/profile.resolver' 6 | import { AuthService } from './services/auth.service' 7 | import { AuthIndexComponent } from './containers/auth-index/auth-index.component' 8 | import { AuthLogoutComponent } from './containers/auth-logout/auth-logout.component' 9 | import { AuthLoginComponent } from './containers/auth-login/auth-login.component' 10 | import { AuthProfileComponent } from './containers/auth-profile/auth-profile.component' 11 | 12 | export const AuthModuleRoutes: Routes = [ 13 | { path: '', redirectTo: 'profile', pathMatch: 'full' }, 14 | { 15 | path: '', 16 | component: AuthIndexComponent, 17 | resolve: { authState: ProfileResolver }, 18 | children: [ 19 | { path: 'login', component: AuthLoginComponent }, 20 | { path: 'logout', component: AuthLogoutComponent }, 21 | { 22 | path: 'profile', 23 | component: AuthProfileComponent, 24 | canActivate: [LoggedInGuard], 25 | }, 26 | ], 27 | }, 28 | ] 29 | 30 | @NgModule({ 31 | imports: [SharedModule], 32 | declarations: [AuthIndexComponent, AuthLogoutComponent, AuthLoginComponent, AuthProfileComponent], 33 | providers: [AuthService, ProfileResolver, LoggedInGuard], 34 | }) 35 | export class AuthModule {} 36 | -------------------------------------------------------------------------------- /src/app/auth/containers/auth-index/auth-index.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing' 2 | 3 | import { AuthIndexComponent } from './auth-index.component' 4 | 5 | describe('AuthIndexComponent', () => { 6 | let component: AuthIndexComponent 7 | let fixture: ComponentFixture 8 | 9 | beforeEach( 10 | async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [AuthIndexComponent], 13 | }).compileComponents() 14 | }) 15 | ) 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(AuthIndexComponent) 19 | component = fixture.componentInstance 20 | fixture.detectChanges() 21 | }) 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy() 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /src/app/auth/containers/auth-index/auth-index.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core' 2 | 3 | @Component({ 4 | selector: 'app-auth-index', 5 | template: ` 6 | 7 |
8 |
9 | 10 |
11 |
12 |
13 | `, 14 | }) 15 | export class AuthIndexComponent {} 16 | -------------------------------------------------------------------------------- /src/app/auth/containers/auth-login/auth-login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing' 2 | 3 | import { AuthLoginComponent } from './auth-login.component' 4 | 5 | describe('AuthLoginComponent', () => { 6 | let component: AuthLoginComponent 7 | let fixture: ComponentFixture 8 | 9 | beforeEach( 10 | async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [AuthLoginComponent], 13 | }).compileComponents() 14 | }) 15 | ) 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(AuthLoginComponent) 19 | component = fixture.componentInstance 20 | fixture.detectChanges() 21 | }) 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy() 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /src/app/auth/containers/auth-login/auth-login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core' 2 | import { ActivatedRoute, Router } from '@angular/router' 3 | import { AuthService } from '../../services/auth.service' 4 | 5 | @Component({ 6 | selector: 'app-auth-login', 7 | template: ` 8 | 11 | 12 | 13 | 14 | An error occurred. 15 | {{error}} 16 | 17 | 18 |
19 |
20 | 21 |
22 | 28 |
29 | 30 |
31 |
32 | `, 33 | styles: [], 34 | }) 35 | export class AuthLoginComponent { 36 | private readonly redirectUrl: string 37 | public error = null 38 | 39 | constructor(public auth: AuthService, private route: ActivatedRoute, private router: Router) { 40 | this.redirectUrl = this.route.snapshot.queryParams['url'] || '/auth/profile' 41 | } 42 | 43 | redirect() { 44 | return this.router.navigate([this.redirectUrl]) 45 | } 46 | 47 | login(provider) { 48 | this.error = false 49 | this.auth.login(provider).subscribe(() => this.redirect(), err => (this.error = err.message)) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/app/auth/containers/auth-logout/auth-logout.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing' 2 | 3 | import { AuthLogoutComponent } from './auth-logout.component' 4 | 5 | describe('AuthLogoutComponent', () => { 6 | let component: AuthLogoutComponent 7 | let fixture: ComponentFixture 8 | 9 | beforeEach( 10 | async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [AuthLogoutComponent], 13 | }).compileComponents() 14 | }) 15 | ) 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(AuthLogoutComponent) 19 | component = fixture.componentInstance 20 | fixture.detectChanges() 21 | }) 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy() 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /src/app/auth/containers/auth-logout/auth-logout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core' 2 | import { AuthService } from '../../services/auth.service' 3 | 4 | @Component({ 5 | selector: 'app-auth-logout', 6 | template: ` 7 | 10 | 11 | 12 |
13 |
14 | Click here to log in again. 15 |
16 |
17 | `, 18 | styles: [], 19 | }) 20 | export class AuthLogoutComponent implements OnInit { 21 | public loggedOut = false 22 | constructor(private auth: AuthService) {} 23 | 24 | ngOnInit() { 25 | this.auth.logout().subscribe(() => (this.loggedOut = !this.loggedOut)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/app/auth/containers/auth-profile/auth-profile.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing' 2 | 3 | import { AuthProfileComponent } from './auth-profile.component' 4 | 5 | describe('AuthProfileComponent', () => { 6 | let component: AuthProfileComponent 7 | let fixture: ComponentFixture 8 | 9 | beforeEach( 10 | async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [AuthProfileComponent], 13 | }).compileComponents() 14 | }) 15 | ) 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(AuthProfileComponent) 19 | component = fixture.componentInstance 20 | fixture.detectChanges() 21 | }) 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy() 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /src/app/auth/containers/auth-profile/auth-profile.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core' 2 | import { AuthService } from '../../services/auth.service' 3 | 4 | @Component({ 5 | selector: 'app-auth-profile', 6 | template: ` 7 | 8 | 9 |
10 | 11 | An error occurred. 12 | {{error}} 13 | 14 |
15 | 16 |
17 | 18 |
19 |
20 |
21 | 23 | 24 | 25 | {{ user.name }} 26 | 27 | {{ user.email }} 28 | 29 | 30 | 31 |
32 |
33 | 36 |
37 |
38 |
39 | 40 |
41 | 45 |
{{ authState$ | async | json }}
46 |
47 | 48 |
49 | `, 50 | styles: [], 51 | }) 52 | export class AuthProfileComponent implements OnInit { 53 | public authState$ 54 | public user$ 55 | public error = null 56 | public debug = false 57 | 58 | constructor(private authService: AuthService) {} 59 | 60 | ngOnInit() { 61 | this.authState$ = this.authService.authState$ 62 | this.user$ = this.authService.user$ 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/app/auth/guards/logged-in.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router' 3 | 4 | import { AuthService } from '../services/auth.service' 5 | import { map, take, tap } from 'rxjs/internal/operators' 6 | 7 | @Injectable() 8 | export class LoggedInGuard implements CanActivate { 9 | constructor(private auth: AuthService, private router: Router) { 10 | } 11 | 12 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { 13 | const redirectUrl = state.url || '/auth/profile' 14 | 15 | return this.auth.authState$ 16 | .pipe( 17 | take(1), 18 | map(authState => !!authState), 19 | tap(authenticated => { 20 | if (!authenticated) { 21 | return this.login(redirectUrl) 22 | } 23 | }), 24 | ) 25 | } 26 | 27 | login(redirectUrl) { 28 | return this.router.navigate([`/auth/login`], { 29 | queryParams: { 30 | url: redirectUrl, 31 | }, 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/auth/resolvers/profile.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | import { Resolve } from '@angular/router' 3 | import { of } from 'rxjs' 4 | 5 | @Injectable() 6 | export class ProfileResolver implements Resolve { 7 | constructor() {} 8 | 9 | resolve() { 10 | return of({}) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/app/auth/services/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing' 2 | 3 | import { AuthService } from './auth.service' 4 | 5 | describe('AuthService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [AuthService], 9 | }) 10 | }) 11 | 12 | it( 13 | 'should be created', 14 | inject([AuthService], (service: AuthService) => { 15 | expect(service).toBeTruthy() 16 | }) 17 | ) 18 | }) 19 | -------------------------------------------------------------------------------- /src/app/auth/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | import { AngularFirestore, AngularFirestoreDocument } from 'angularfire2/firestore' 3 | 4 | import { firebase } from '@firebase/app'; 5 | 6 | 7 | import { AngularFireAuth } from 'angularfire2/auth' 8 | import { User } from 'firebase/auth' 9 | 10 | import { Observable, of } from 'rxjs' 11 | import { fromPromise } from 'rxjs/internal/observable/fromPromise' 12 | import { switchMap } from 'rxjs/internal/operators' 13 | // import GoogleAuthProvider = firebase.auth.GoogleAuthProvider 14 | 15 | export interface FireUser { 16 | id: string 17 | name: string 18 | email: string 19 | avatar?: string 20 | avatarText?: string 21 | // providerData?: any 22 | // [key: string]: any | any[] 23 | } 24 | 25 | const defaultProfile = user => ({ 26 | id: user.uid, 27 | name: user.displayName, 28 | email: user.email, 29 | avatar: user.photoURL, 30 | // providerData: user.providerData, 31 | }) 32 | 33 | @Injectable() 34 | export class AuthService { 35 | private readonly collection = 'Users' 36 | 37 | public authState$: Observable 38 | public user$: Observable 39 | 40 | private _isLoggedIn = false 41 | public get isLoggedIn() { 42 | return this._isLoggedIn 43 | } 44 | 45 | constructor(private afAuth: AngularFireAuth, private db: AngularFirestore) { 46 | this._observeState() 47 | } 48 | 49 | /** 50 | * Method that handles observing the state 51 | * @private 52 | */ 53 | private _observeState() { 54 | // Set the main authState 55 | this.authState$ = this.afAuth.authState 56 | // Set the user profile 57 | this.user$ = this.authState$.pipe( 58 | switchMap(user => { 59 | if (user) { 60 | this._isLoggedIn = true 61 | return this._getDocRef(user.uid).valueChanges() 62 | } else { 63 | this._isLoggedIn = false 64 | return of(null) 65 | } 66 | }) 67 | ) 68 | } 69 | 70 | /** 71 | * Helper to get a reference to the AngularFirestoreDocument 72 | * @param id 73 | * @returns {AngularFirestoreDocument<>} 74 | * @private 75 | */ 76 | private _getDocRef(id): AngularFirestoreDocument { 77 | return this.db.doc(`${this.collection}/${id}`) 78 | } 79 | 80 | /** 81 | * Method that updates the User profile 82 | * @param user 83 | * @returns {Promise} 84 | * @private 85 | */ 86 | private _updateProfile(user) { 87 | return this._getDocRef(user.uid).set(defaultProfile(user), { merge: true }) 88 | } 89 | 90 | /** 91 | * Perform the actual login with the given provider 92 | * @param provider 93 | * @returns {Promise} 94 | * @private 95 | */ 96 | private _login(provider) { 97 | return this.afAuth.auth.signInWithPopup(provider) 98 | } 99 | 100 | /** 101 | * Perform the login with the Github provider 102 | * @returns {Promise} 103 | */ 104 | public loginGithub() { 105 | return this._login(new firebase.auth.GithubAuthProvider()).then(credentials => 106 | this._updateProfile(credentials.user) 107 | ) 108 | } 109 | 110 | /** 111 | * Perform the login with the Google provider 112 | * @returns {Promise} 113 | */ 114 | public loginGoogle() { 115 | return this._login(new firebase.auth.GoogleAuthProvider()).then(credentials => 116 | this._updateProfile(credentials.user) 117 | ) 118 | } 119 | 120 | public get providers() { 121 | return [ 122 | { 123 | id: 'google', 124 | name: 'Google', 125 | }, 126 | ] 127 | } 128 | 129 | /** 130 | * Public login method 131 | * @param {"google"} provider 132 | * @returns {any} 133 | */ 134 | public login(provider: 'google' | 'github' = 'google') { 135 | switch (provider) { 136 | case 'github': 137 | return fromPromise(this.loginGithub()) 138 | case 'google': 139 | return fromPromise(this.loginGoogle()) 140 | default: 141 | console.log('Unknown provider', provider) 142 | return of(null) 143 | } 144 | } 145 | 146 | /** 147 | * Public login method 148 | * @returns {Observable} 149 | */ 150 | public logout() { 151 | return fromPromise(this.afAuth.auth.signOut()) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/app/crud/collections/beer.model.ts: -------------------------------------------------------------------------------- 1 | export const BeerModel = { 2 | icon: 'fa fa-fw fa-beer', 3 | id: 'Beers', 4 | name: 'Beers', 5 | properties: { 6 | brewery: { 7 | id: 'brewery', 8 | type: 'string', 9 | visible: 'true', 10 | }, 11 | id: { 12 | required: true, 13 | type: 'string', 14 | }, 15 | name: { 16 | id: 'name', 17 | type: 'string', 18 | visible: 'true', 19 | }, 20 | percentage: { 21 | id: 'percentage', 22 | type: 'string', 23 | }, 24 | type: { 25 | id: 'type', 26 | type: 'string', 27 | }, 28 | }, 29 | fields: [ 30 | { 31 | label: 'Brewery', 32 | key: 'brewery', 33 | type: 'string', 34 | link: true, 35 | }, 36 | { 37 | label: 'Name', 38 | key: 'name', 39 | type: 'string', 40 | }, 41 | { 42 | label: 'Percentage', 43 | key: 'percentage', 44 | type: 'string', 45 | headerClass: 'w-25 text-right', 46 | columnClass: 'text-right', 47 | }, 48 | ], 49 | } 50 | -------------------------------------------------------------------------------- /src/app/crud/collections/note.model.ts: -------------------------------------------------------------------------------- 1 | export const NoteModel = { 2 | icon: 'fa fa-fw fa-pencil-square-o', 3 | id: 'Notes', 4 | name: 'Notes', 5 | properties: { 6 | id: { 7 | required: true, 8 | type: 'string', 9 | }, 10 | title: { 11 | id: 'title', 12 | type: 'string', 13 | }, 14 | content: { 15 | id: 'content', 16 | type: 'string', 17 | }, 18 | }, 19 | fields: [ 20 | { 21 | key: 'title', 22 | type: 'string', 23 | link: true, 24 | }, 25 | { 26 | key: 'created', 27 | type: 'date', 28 | headerClass: 'w-25 text-right', 29 | columnClass: 'text-right', 30 | }, 31 | ], 32 | } 33 | -------------------------------------------------------------------------------- /src/app/crud/components/item-field/item-field.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core' 2 | 3 | @Component({ 4 | selector: 'app-item-field', 5 | template: ` 6 | 7 | {{ item[field.key] }} 8 | 9 | 10 | 11 | {{ item[field.key] }} 12 | 13 | 14 | {{ item[field.key].toDate() | date: 'short' }} 15 | 16 | 17 | `, 18 | }) 19 | export class ItemFieldComponent { 20 | @Input() public field 21 | @Input() public first 22 | @Input() public item 23 | @Output() public action = new EventEmitter() 24 | click(e, payload) { 25 | e.preventDefault() 26 | this.action.emit({ type: 'EDIT', payload }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/crud/components/item-form/item-form.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core' 2 | 3 | @Component({ 4 | selector: 'app-item-form', 5 | template: ` 6 |
7 | 18 | 29 | 49 |
{{ item | json }}
50 |
51 | `, 52 | changeDetection: ChangeDetectionStrategy.OnPush, 53 | }) 54 | export class ItemFormComponent implements OnInit { 55 | private initialItem 56 | @Input() public item: any 57 | @Input() public user: any 58 | @Input() public properties: any[] 59 | @Input() public editMode: boolean 60 | @Output() public action = new EventEmitter() 61 | public inspect = false 62 | ngOnInit() { 63 | this.initialItem = Object.assign({}, this.item) 64 | this.editMode = this.editMode || !!this.item.id 65 | } 66 | 67 | public cancel() { 68 | this.action.emit({ type: 'CANCEL' }) 69 | } 70 | 71 | public save(close = false) { 72 | if (!this.canEdit(this.item)) { 73 | alert('Sorry, you are not allowed to edit this item!') 74 | } 75 | const type = close ? 'SAVE_CLOSE' : 'SAVE' 76 | this.action.emit({ type, payload: this.item }) 77 | if (!this.editMode) { 78 | this.item = Object.assign({}, this.initialItem) 79 | } 80 | } 81 | 82 | public toggleInspect(e) { 83 | e.preventDefault() 84 | this.inspect = !this.inspect 85 | } 86 | 87 | canEdit(item) { 88 | if (!item || !this.user) { 89 | return false 90 | } 91 | const isNew = typeof item.id === 'undefined' 92 | const isAdmin = this.user['admin'] 93 | const isOwner = item.user && item.user.id === this.user.id 94 | 95 | return isNew || isAdmin || isOwner 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/app/crud/components/item-list/item-list.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core' 2 | import { Collection } from '../../data.typings' 3 | 4 | @Component({ 5 | selector: 'app-item-list', 6 | template: ` 7 |
8 |
9 |

10 | 11 | {{ collection.name }} 12 | 15 |

16 |
17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 30 | 36 | 46 | 47 |
21 | {{ field.label || field.key }} 22 |
27 | 28 | 29 | 31 | 34 | 35 | 37 | 40 | 41 | 44 | 45 |
48 |
49 | `, 50 | changeDetection: ChangeDetectionStrategy.OnPush, 51 | }) 52 | export class ItemListComponent { 53 | @Input() collection: Collection 54 | @Input() items 55 | @Input() user: any 56 | @Output() action = new EventEmitter() 57 | 58 | delete(id) { 59 | if (window.confirm('Are you sure?')) { 60 | this.action.emit({ type: 'DELETE', payload: id }) 61 | } 62 | } 63 | 64 | inspect(item) { 65 | this.action.emit({ type: 'INSPECT', payload: item }) 66 | } 67 | 68 | add() { 69 | this.action.emit({ type: 'ADD' }) 70 | } 71 | canDelete(item) { 72 | if (!item || !this.user) { 73 | return false 74 | } 75 | const isAdmin = this.user['admin'] 76 | const isOwner = item.user && item.user.id === this.user.id 77 | 78 | return isAdmin || isOwner 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/app/crud/components/item-modal/item-modal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core' 2 | import { BsModalRef } from 'ngx-bootstrap' 3 | 4 | @Component({ 5 | selector: 'app-item-modal', 6 | template: ` 7 |
8 |
9 |

Inspect item

10 |
{{ item | json }}
11 |
12 |
13 | 19 | `, 20 | }) 21 | export class ItemModalComponent { 22 | @Input() type: 'inspect' | 'form' = 'form' 23 | @Input() public item 24 | @Input() public user 25 | @Input() public properties 26 | @Output() public action = new EventEmitter() 27 | 28 | constructor(public bsModalRef: BsModalRef) {} 29 | 30 | handleAction({ type, payload }) { 31 | switch (type) { 32 | case 'SAVE': 33 | return this.action.emit({ type: 'SAVE', payload }) 34 | case 'SAVE_CLOSE': 35 | return this.action.emit({ type: 'SAVE_CLOSE', payload }) 36 | case 'CANCEL': 37 | return this.action.emit({ type: 'CANCEL', payload }) 38 | default: 39 | this.action.emit({ type, payload }) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/app/crud/containers/items-index/items-index.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core' 2 | import { ActivatedRoute } from '@angular/router' 3 | import { BsModalRef, BsModalService } from 'ngx-bootstrap' 4 | import { AuthService } from '../../../auth/services/auth.service' 5 | import { ItemModalComponent } from '../../components/item-modal/item-modal.component' 6 | import { Collection } from '../../data.typings' 7 | import { DataService } from '../../services' 8 | import 'rxjs/add/operator/take' 9 | 10 | export const getProperties = o => 11 | Object.keys(o || {}) 12 | .map(id => Object.assign({}, o[id], { id })) 13 | .filter(prop => prop.id !== 'id') 14 | 15 | @Component({ 16 | selector: 'app-notes-index', 17 | template: ` 18 | 19 |
20 |
21 |
22 | 26 | 27 |
28 |
29 |
30 |
31 |

Crud Models

32 |

33 | The UI of the CRUD models is dynamically generated using a Model 34 | definition. 35 |

36 |

37 | Using an Angular resolver we pass the Model information to the 38 | components. Each model is backed by a Firebase collection. 39 |

40 |

41 | This allows you to quickly create CRUD interfaces for existing 42 | data by adding more model definitions! 43 |

44 |
45 |
{{ collection | json }}
46 |
47 |
48 |
49 |
50 |
51 | `, 52 | }) 53 | export class ItemsIndexComponent implements OnInit { 54 | private modalRef: BsModalRef 55 | public collection: Collection 56 | public properties: any[] 57 | public items$ 58 | public user 59 | 60 | constructor( 61 | private auth: AuthService, 62 | private route: ActivatedRoute, 63 | private data: DataService, 64 | private modal: BsModalService 65 | ) {} 66 | 67 | ngOnInit() { 68 | this.route.data 69 | .map(res => this.parseCollection(res['collection'])) 70 | .map(res => res && res.id) 71 | .subscribe(id => (this.items$ = this.data.getItems(id))) 72 | this.auth.user$.subscribe(user => (this.user = user)) 73 | } 74 | 75 | parseCollection(collection) { 76 | this.collection = collection 77 | this.properties = getProperties(collection.properties || {}) 78 | return collection 79 | } 80 | 81 | hideModal() { 82 | this.modalRef.hide() 83 | } 84 | 85 | showModal(type, item) { 86 | const initialState = { 87 | item: Object.assign({}, item), 88 | properties: this.properties, 89 | user: this.user, 90 | type, 91 | } 92 | this.modalRef = this.modal.show(ItemModalComponent, { initialState }) 93 | this.modalRef.content.action.subscribe(event => this.handleAction(event)) 94 | } 95 | 96 | handleAction({ type, payload }) { 97 | switch (type) { 98 | case 'DELETE': 99 | return this.data.deleteItem(this.collection.id, payload) 100 | case 'CANCEL': 101 | return this.hideModal() 102 | case 'INSPECT': 103 | return this.showModal('inspect', payload) 104 | case 'EDIT': 105 | return this.showModal('form', payload) 106 | case 'ADD': 107 | return this.showModal('form', { user: this.user }) 108 | case 'SAVE_CLOSE': 109 | case 'SAVE': 110 | return this.data 111 | .upsertItem(this.collection.id, payload) 112 | .subscribe(() => type === 'SAVE_CLOSE' && this.hideModal(), err => console.log('err', err)) 113 | default: 114 | console.log(`Unknown type: ${type}. Payload: `, payload) 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/app/crud/crud.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core' 2 | import { RouterModule, Routes } from '@angular/router' 3 | import { SharedModule } from '@tabler/angular-core' 4 | import { ModalModule } from 'ngx-bootstrap' 5 | 6 | import { DataService, FirebaseDataService } from './services' 7 | 8 | import { CollectionResolver } from './resolvers/collection.resolver' 9 | 10 | import { ItemsIndexComponent } from './containers/items-index/items-index.component' 11 | import { ItemListComponent } from './components/item-list/item-list.component' 12 | import { ItemFieldComponent } from './components/item-field/item-field.component' 13 | import { ItemModalComponent } from './components/item-modal/item-modal.component' 14 | import { ItemFormComponent } from './components/item-form/item-form.component' 15 | 16 | const routes: Routes = [ 17 | { 18 | path: ':collectionId', 19 | resolve: { collection: CollectionResolver }, 20 | data: { service: FirebaseDataService }, 21 | children: [{ path: '', component: ItemsIndexComponent }], 22 | }, 23 | ] 24 | 25 | @NgModule({ 26 | imports: [SharedModule, RouterModule.forChild(routes), ModalModule.forRoot()], 27 | declarations: [ItemsIndexComponent, ItemFieldComponent, ItemListComponent, ItemModalComponent, ItemFormComponent], 28 | entryComponents: [ItemModalComponent], 29 | providers: [DataService, FirebaseDataService, CollectionResolver], 30 | }) 31 | export class CrudModule { 32 | constructor(data: DataService, firebase: FirebaseDataService) { 33 | data.service = firebase 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/crud/data.typings.ts: -------------------------------------------------------------------------------- 1 | export interface Collection { 2 | id: string 3 | name?: string 4 | icon?: any 5 | properties?: any 6 | fields?: any 7 | } 8 | 9 | export interface Item { 10 | id: string 11 | [key: string]: any 12 | } 13 | -------------------------------------------------------------------------------- /src/app/crud/index.ts: -------------------------------------------------------------------------------- 1 | export { CrudModule } from './crud.module' 2 | export * from './data.typings' 3 | export * from './services' 4 | -------------------------------------------------------------------------------- /src/app/crud/resolvers/collection.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | import { ActivatedRouteSnapshot, Resolve } from '@angular/router' 3 | import { of } from 'rxjs/observable/of' 4 | import 'rxjs/add/operator/map' 5 | 6 | import { BeerModel } from '../collections/beer.model' 7 | import { NoteModel } from '../collections/note.model' 8 | 9 | const collections = { 10 | beers: BeerModel, 11 | notes: NoteModel, 12 | } 13 | 14 | @Injectable() 15 | export class CollectionResolver implements Resolve { 16 | resolve(route: ActivatedRouteSnapshot) { 17 | let { collectionId } = route.params 18 | if (!collectionId || !collections[collectionId]) { 19 | collectionId = 'beers' 20 | } 21 | return of(collections[collectionId]) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/crud/services/abstract-data.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | import { Observable } from 'rxjs/Observable' 3 | 4 | import { Item } from '../data.typings' 5 | 6 | @Injectable() 7 | export abstract class AbstractDataService { 8 | abstract getItems(collectionId: string): Observable 9 | 10 | abstract addItem(collectionId: string, item: Item): Observable 11 | 12 | abstract updateItem(collectionId: string, item: Item): Observable 13 | 14 | abstract upsertItem(collectionId: string, item: Item): Observable 15 | 16 | abstract deleteItem(collectionId: string, id: string): Observable 17 | } 18 | -------------------------------------------------------------------------------- /src/app/crud/services/data.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | import { Observable } from 'rxjs/Observable' 3 | import { Item } from '../data.typings' 4 | 5 | import { AbstractDataService } from './abstract-data.service' 6 | 7 | @Injectable() 8 | export class DataService { 9 | private _service: AbstractDataService 10 | 11 | public set service(service) { 12 | this._service = service 13 | } 14 | 15 | public get service() { 16 | return this._service 17 | } 18 | 19 | public upsertItem(collectionId: string, item: Item): Observable { 20 | return this.service.upsertItem(collectionId, item) 21 | } 22 | 23 | public deleteItem(collectionId: string, id: string): Observable { 24 | return this.service.deleteItem(collectionId, id) 25 | } 26 | 27 | public getItems(collectionId: string): Observable { 28 | return this.service.getItems(collectionId) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/crud/services/firebase-data.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | import { AngularFirestore } from 'angularfire2/firestore' 3 | import { Observable } from 'rxjs' 4 | 5 | import { Item } from '../data.typings' 6 | import { AbstractDataService } from './abstract-data.service' 7 | import { fromPromise } from 'rxjs/internal/observable/fromPromise' 8 | 9 | @Injectable() 10 | export class FirebaseDataService implements AbstractDataService { 11 | private collectionsRef = (path: string): Observable => { 12 | return this.db.collection(path).valueChanges() 13 | } 14 | 15 | private docRef = (path: string) => { 16 | return this.db.doc(path) 17 | } 18 | 19 | private colDocRef = (collectionId: string, itemId: string) => { 20 | return this.docRef(`${collectionId}/${itemId}`) 21 | } 22 | 23 | constructor(private db: AngularFirestore) {} 24 | 25 | public getItems(collectionId: string): Observable { 26 | return this.collectionsRef(collectionId) 27 | } 28 | 29 | public addItem(collectionId: string, item: Item): Observable { 30 | item.id = item.id || this.db.createId() 31 | item.created = new Date() 32 | return fromPromise( 33 | this.db 34 | .collection(collectionId) 35 | .doc(item.id) 36 | .set(item) 37 | ) 38 | } 39 | 40 | public updateItem(collectionId: string, item: Item): Observable { 41 | item.updated = new Date() 42 | return fromPromise( 43 | this.db 44 | .collection(collectionId) 45 | .doc(item.id) 46 | .update(item) 47 | ) 48 | } 49 | 50 | public upsertItem(collectionId: string, item: Item): Observable { 51 | return item.id ? this.updateItem(collectionId, item) : this.addItem(collectionId, item) 52 | } 53 | 54 | public deleteItem(collectionId: string, id: string): Observable { 55 | return fromPromise( 56 | this.colDocRef(collectionId, id) 57 | .delete() 58 | .then(() => true) 59 | ) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/app/crud/services/index.ts: -------------------------------------------------------------------------------- 1 | export { AbstractDataService } from './abstract-data.service' 2 | export { DataService } from './data.service' 3 | export { FirebaseDataService } from './firebase-data.service' 4 | -------------------------------------------------------------------------------- /src/app/fire/components/message-form/message-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing' 2 | 3 | import { MessageFormComponent } from './message-form.component' 4 | 5 | describe('MessageFormComponent', () => { 6 | let component: MessageFormComponent 7 | let fixture: ComponentFixture 8 | 9 | beforeEach( 10 | async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [MessageFormComponent], 13 | }).compileComponents() 14 | }) 15 | ) 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(MessageFormComponent) 19 | component = fixture.componentInstance 20 | fixture.detectChanges() 21 | }) 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy() 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /src/app/fire/components/message-form/message-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core' 2 | 3 | @Component({ 4 | selector: 'app-message-form', 5 | template: ` 6 |
7 |
8 |
9 | 12 |
13 | 18 |
19 | 24 |
25 |
26 |
27 | `, 28 | styles: [], 29 | }) 30 | export class MessageFormComponent { 31 | @Input() message = '' 32 | @Input() user = '' 33 | @Output() action = new EventEmitter() 34 | 35 | sendMessage() { 36 | const payload = { message: this.message, user: this.user } 37 | this.action.emit({ type: 'ADD', payload }) 38 | this.message = '' 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/fire/components/message-list/message-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing' 2 | 3 | import { MessageListComponent } from './message-list.component' 4 | 5 | describe('MessageListComponent', () => { 6 | let component: MessageListComponent 7 | let fixture: ComponentFixture 8 | 9 | beforeEach( 10 | async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [MessageListComponent], 13 | }).compileComponents() 14 | }) 15 | ) 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(MessageListComponent) 19 | component = fixture.componentInstance 20 | fixture.detectChanges() 21 | }) 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy() 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /src/app/fire/components/message-list/message-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' 2 | 3 | @Component({ 4 | selector: 'app-message-list', 5 | template: ` 6 |
7 | 8 |
9 |
No messages found...
10 |
11 | 12 |
14 | 15 | 16 | 17 |
18 |
19 | `, 20 | styles: [], 21 | }) 22 | export class MessageListComponent { 23 | @Input() public messages = [] 24 | @Input() public user 25 | @Output() public action = new EventEmitter() 26 | 27 | handleAction($event) { 28 | console.log($event) 29 | this.action.emit($event) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/app/fire/components/message/message.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing' 2 | 3 | import { MessageComponent } from './message.component' 4 | 5 | describe('MessageComponent', () => { 6 | let component: MessageComponent 7 | let fixture: ComponentFixture 8 | 9 | beforeEach( 10 | async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [MessageComponent], 13 | }).compileComponents() 14 | }) 15 | ) 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(MessageComponent) 19 | component = fixture.componentInstance 20 | fixture.detectChanges() 21 | }) 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy() 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /src/app/fire/components/message/message.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core' 2 | 3 | @Component({ 4 | selector: 'app-message', 5 | template: ` 6 |
7 |
8 | 10 | 11 | 13 | 14 |
15 |
16 |
17 | {{message.user && message.user.name || 'Anonymous' }} 18 | 20 | Admin 21 | 22 |
23 |
24 | {{message.message}} 25 |
26 |
27 |
28 |
29 | 30 | {{ message.created.toDate() | date: 'short' }} 31 | 32 | 35 | 36 | 37 |
38 |
39 |
40 | `, 41 | styles: [], 42 | }) 43 | export class MessageComponent { 44 | @Input() public message 45 | @Input() public user 46 | @Output() action = new EventEmitter() 47 | 48 | deleteMessage(id) { 49 | if (window.confirm('Are you sure?')) { 50 | this.action.emit({ type: 'DELETE', payload: id }) 51 | } 52 | } 53 | 54 | showDelete() { 55 | return (this.user && this.user.admin) || (this.user && this.message && this.message.user.id === this.user.id) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/app/fire/containers/fire-index/fire-index.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing' 2 | 3 | import { FireIndexComponent } from './fire-index.component' 4 | 5 | describe('FireIndexComponent', () => { 6 | let component: FireIndexComponent 7 | let fixture: ComponentFixture 8 | 9 | beforeEach( 10 | async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [FireIndexComponent], 13 | }).compileComponents() 14 | }) 15 | ) 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(FireIndexComponent) 19 | component = fixture.componentInstance 20 | fixture.detectChanges() 21 | }) 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy() 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /src/app/fire/containers/fire-index/fire-index.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core' 2 | 3 | @Component({ 4 | selector: 'app-fire-index', 5 | template: ` 6 | 7 |
8 |
9 | 10 |
11 |
12 |
13 | `, 14 | }) 15 | export class FireIndexComponent {} 16 | -------------------------------------------------------------------------------- /src/app/fire/containers/fire-sidebar/fire-sidebar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing' 2 | 3 | import { FireSidebarComponent } from './fire-sidebar.component' 4 | 5 | describe('FireSidebarComponent', () => { 6 | let component: FireSidebarComponent 7 | let fixture: ComponentFixture 8 | 9 | beforeEach( 10 | async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [FireSidebarComponent], 13 | }).compileComponents() 14 | }) 15 | ) 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(FireSidebarComponent) 19 | component = fixture.componentInstance 20 | fixture.detectChanges() 21 | }) 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy() 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /src/app/fire/containers/fire-sidebar/fire-sidebar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core' 2 | 3 | @Component({ 4 | selector: 'app-fire-sidebar', 5 | template: ` 6 | 16 | `, 17 | styles: [], 18 | }) 19 | export class FireSidebarComponent implements OnInit { 20 | public items = [ 21 | { 22 | url: 'guestbook', 23 | title: 'Guestbook', 24 | icon: 'fe fe-book', 25 | }, 26 | ] 27 | 28 | constructor() {} 29 | 30 | ngOnInit() {} 31 | } 32 | -------------------------------------------------------------------------------- /src/app/fire/containers/guestbook/guestbook.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing' 2 | 3 | import { GuestbookComponent } from './guestbook.component' 4 | 5 | describe('GuestbookComponent', () => { 6 | let component: GuestbookComponent 7 | let fixture: ComponentFixture 8 | 9 | beforeEach( 10 | async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [GuestbookComponent], 13 | }).compileComponents() 14 | }) 15 | ) 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(GuestbookComponent) 19 | component = fixture.componentInstance 20 | fixture.detectChanges() 21 | }) 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy() 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /src/app/fire/containers/guestbook/guestbook.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core' 2 | import { AuthService } from '../../../auth/services/auth.service' 3 | import { FireService } from '../../services/fire.service' 4 | 5 | @Component({ 6 | selector: 'app-guestbook', 7 | template: ` 8 | 12 | 13 | 14 | 18 | 19 | 20 |
21 | 22 | {{error}} 23 | 24 |
25 | 26 | 30 | 31 | `, 32 | }) 33 | export class GuestbookComponent { 34 | public error = null 35 | public message = '' 36 | 37 | constructor(public auth: AuthService, public fire: FireService) {} 38 | 39 | addMessage({ message, user }) { 40 | this.error = null 41 | if (message === '') { 42 | this.error = 'Please write a message first!' 43 | return 44 | } 45 | this.fire.upsert({ message, user }).subscribe(() => console.log('Item added')) 46 | } 47 | 48 | deleteMessage(id) { 49 | this.fire.delete(id).subscribe(() => console.log('Item deleted')) 50 | } 51 | 52 | handleAction({ type, payload }) { 53 | switch (type) { 54 | case 'ADD': 55 | return this.addMessage(payload) 56 | case 'DELETE': 57 | return this.deleteMessage(payload) 58 | default: 59 | return console.log('Unknown type', type, payload) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/app/fire/fire.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core' 2 | import { FormsModule, ReactiveFormsModule } from '@angular/forms' 3 | import { RouterModule, Routes } from '@angular/router' 4 | import { SharedModule } from '@tabler/angular-core' 5 | import { GuestbookComponent } from './containers/guestbook/guestbook.component' 6 | import { FireIndexComponent } from './containers/fire-index/fire-index.component' 7 | import { FireSidebarComponent } from './containers/fire-sidebar/fire-sidebar.component' 8 | import { FireService } from './services/fire.service' 9 | import { MessageListComponent } from './components/message-list/message-list.component' 10 | import { MessageFormComponent } from './components/message-form/message-form.component' 11 | import { MessageComponent } from './components/message/message.component' 12 | 13 | const routes: Routes = [ 14 | { 15 | path: '', 16 | component: FireIndexComponent, 17 | children: [ 18 | { path: '', component: GuestbookComponent }, 19 | ], 20 | }, 21 | ] 22 | 23 | @NgModule({ 24 | imports: [SharedModule, ReactiveFormsModule, FormsModule, RouterModule.forChild(routes)], 25 | declarations: [ 26 | GuestbookComponent, 27 | FireIndexComponent, 28 | FireSidebarComponent, 29 | MessageListComponent, 30 | MessageFormComponent, 31 | MessageComponent, 32 | ], 33 | providers: [FireService], 34 | }) 35 | export class FireModule {} 36 | -------------------------------------------------------------------------------- /src/app/fire/services/fire.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing' 2 | 3 | import { FireService } from './fire.service' 4 | 5 | describe('FireService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [FireService], 9 | }) 10 | }) 11 | 12 | it( 13 | 'should be created', 14 | inject([FireService], (service: FireService) => { 15 | expect(service).toBeTruthy() 16 | }) 17 | ) 18 | }) 19 | -------------------------------------------------------------------------------- /src/app/fire/services/fire.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | import { AngularFirestore } from 'angularfire2/firestore' 3 | import { fromPromise } from 'rxjs/internal/observable/fromPromise' 4 | 5 | @Injectable() 6 | export class FireService { 7 | private collection = 'Items' 8 | 9 | public collection$ 10 | 11 | constructor(private db: AngularFirestore) { 12 | this.collection$ = db.collection(this.collection, ref => ref.orderBy('created', 'desc')).valueChanges() 13 | } 14 | 15 | public create(data) { 16 | const id = this.db.createId() 17 | const created = new Date() 18 | return fromPromise( 19 | this.db 20 | .collection(this.collection) 21 | .doc(id) 22 | .set(Object.assign({}, data, { id, created })) 23 | ) 24 | } 25 | 26 | public update(id, data) { 27 | const updated = new Date() 28 | return fromPromise( 29 | this.db 30 | .collection(this.collection) 31 | .doc(id) 32 | .update(Object.assign({}, data, { updated })) 33 | ) 34 | } 35 | 36 | public delete(id) { 37 | return fromPromise( 38 | this.db 39 | .collection(this.collection) 40 | .doc(id) 41 | .delete() 42 | ) 43 | } 44 | 45 | public upsert(data) { 46 | return data.id ? this.update(data.id, data) : this.create(data) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/assets/tabler.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tabler 5 | Created with Sketch. 6 | 7 | 8 | 9 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /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 | # For IE 9-11 support, please uncomment the last line of the file and adjust as needed 5 | > 0.5% 6 | last 2 versions 7 | Firefox ESR 8 | not dead 9 | # IE 9-11 -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | firebase: { 4 | apiKey: 'AIzaSyD0emp0zI_qw2CCxo6aY2MzTVvp7GbvAGg', 5 | authDomain: 'tabler-angular-fire.firebaseapp.com', 6 | databaseURL: 'https://tabler-angular-fire.firebaseio.com', 7 | projectId: 'tabler-angular-fire', 8 | storageBucket: '', 9 | messagingSenderId: '856023319444', 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /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 | firebase: { 8 | apiKey: 'AIzaSyD0emp0zI_qw2CCxo6aY2MzTVvp7GbvAGg', 9 | authDomain: 'tabler-angular-fire.firebaseapp.com', 10 | databaseURL: 'https://tabler-angular-fire.firebaseio.com', 11 | projectId: 'tabler-angular-fire', 12 | storageBucket: '', 13 | messagingSenderId: '856023319444', 14 | }, 15 | }; 16 | 17 | /* 18 | * In development mode, to ignore zone related error stack frames such as 19 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can 20 | * import the following file, but please comment it out in production mode 21 | * because it will have performance impact when throw error 22 | */ 23 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 24 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabler/tabler-angular-firebase/e120b4f0077158263d4b41e866326feded8ae164/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TablerAngularFirebase 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /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'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following for the Reflect API. */ 41 | // import 'core-js/es6/reflect'; 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 46 | import 'core-js/es7/reflect'; 47 | 48 | 49 | /** 50 | * Required to support Web Animations `@angular/platform-browser/animations`. 51 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation 52 | **/ 53 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 54 | 55 | /** 56 | * By default, zone.js will patch all possible macroTask and DomEvents 57 | * user can disable parts of macroTask/DomEvents patch by setting following flags 58 | */ 59 | 60 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 61 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 62 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 63 | 64 | /* 65 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 66 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 67 | */ 68 | // (window as any).__Zone_enable_cross_context_check = true; 69 | 70 | /*************************************************************************************************** 71 | * Zone JS is required by default for Angular itself. 72 | */ 73 | import 'zone.js/dist/zone'; // Included with Angular CLI. 74 | 75 | 76 | 77 | /*************************************************************************************************** 78 | * APPLICATION IMPORTS 79 | */ 80 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | $tabler-base: "~@tabler/angular-styles" !default; 3 | @import "~@tabler/angular-styles/scss/tabler.scss"; 4 | .fe { 5 | display: inline-block; 6 | } 7 | -------------------------------------------------------------------------------- /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 | "module": "es2015", 6 | "types": [] 7 | }, 8 | "exclude": [ 9 | "src/test.ts", 10 | "**/*.spec.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "module": "commonjs", 6 | "types": [ 7 | "jasmine", 8 | "node" 9 | ] 10 | }, 11 | "files": [ 12 | "test.ts", 13 | "polyfills.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/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 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2017", 17 | "esnext.asynciterable", 18 | "dom" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-shadowed-variable": true, 69 | "no-string-literal": false, 70 | "no-string-throw": true, 71 | "no-switch-case-fall-through": true, 72 | "no-trailing-whitespace": true, 73 | "no-unnecessary-initializer": true, 74 | "no-unused-expression": true, 75 | "no-use-before-declare": true, 76 | "no-var-keyword": true, 77 | "object-literal-sort-keys": false, 78 | "one-line": [ 79 | true, 80 | "check-open-brace", 81 | "check-catch", 82 | "check-else", 83 | "check-whitespace" 84 | ], 85 | "prefer-const": true, 86 | "quotemark": [ 87 | true, 88 | "single" 89 | ], 90 | "radix": true, 91 | "semicolon": [ 92 | true, 93 | "never" 94 | ], 95 | "triple-equals": [ 96 | true, 97 | "allow-null-check" 98 | ], 99 | "typedef-whitespace": [ 100 | true, 101 | { 102 | "call-signature": "nospace", 103 | "index-signature": "nospace", 104 | "parameter": "nospace", 105 | "property-declaration": "nospace", 106 | "variable-declaration": "nospace" 107 | } 108 | ], 109 | "unified-signatures": true, 110 | "variable-name": false, 111 | "whitespace": [ 112 | true, 113 | "check-branch", 114 | "check-decl", 115 | "check-operator", 116 | "check-separator", 117 | "check-type" 118 | ], 119 | "no-output-on-prefix": true, 120 | "use-input-property-decorator": true, 121 | "use-output-property-decorator": true, 122 | "use-host-property-decorator": true, 123 | "no-input-rename": true, 124 | "no-output-rename": true, 125 | "use-life-cycle-interface": true, 126 | "use-pipe-transform-interface": true, 127 | "component-class-suffix": true, 128 | "directive-class-suffix": true 129 | } 130 | } 131 | --------------------------------------------------------------------------------