├── .editorconfig ├── .gitignore ├── README.md ├── angular-cli.json ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.json ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── src ├── app │ ├── app.component.css │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── home-page │ │ ├── home-page.component.css │ │ ├── home-page.component.html │ │ ├── home-page.component.spec.ts │ │ └── home-page.component.ts │ ├── login-page │ │ ├── login-page.component.css │ │ ├── login-page.component.html │ │ ├── login-page.component.spec.ts │ │ └── login-page.component.ts │ └── registration-page │ │ ├── registration-page.component.css │ │ ├── registration-page.component.html │ │ ├── registration-page.component.spec.ts │ │ └── registration-page.component.ts ├── assets │ └── .gitkeep ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── providers │ └── af.ts ├── styles.css ├── test.ts └── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | 10 | # IDEs and editors 11 | /.idea 12 | .project 13 | .classpath 14 | .c9/ 15 | *.launch 16 | .settings/ 17 | 18 | # IDE - VSCode 19 | .vscode/* 20 | !.vscode/settings.json 21 | !.vscode/tasks.json 22 | !.vscode/launch.json 23 | !.vscode/extensions.json 24 | 25 | # misc 26 | /.sass-cache 27 | /connect.lock 28 | /coverage/* 29 | /libpeerconnection.log 30 | npm-debug.log 31 | testem.log 32 | /typings 33 | 34 | # e2e 35 | /e2e/*.js 36 | /e2e/*.map 37 | 38 | #System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FirebaseCrud 2 | 3 | ## This is the code for a tutorial series that can be found on [progblog.io](https://progblog.io) 4 | 5 | In this tutorial we start with a blank app generated using Angular CLI. We use [Firebase](https://firebase.google.com/) to make the next steps EASY. First, we use Firebase's authentication to add Google Login. We later extend that to also use email/password. We then use the Firebase Realtime Database to make a simple chat app. This project is my attempt to show how easy Firebase makes seemingly arduous tasks. 6 | 7 | ### Sections 8 | 9 | [**Part 1:**](https://progblog.io/Angular-2-Firebase-Tutorial-Part-1-Create-a-Firebase-3-CRUD-app-with-Angular-CLI/) Installing necessary components. Generating a blank app through Angular CLI. Add Bootstrap for styling. Create Firebase account. Install and setup Firebase through NPM. 10 | 11 | The code for **part 1** can be found [here](https://github.com/HuddleHouse/firebase-crud/tree/firebase-crud-part-1). 12 | 13 | [**Part 2:**](https://progblog.io/Angular-2-Firebase-Tutorial-Part-2-Adding-Authentication/) Add user login through their Google account. Create the Login Page and Home Page Components. Add asynchronous monitoring of the logged in status. Redirect to the Home Page when logged in and to the Login Page when the user logs out. 14 | 15 | The code for **part 2** can be found [here](https://github.com/HuddleHouse/firebase-crud/tree/firebase-crud-part-2). 16 | 17 | [**Part 3:**](https://progblog.io/Angular-2-Firebase-Tutorial-Part-3-Adding-the-Realtime-Database/) We will add a simple chat app that stores messages into a Firebase Realtime Database. 18 | 19 | The code for **part 3** can be found [here](https://github.com/HuddleHouse/firebase-crud/tree/firebase-crud-part-3). 20 | 21 | [**Part 4:**](https://progblog.io/Angular-2-Firebase-Tutorial-Part-4-Adding-Email-Password-Authentication/) We add Email/Password user authentication using Firebase. 22 | 23 | The code for **part 4** can be found [here](https://github.com/HuddleHouse/firebase-crud/tree/firebase-crud-part-4). 24 | 25 | **Part 5:** COMING SOON Adding the ability for a user to edit or delete any messages that they have sent. 26 | 27 | ## Development server 28 | 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. 29 | 30 | ## Code scaffolding 31 | 32 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`. 33 | 34 | ## Build 35 | 36 | 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. 37 | 38 | ## Running unit tests 39 | 40 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 41 | 42 | ## Running end-to-end tests 43 | 44 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 45 | Before running the tests make sure you are serving the app via `ng serve`. 46 | 47 | ## Deploying to Github Pages 48 | 49 | Run `ng github-pages:deploy` to deploy to Github Pages. 50 | 51 | ## Further help 52 | 53 | 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). 54 | # firebase-crud 55 | -------------------------------------------------------------------------------- /angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": { 3 | "version": "1.0.0-beta.24", 4 | "name": "firebase-crud" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "test": "test.ts", 17 | "tsconfig": "tsconfig.json", 18 | "prefix": "app", 19 | "mobile": false, 20 | "styles": [ 21 | "styles.css" 22 | ], 23 | "scripts": [], 24 | "environments": { 25 | "source": "environments/environment.ts", 26 | "dev": "environments/environment.ts", 27 | "prod": "environments/environment.prod.ts" 28 | } 29 | } 30 | ], 31 | "addons": [], 32 | "packages": [], 33 | "e2e": { 34 | "protractor": { 35 | "config": "./protractor.conf.js" 36 | } 37 | }, 38 | "test": { 39 | "karma": { 40 | "config": "./karma.conf.js" 41 | } 42 | }, 43 | "defaults": { 44 | "styleExt": "css", 45 | "prefixInterfaces": false, 46 | "inline": { 47 | "style": false, 48 | "template": false 49 | }, 50 | "spec": { 51 | "class": false, 52 | "component": true, 53 | "directive": true, 54 | "module": false, 55 | "pipe": true, 56 | "service": true 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { FirebaseCrudPage } from './app.po'; 2 | 3 | describe('firebase-crud App', function() { 4 | let page: FirebaseCrudPage; 5 | 6 | beforeEach(() => { 7 | page = new FirebaseCrudPage(); 8 | }); 9 | 10 | it('should display message saying app works', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('app works!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | export class FirebaseCrudPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "outDir": "../dist/out-tsc-e2e", 10 | "sourceMap": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "../node_modules/@types" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', 'angular-cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-remap-istanbul'), 12 | require('angular-cli/plugins/karma') 13 | ], 14 | files: [ 15 | { pattern: './src/test.ts', watched: false } 16 | ], 17 | preprocessors: { 18 | './src/test.ts': ['angular-cli'] 19 | }, 20 | mime: { 21 | 'text/x-typescript': ['ts','tsx'] 22 | }, 23 | remapIstanbulReporter: { 24 | reports: { 25 | html: 'coverage', 26 | lcovonly: './coverage/coverage.lcov' 27 | } 28 | }, 29 | angularCli: { 30 | config: './angular-cli.json', 31 | environment: 'dev' 32 | }, 33 | reporters: config.angularCli && config.angularCli.codeCoverage 34 | ? ['progress', 'karma-remap-istanbul'] 35 | : ['progress'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "firebase-crud", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "angular-cli": {}, 6 | "scripts": { 7 | "ng": "ng", 8 | "start": "ng serve", 9 | "lint": "tslint \"src/**/*.ts\"", 10 | "test": "ng test", 11 | "pree2e": "webdriver-manager update --standalone false --gecko false", 12 | "e2e": "protractor" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/common": "^2.3.1", 17 | "@angular/compiler": "^2.3.1", 18 | "@angular/core": "^2.3.1", 19 | "@angular/forms": "^2.3.1", 20 | "@angular/http": "^2.3.1", 21 | "@angular/platform-browser": "^2.3.1", 22 | "@angular/platform-browser-dynamic": "^2.3.1", 23 | "@angular/router": "^3.3.1", 24 | "angularfire2": "^2.0.0-beta.6", 25 | "core-js": "^2.4.1", 26 | "firebase": "^3.6.4", 27 | "rxjs": "^5.0.1", 28 | "ts-helpers": "^1.1.1", 29 | "zone.js": "^0.7.2" 30 | }, 31 | "devDependencies": { 32 | "@angular/compiler-cli": "^2.3.1", 33 | "@types/jasmine": "2.5.38", 34 | "@types/node": "^6.0.42", 35 | "angular-cli": "1.0.0-beta.24", 36 | "codelyzer": "~2.0.0-beta.1", 37 | "jasmine-core": "2.5.2", 38 | "jasmine-spec-reporter": "2.5.0", 39 | "karma": "1.2.0", 40 | "karma-chrome-launcher": "^2.0.0", 41 | "karma-cli": "^1.0.1", 42 | "karma-jasmine": "^1.0.2", 43 | "karma-remap-istanbul": "^0.2.1", 44 | "protractor": "~4.0.13", 45 | "ts-node": "1.2.1", 46 | "tslint": "^4.0.2", 47 | "typescript": "~2.0.3" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | /*global jasmine */ 5 | var SpecReporter = require('jasmine-spec-reporter'); 6 | 7 | exports.config = { 8 | allScriptsTimeout: 11000, 9 | specs: [ 10 | './e2e/**/*.e2e-spec.ts' 11 | ], 12 | capabilities: { 13 | 'browserName': 'chrome' 14 | }, 15 | directConnect: true, 16 | baseUrl: 'http://localhost:4200/', 17 | framework: 'jasmine', 18 | jasmineNodeOpts: { 19 | showColors: true, 20 | defaultTimeoutInterval: 30000, 21 | print: function() {} 22 | }, 23 | useAllAngular2AppRoots: true, 24 | beforeLaunch: function() { 25 | require('ts-node').register({ 26 | project: 'e2e' 27 | }); 28 | }, 29 | onPrepare: function() { 30 | jasmine.getEnv().addReporter(new SpecReporter()); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .border-left { 2 | border-left: solid 1px; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 18 | 19 |
20 | 21 |
22 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { AppComponent } from './app.component'; 5 | 6 | describe('AppComponent', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | declarations: [ 10 | AppComponent 11 | ], 12 | }); 13 | TestBed.compileComponents(); 14 | }); 15 | 16 | it('should create the app', async(() => { 17 | let fixture = TestBed.createComponent(AppComponent); 18 | let app = fixture.debugElement.componentInstance; 19 | expect(app).toBeTruthy(); 20 | })); 21 | 22 | it(`should have as title 'app works!'`, async(() => { 23 | let fixture = TestBed.createComponent(AppComponent); 24 | let app = fixture.debugElement.componentInstance; 25 | expect(app.title).toEqual('app works!'); 26 | })); 27 | 28 | it('should render title in a h1 tag', async(() => { 29 | let fixture = TestBed.createComponent(AppComponent); 30 | fixture.detectChanges(); 31 | let compiled = fixture.debugElement.nativeElement; 32 | expect(compiled.querySelector('h1').textContent).toContain('app works!'); 33 | })); 34 | }); 35 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { AF } from "../providers/af"; 3 | import { Router } from "@angular/router"; 4 | 5 | @Component({ 6 | selector: 'app-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.css'] 9 | }) 10 | export class AppComponent { 11 | public isLoggedIn: boolean; 12 | 13 | constructor(public afService: AF, private router: Router) { 14 | // This asynchronously checks if our user is logged it and will automatically 15 | // redirect them to the Login page when the status changes. 16 | // This is just a small thing that Firebase does that makes it easy to use. 17 | this.afService.af.auth.subscribe( 18 | (auth) => { 19 | if(auth == null) { 20 | console.log("Not Logged in."); 21 | 22 | this.isLoggedIn = false; 23 | this.router.navigate(['login']); 24 | } 25 | else { 26 | console.log("Successfully Logged in."); 27 | // Set the Display Name and Email so we can attribute messages to them 28 | if(auth.google) { 29 | this.afService.displayName = auth.google.displayName; 30 | this.afService.email = auth.google.email; 31 | } 32 | else { 33 | this.afService.displayName = auth.auth.email; 34 | this.afService.email = auth.auth.email; 35 | } 36 | 37 | this.isLoggedIn = true; 38 | this.router.navigate(['']); 39 | } 40 | } 41 | ); 42 | } 43 | 44 | logout() { 45 | this.afService.logout(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { AppComponent } from './app.component'; 4 | import { AngularFireModule } from 'angularfire2'; 5 | import { LoginPageComponent } from './login-page/login-page.component'; 6 | import {RouterModule, Routes} from "@angular/router"; 7 | import {AF} from "../providers/af"; 8 | import { HomePageComponent } from './home-page/home-page.component'; 9 | import {FormsModule} from "@angular/forms"; 10 | import { RegistrationPageComponent } from './registration-page/registration-page.component'; 11 | 12 | export const firebaseConfig = { 13 | apiKey: "*************************", 14 | authDomain: "fir-crud-93710.firebaseapp.com", 15 | databaseURL: "https://fir-crud-93710.firebaseio.com", 16 | storageBucket: "fir-crud-93710.appspot.com", 17 | messagingSenderId: "720204736178" 18 | }; 19 | 20 | const routes: Routes = [ 21 | { path: '', component: HomePageComponent }, 22 | { path: 'login', component: LoginPageComponent }, 23 | { path: 'register', component: RegistrationPageComponent} 24 | ]; 25 | 26 | @NgModule({ 27 | imports: [ 28 | BrowserModule, 29 | AngularFireModule.initializeApp(firebaseConfig), 30 | RouterModule.forRoot(routes), 31 | FormsModule 32 | ], 33 | declarations: [ AppComponent, LoginPageComponent, HomePageComponent, RegistrationPageComponent ], 34 | bootstrap: [ AppComponent ], 35 | providers: [AF] 36 | }) 37 | export class AppModule {} 38 | -------------------------------------------------------------------------------- /src/app/home-page/home-page.component.css: -------------------------------------------------------------------------------- 1 | 2 | .bs-example { 3 | position: relative; 4 | padding: 15px 15px 15px; 5 | margin: 0 -15px 0px; 6 | border-color: #e5e5e5 #eee #eee; 7 | border-style: solid; 8 | border-width: 1px 0; 9 | background-color: #E1E2E3; 10 | box-shadow: inset 0 3px 6px rgba(0,0,0,.05); 11 | height: 60vh; 12 | overflow-y: scroll; 13 | } 14 | #messages { 15 | background-color: #2d384a !important; 16 | } 17 | 18 | .author { 19 | font-size: 12px; 20 | } 21 | .send-message { 22 | color: #ffffff; 23 | background-color: #4184f3; 24 | text-decoration: none; 25 | padding: 10px 20px 10px 20px; 26 | display: inline-flex; 27 | cursor: pointer; 28 | } 29 | 30 | .message-text { 31 | display: block; 32 | padding: 9.5px; 33 | margin: 0 0 10px; 34 | font-size: 13px; 35 | line-height: 1.42857143; 36 | color: #333; 37 | word-break: break-all; 38 | word-wrap: break-word; 39 | background-color: #f5f5f5; 40 | border: 1px solid #ccc; 41 | border-radius: 4px; 42 | width: 100%; 43 | } 44 | 45 | p { 46 | font-size: 16px; 47 | } 48 | 49 | .bubble{ 50 | background-color: #F2F2F2; 51 | border-radius: 5px; 52 | box-shadow: 0 0 6px #B2B2B2; 53 | display: inline-block; 54 | padding: 10px 18px; 55 | position: relative; 56 | vertical-align: top; 57 | } 58 | 59 | .bubble::before { 60 | background-color: #F2F2F2; 61 | content: "\00a0"; 62 | display: block; 63 | height: 16px; 64 | position: absolute; 65 | top: 11px; 66 | transform: rotate( 29deg ) skew( -35deg ); 67 | -moz-transform: rotate( 29deg ) skew( -35deg ); 68 | -ms-transform: rotate( 29deg ) skew( -35deg ); 69 | -o-transform: rotate( 29deg ) skew( -35deg ); 70 | -webkit-transform: rotate( 29deg ) skew( -35deg ); 71 | width: 20px; 72 | } 73 | 74 | .me { 75 | display: inherit; 76 | margin: 5px 45px 5px 20px; 77 | } 78 | 79 | .me::before { 80 | box-shadow: -2px 2px 2px 0 rgba( 178, 178, 178, .4 ); 81 | left: -9px; 82 | } 83 | 84 | .you { 85 | display: inherit; 86 | margin: 5px 20px 5px 45px; 87 | } 88 | 89 | .you::before { 90 | box-shadow: 2px -2px 2px 0 rgba( 178, 178, 178, .4 ); 91 | right: -9px; 92 | } 93 | 94 | .bs-example+.highlight, .bs-example+.zero-clipboard+.highlight { 95 | margin: 0px -15px 15px; 96 | border-width: 0 0 1px; 97 | border-radius: 0; 98 | } 99 | .highlight { 100 | padding: 9px 14px; 101 | margin-bottom: 14px; 102 | background-color: #f7f7f9; 103 | border: 1px solid #e1e1e8; 104 | border-radius: 4px; 105 | } 106 | -------------------------------------------------------------------------------- /src/app/home-page/home-page.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |

{{ message.message }}

8 |
9 | {{ message.displayName }} | {{ message.timestamp | date:"MM/dd/yy hh:mm a" }} 10 |
11 |
12 |
13 | 14 |
15 | 16 |
17 | 18 | SEND 19 |
20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /src/app/home-page/home-page.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { HomePageComponent } from './home-page.component'; 7 | 8 | describe('HomePageComponent', () => { 9 | let component: HomePageComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ HomePageComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(HomePageComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/home-page/home-page.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, AfterViewChecked, ElementRef, ViewChild} from '@angular/core'; 2 | import {AF} from "../../providers/af"; 3 | import {FirebaseListObservable} from "angularfire2"; 4 | 5 | @Component({ 6 | selector: 'app-home-page', 7 | templateUrl: './home-page.component.html', 8 | styleUrls: ['./home-page.component.css'] 9 | }) 10 | export class HomePageComponent implements OnInit, AfterViewChecked { 11 | @ViewChild('scrollMe') private myScrollContainer: ElementRef; 12 | public newMessage: string; 13 | public messages: FirebaseListObservable; 14 | 15 | constructor(public afService: AF) { 16 | this.messages = this.afService.messages; 17 | } 18 | 19 | ngOnInit() {} 20 | 21 | ngAfterViewChecked() { 22 | this.scrollToBottom(); 23 | } 24 | 25 | scrollToBottom(): void { 26 | try { 27 | this.myScrollContainer.nativeElement.scrollTop = this.myScrollContainer.nativeElement.scrollHeight; 28 | } catch(err) { } 29 | } 30 | 31 | sendMessage(){ 32 | this.afService.sendMessage(this.newMessage); 33 | this.newMessage = ''; 34 | } 35 | 36 | isYou(email) { 37 | if(email == this.afService.email) 38 | return true; 39 | else 40 | return false; 41 | } 42 | 43 | isMe(email) { 44 | if(email == this.afService.email) 45 | return false; 46 | else 47 | return true; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/app/login-page/login-page.component.css: -------------------------------------------------------------------------------- 1 | /****** LOGIN MODAL ******/ 2 | .loginmodal-container { 3 | padding: 30px; 4 | max-width: 350px; 5 | width: 100% !important; 6 | background-color: #F7F7F7; 7 | margin: 0 auto; 8 | border-radius: 2px; 9 | box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); 10 | overflow: hidden; 11 | font-family: roboto; 12 | } 13 | 14 | .loginmodal-container h1 { 15 | text-align: center; 16 | font-size: 1.8em; 17 | font-family: roboto; 18 | } 19 | 20 | .loginmodal-submit { 21 | /* border: 1px solid #3079ed; */ 22 | width: 100%; 23 | border: 0px; 24 | color: #fff; 25 | text-shadow: 0 1px rgba(0,0,0,0.1); 26 | background-color: #4d90fe; 27 | padding: 11px 0px; 28 | font-family: roboto; 29 | font-size: 21px; 30 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#4d90fe), to(#4787ed)); 31 | } 32 | 33 | .loginmodal-submit:hover { 34 | /* border: 1px solid #2f5bb7; */ 35 | border: 0px; 36 | text-shadow: 0 1px rgba(0,0,0,0.3); 37 | background-color: #357ae8; 38 | /* background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#4d90fe), to(#357ae8)); */ 39 | } 40 | 41 | .loginmodal-container a { 42 | text-decoration: none; 43 | color: #2e6da4; 44 | font-weight: 400; 45 | text-align: center; 46 | display: inline-block; 47 | opacity: 0.6; 48 | transition: opacity ease 0.5s; 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/app/login-page/login-page.component.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 26 | -------------------------------------------------------------------------------- /src/app/login-page/login-page.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { LoginPageComponent } from './login-page.component'; 7 | 8 | describe('LoginPageComponent', () => { 9 | let component: LoginPageComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ LoginPageComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(LoginPageComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/login-page/login-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {AF} from "../../providers/af"; 3 | import {Router} from "@angular/router"; 4 | 5 | @Component({ 6 | selector: 'app-login-page', 7 | templateUrl: './login-page.component.html', 8 | styleUrls: ['./login-page.component.css'] 9 | }) 10 | export class LoginPageComponent { 11 | public error: any; 12 | 13 | constructor(public afService: AF, private router: Router) {} 14 | 15 | loginWithGoogle() { 16 | this.afService.loginWithGoogle().then((data) => { 17 | // Send them to the homepage if they are logged in 18 | console.log(data); 19 | this.afService.addUserInfo(); 20 | this.router.navigate(['']); 21 | }) 22 | } 23 | 24 | loginWithEmail(event, email, password){ 25 | event.preventDefault(); 26 | this.afService.loginWithEmail(email, password).then(() => { 27 | this.router.navigate(['']); 28 | }) 29 | .catch((error: any) => { 30 | if (error) { 31 | this.error = error; 32 | console.log(this.error); 33 | } 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/registration-page/registration-page.component.css: -------------------------------------------------------------------------------- 1 | /****** LOGIN MODAL ******/ 2 | .registermodal-container { 3 | padding: 30px; 4 | max-width: 350px; 5 | width: 100% !important; 6 | background-color: #F7F7F7; 7 | margin: 0 auto; 8 | border-radius: 2px; 9 | box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); 10 | overflow: hidden; 11 | font-family: roboto; 12 | } 13 | 14 | .registermodal-container h1 { 15 | text-align: center; 16 | font-size: 1.8em; 17 | font-family: roboto; 18 | } 19 | -------------------------------------------------------------------------------- /src/app/registration-page/registration-page.component.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | -------------------------------------------------------------------------------- /src/app/registration-page/registration-page.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { RegistrationPageComponent } from './registration-page.component'; 7 | 8 | describe('RegistrationPageComponent', () => { 9 | let component: RegistrationPageComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ RegistrationPageComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(RegistrationPageComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/registration-page/registration-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {AF} from "../../providers/af"; 3 | import {Router} from "@angular/router"; 4 | 5 | @Component({ 6 | selector: 'app-registration-page', 7 | templateUrl: './registration-page.component.html', 8 | styleUrls: ['./registration-page.component.css'] 9 | }) 10 | export class RegistrationPageComponent { 11 | public error: any; 12 | 13 | constructor(private afService: AF, private router: Router) { } 14 | 15 | register(event, name, email, password) { 16 | event.preventDefault(); 17 | this.afService.registerUser(email, password).then((user) => { 18 | this.afService.saveUserInfoFromForm(user.uid, name, email).then(() => { 19 | this.router.navigate(['']); 20 | }) 21 | .catch((error) => { 22 | this.error = error; 23 | }); 24 | }) 25 | .catch((error) => { 26 | this.error = error; 27 | console.log(this.error); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuddleHouse/firebase-crud/270f5860622bea69e7f5a8bd676439a8149cca5d/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuddleHouse/firebase-crud/270f5860622bea69e7f5a8bd676439a8149cca5d/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FirebaseCrud 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 2 | 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { enableProdMode } from '@angular/core'; 5 | import { environment } from './environments/environment'; 6 | import { AppModule } from './app/app.module'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule); 13 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | // This file includes polyfills needed by Angular 2 and is loaded before 2 | // the app. You can add your own extra polyfills to this file. 3 | import 'core-js/es6/symbol'; 4 | import 'core-js/es6/object'; 5 | import 'core-js/es6/function'; 6 | import 'core-js/es6/parse-int'; 7 | import 'core-js/es6/parse-float'; 8 | import 'core-js/es6/number'; 9 | import 'core-js/es6/math'; 10 | import 'core-js/es6/string'; 11 | import 'core-js/es6/date'; 12 | import 'core-js/es6/array'; 13 | import 'core-js/es6/regexp'; 14 | import 'core-js/es6/map'; 15 | import 'core-js/es6/set'; 16 | import 'core-js/es6/reflect'; 17 | 18 | import 'core-js/es7/reflect'; 19 | import 'zone.js/dist/zone'; 20 | -------------------------------------------------------------------------------- /src/providers/af.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from "@angular/core"; 2 | import {AngularFire, AuthProviders, AuthMethods, FirebaseListObservable, FirebaseObjectObservable} from 'angularfire2'; 3 | import {FirebaseObjectFactoryOpts} from "angularfire2/interfaces"; 4 | 5 | @Injectable() 6 | export class AF { 7 | public messages: FirebaseListObservable; 8 | public users: FirebaseListObservable; 9 | public displayName: string; 10 | public email: string; 11 | public user: FirebaseObjectObservable; 12 | 13 | constructor(public af: AngularFire) { 14 | this.af.auth.subscribe( 15 | (auth) => { 16 | if (auth != null) { 17 | this.user = this.af.database.object('users/' + auth.uid); 18 | } 19 | }); 20 | 21 | this.messages = this.af.database.list('messages'); 22 | this.users = this.af.database.list('users'); 23 | } 24 | 25 | /** 26 | * Logs in the user 27 | * @returns {firebase.Promise} 28 | */ 29 | loginWithGoogle() { 30 | return this.af.auth.login({ 31 | provider: AuthProviders.Google, 32 | method: AuthMethods.Popup, 33 | }); 34 | } 35 | 36 | /** 37 | * Logs out the current user 38 | */ 39 | logout() { 40 | return this.af.auth.logout(); 41 | } 42 | 43 | /** 44 | * 45 | */ 46 | addUserInfo(){ 47 | //We saved their auth info now save the rest to the db. 48 | this.users.push({ 49 | email: this.email, 50 | displayName: this.displayName 51 | }); 52 | } 53 | 54 | /** 55 | * Saves a message to the Firebase Realtime Database 56 | * @param text 57 | */ 58 | sendMessage(text) { 59 | var message = { 60 | message: text, 61 | displayName: this.displayName, 62 | email: this.email, 63 | timestamp: Date.now() 64 | }; 65 | this.messages.push(message); 66 | } 67 | 68 | /** 69 | * 70 | * @param model 71 | * @returns {firebase.Promise} 72 | */ 73 | registerUser(email, password) { 74 | console.log(email) 75 | return this.af.auth.createUser({ 76 | email: email, 77 | password: password 78 | }); 79 | 80 | 81 | } 82 | 83 | /** 84 | * 85 | * @param uid 86 | * @param model 87 | * @returns {firebase.Promise} 88 | */ 89 | saveUserInfoFromForm(uid, name, email) { 90 | return this.af.database.object('registeredUsers/' + uid).set({ 91 | name: name, 92 | email: email, 93 | }); 94 | } 95 | 96 | /** 97 | * Logs the user in using their Email/Password combo 98 | * @param email 99 | * @param password 100 | * @returns {firebase.Promise} 101 | */ 102 | loginWithEmail(email, password) { 103 | return this.af.auth.login({ 104 | email: email, 105 | password: password, 106 | }, 107 | { 108 | provider: AuthProviders.Password, 109 | method: AuthMethods.Password, 110 | }); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare var __karma__: any; 17 | declare var require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | let context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "", 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": ["es6", "dom"], 8 | "mapRoot": "./", 9 | "module": "es6", 10 | "moduleResolution": "node", 11 | "outDir": "../dist/out-tsc", 12 | "sourceMap": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "../node_modules/@types" 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "class-name": true, 7 | "comment-format": [ 8 | true, 9 | "check-space" 10 | ], 11 | "curly": true, 12 | "eofline": true, 13 | "forin": true, 14 | "indent": [ 15 | true, 16 | "spaces" 17 | ], 18 | "label-position": true, 19 | "max-line-length": [ 20 | true, 21 | 140 22 | ], 23 | "member-access": false, 24 | "member-ordering": [ 25 | true, 26 | "static-before-instance", 27 | "variables-before-functions" 28 | ], 29 | "no-arg": true, 30 | "no-bitwise": true, 31 | "no-console": [ 32 | true, 33 | "debug", 34 | "info", 35 | "time", 36 | "timeEnd", 37 | "trace" 38 | ], 39 | "no-construct": true, 40 | "no-debugger": true, 41 | "no-duplicate-variable": true, 42 | "no-empty": false, 43 | "no-eval": true, 44 | "no-inferrable-types": true, 45 | "no-shadowed-variable": true, 46 | "no-string-literal": false, 47 | "no-switch-case-fall-through": true, 48 | "no-trailing-whitespace": true, 49 | "no-unused-expression": true, 50 | "no-use-before-declare": true, 51 | "no-var-keyword": true, 52 | "object-literal-sort-keys": false, 53 | "one-line": [ 54 | true, 55 | "check-open-brace", 56 | "check-catch", 57 | "check-else", 58 | "check-whitespace" 59 | ], 60 | "quotemark": [ 61 | true, 62 | "single" 63 | ], 64 | "radix": true, 65 | "semicolon": [ 66 | "always" 67 | ], 68 | "triple-equals": [ 69 | true, 70 | "allow-null-check" 71 | ], 72 | "typedef-whitespace": [ 73 | true, 74 | { 75 | "call-signature": "nospace", 76 | "index-signature": "nospace", 77 | "parameter": "nospace", 78 | "property-declaration": "nospace", 79 | "variable-declaration": "nospace" 80 | } 81 | ], 82 | "variable-name": false, 83 | "whitespace": [ 84 | true, 85 | "check-branch", 86 | "check-decl", 87 | "check-operator", 88 | "check-separator", 89 | "check-type" 90 | ], 91 | 92 | "directive-selector": [true, "attribute", "app", "camelCase"], 93 | "component-selector": [true, "element", "app", "kebab-case"], 94 | "use-input-property-decorator": true, 95 | "use-output-property-decorator": true, 96 | "use-host-property-decorator": true, 97 | "no-input-rename": true, 98 | "no-output-rename": true, 99 | "use-life-cycle-interface": true, 100 | "use-pipe-transform-interface": true, 101 | "component-class-suffix": true, 102 | "directive-class-suffix": true, 103 | "no-access-missing-member": true, 104 | "templates-use-public": true, 105 | "invoke-injectable": true 106 | } 107 | } 108 | --------------------------------------------------------------------------------