├── .gitignore
├── README.md
├── angular.json
├── browserlist
├── e2e
├── protractor.conf.js
├── src
│ ├── app.e2e-spec.ts
│ └── app.po.ts
└── tsconfig.json
├── firebase.json
├── ionic.config.json
├── karma.conf.js
├── licence.md
├── package-lock.json
├── package.json
├── src
├── app
│ ├── app-routing.module.ts
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── components
│ │ └── auth-form
│ │ │ ├── auth-form.component.html
│ │ │ ├── auth-form.component.scss
│ │ │ ├── auth-form.component.spec.ts
│ │ │ └── auth-form.component.ts
│ ├── home
│ │ ├── home.module.ts
│ │ ├── home.page.html
│ │ ├── home.page.scss
│ │ ├── home.page.spec.ts
│ │ └── home.page.ts
│ ├── models
│ │ └── user.ts
│ ├── pages
│ │ ├── login
│ │ │ ├── login.module.ts
│ │ │ ├── login.page.html
│ │ │ ├── login.page.scss
│ │ │ ├── login.page.spec.ts
│ │ │ └── login.page.ts
│ │ ├── profile
│ │ │ ├── profile.module.ts
│ │ │ ├── profile.page.html
│ │ │ ├── profile.page.scss
│ │ │ ├── profile.page.spec.ts
│ │ │ ├── profile.page.ts
│ │ │ ├── profile.service.spec.ts
│ │ │ ├── profile.service.ts
│ │ │ └── profile.store.ts
│ │ ├── reset-password
│ │ │ ├── reset-password.module.ts
│ │ │ ├── reset-password.page.html
│ │ │ ├── reset-password.page.scss
│ │ │ ├── reset-password.page.spec.ts
│ │ │ └── reset-password.page.ts
│ │ └── signup
│ │ │ ├── signup.module.ts
│ │ │ ├── signup.page.html
│ │ │ ├── signup.page.scss
│ │ │ ├── signup.page.spec.ts
│ │ │ └── signup.page.ts
│ ├── services
│ │ ├── auth.guard.spec.ts
│ │ ├── auth.guard.ts
│ │ ├── auth.service.spec.ts
│ │ └── auth.service.ts
│ └── shared-modules
│ │ └── auth.module.ts
├── assets
│ ├── icon
│ │ └── favicon.png
│ └── shapes.svg
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── global.scss
├── index.html
├── main.ts
├── polyfills.ts
├── test.ts
├── theme
│ └── variables.scss
└── zone-flags.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.spec.json
└── tslint.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Specifies intentionally untracked files to ignore when using Git
2 | # http://git-scm.com/docs/gitignore
3 |
4 | *~
5 | *.sw[mnpcod]
6 | *.log
7 | *.tmp
8 | *.tmp.*
9 | log.txt
10 | *.sublime-project
11 | *.sublime-workspace
12 | .vscode/
13 | npm-debug.log*
14 |
15 | .idea/
16 | .ionic/
17 | .sourcemaps/
18 | .sass-cache/
19 | .tmp/
20 | .versions/
21 | coverage/
22 | www/
23 | node_modules/
24 | tmp/
25 | temp/
26 | platforms/
27 | plugins/
28 | plugins/android.json
29 | plugins/ios.json
30 | $RECYCLE.BIN/
31 |
32 | .DS_Store
33 | Thumbs.db
34 | UserInterfaceState.xcuserstate
35 |
36 | functions/
37 | dist/
38 | # Firebase
39 | .firebase
40 | *-debug.log
41 | .runtimeconfig.json
42 | .angular/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ionic and Firebase Authentication Starter
2 |
3 | This is an authentication starter template built with Ionic and Firebase. It's using Angular as the programming
4 | framework.
5 |
6 | ## Packages
7 |
8 | It's using:
9 |
10 | - `@angular`: "13.3.1"
11 | - `@angular/fire`: "7.3.0"
12 | - `@ionic/angular`: "6.0.14"
13 | - `firebase`: "9.6.10"
14 | - `@ngrx/component-store`: "^13.1.0" (_Right now it's only for the profile page_)
15 |
16 | I'll do my best to update the `README` when I update those packages.
17 |
18 | ## Contents
19 |
20 | This starter templates has:
21 |
22 | - Login Page
23 | - Signup Page
24 | - Reset Password Page
25 | - Profile Page
26 |
27 | It extracts the common login/signup/reset functionality into an authentication component that creates the authentication
28 | form and passes the user credentials to the parent component (_either Login, Signup, or ResetPassword_).
29 |
30 | ## Licence
31 |
32 | The starter is licenced as MIT, you can read the full [licence here](/licence.md).
33 |
34 | ## Payment
35 |
36 | The template is free, but if you want to give me money, you can do it through [this link](https://paypal.me/javebratt).
37 | Contact me if you need an invoice to mark it as a business expense.
38 |
39 | ## Usage
40 |
41 | I have built a tutorial that explains how to use this starter. You can find it in the
42 | [Firebase Authentication](https://courses.jsmobiledev.com/course/authentication/intro) course on my
43 | [main course site](https://courses.jsmobiledev.com/).
44 |
45 | Have ideas on how you'd like this explained? [Shoot me an email](https://jsmobiledev.com/contact)
46 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular-devkit/core/src/workspace/workspace-schema.json",
3 | "version": 1,
4 | "defaultProject": "app",
5 | "newProjectRoot": "projects",
6 | "projects": {
7 | "app": {
8 | "root": "",
9 | "sourceRoot": "src",
10 | "projectType": "application",
11 | "prefix": "app",
12 | "schematics": {},
13 | "architect": {
14 | "build": {
15 | "builder": "@angular-devkit/build-angular:browser",
16 | "options": {
17 | "aot": true,
18 | "outputPath": "www",
19 | "index": "src/index.html",
20 | "main": "src/main.ts",
21 | "polyfills": "src/polyfills.ts",
22 | "tsConfig": "tsconfig.app.json",
23 | "assets": [
24 | {
25 | "glob": "**/*",
26 | "input": "src/assets",
27 | "output": "assets"
28 | },
29 | {
30 | "glob": "**/*.svg",
31 | "input": "node_modules/ionicons/dist/ionicons/svg",
32 | "output": "./svg"
33 | }
34 | ],
35 | "styles": [
36 | {
37 | "input": "src/theme/variables.scss"
38 | },
39 | {
40 | "input": "src/global.scss"
41 | }
42 | ],
43 | "scripts": []
44 | },
45 | "configurations": {
46 | "production": {
47 | "fileReplacements": [
48 | {
49 | "replace": "src/environments/environment.ts",
50 | "with": "src/environments/environment.prod.ts"
51 | }
52 | ],
53 | "optimization": true,
54 | "outputHashing": "all",
55 | "sourceMap": false,
56 | "extractCss": true,
57 | "namedChunks": false,
58 | "aot": true,
59 | "extractLicenses": true,
60 | "vendorChunk": false,
61 | "buildOptimizer": true,
62 | "budgets": [
63 | {
64 | "type": "initial",
65 | "maximumWarning": "2mb",
66 | "maximumError": "5mb"
67 | },
68 | {
69 | "type": "anyComponentStyle",
70 | "maximumWarning": "6kb"
71 | }
72 | ]
73 | },
74 | "ci": {
75 | "budgets": [
76 | {
77 | "type": "anyComponentStyle",
78 | "maximumWarning": "6kb"
79 | }
80 | ],
81 | "progress": false
82 | }
83 | }
84 | },
85 | "serve": {
86 | "builder": "@angular-devkit/build-angular:dev-server",
87 | "options": {
88 | "browserTarget": "app:build"
89 | },
90 | "configurations": {
91 | "production": {
92 | "browserTarget": "app:build:production"
93 | },
94 | "ci": {
95 | "progress": false
96 | }
97 | }
98 | },
99 | "extract-i18n": {
100 | "builder": "@angular-devkit/build-angular:extract-i18n",
101 | "options": {
102 | "browserTarget": "app:build"
103 | }
104 | },
105 | "test": {
106 | "builder": "@angular-devkit/build-angular:karma",
107 | "options": {
108 | "main": "src/test.ts",
109 | "polyfills": "src/polyfills.ts",
110 | "tsConfig": "tsconfig.spec.json",
111 | "karmaConfig": "karma.conf.js",
112 | "styles": [],
113 | "scripts": [],
114 | "assets": [
115 | {
116 | "glob": "favicon.ico",
117 | "input": "src/",
118 | "output": "/"
119 | },
120 | {
121 | "glob": "**/*",
122 | "input": "src/assets",
123 | "output": "/assets"
124 | }
125 | ]
126 | },
127 | "configurations": {
128 | "ci": {
129 | "progress": false,
130 | "watch": false
131 | }
132 | }
133 | },
134 | "lint": {
135 | "builder": "@angular-devkit/build-angular:tslint",
136 | "options": {
137 | "tsConfig": [
138 | "tsconfig.app.json",
139 | "tsconfig.spec.json",
140 | "e2e/tsconfig.json"
141 | ],
142 | "exclude": [
143 | "**/node_modules/**"
144 | ]
145 | }
146 | },
147 | "e2e": {
148 | "builder": "@angular-devkit/build-angular:protractor",
149 | "options": {
150 | "protractorConfig": "e2e/protractor.conf.js",
151 | "devServerTarget": "app:serve"
152 | },
153 | "configurations": {
154 | "production": {
155 | "devServerTarget": "app:serve:production"
156 | },
157 | "ci": {
158 | "devServerTarget": "app:serve:ci"
159 | }
160 | }
161 | },
162 | "ionic-cordova-build": {
163 | "builder": "@ionic/angular-toolkit:cordova-build",
164 | "options": {
165 | "browserTarget": "app:build"
166 | },
167 | "configurations": {
168 | "production": {
169 | "browserTarget": "app:build:production"
170 | }
171 | }
172 | },
173 | "ionic-cordova-serve": {
174 | "builder": "@ionic/angular-toolkit:cordova-serve",
175 | "options": {
176 | "cordovaBuildTarget": "app:ionic-cordova-build",
177 | "devServerTarget": "app:serve"
178 | },
179 | "configurations": {
180 | "production": {
181 | "cordovaBuildTarget": "app:ionic-cordova-build:production",
182 | "devServerTarget": "app:serve:production"
183 | }
184 | }
185 | }
186 | }
187 | }
188 | },
189 | "cli": {
190 | "defaultCollection": "@ionic/angular-toolkit",
191 | "analytics": "e8398fc6-ead3-4b50-ba23-93d8d528024c"
192 | },
193 | "schematics": {
194 | "@ionic/angular-toolkit:component": {
195 | "styleext": "scss"
196 | },
197 | "@ionic/angular-toolkit:page": {
198 | "styleext": "scss"
199 | }
200 | }
201 | }
--------------------------------------------------------------------------------
/browserlist:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # You can see what browsers were selected by your queries by running:
6 | # npx browserslist
7 |
8 | > 0.5%
9 | last 2 versions
10 | Firefox ESR
11 | not dead
12 | not IE 9-11 # For IE 9-11 support, remove 'not'.
13 |
--------------------------------------------------------------------------------
/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.json')
25 | });
26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/e2e/src/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AppPage } from './app.po';
2 |
3 | describe('new App', () => {
4 | let page: AppPage;
5 |
6 | beforeEach(() => {
7 | page = new AppPage();
8 | });
9 |
10 | it('should be blank', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toContain('The world is your oyster.');
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.deepCss('app-root ion-content')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/e2e/tsconfig.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 | }
14 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/ionic.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "AuthStarterAngularFire",
3 | "integrations": {},
4 | "type": "angular"
5 | }
6 |
--------------------------------------------------------------------------------
/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', 'text-summary'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/licence.md:
--------------------------------------------------------------------------------
1 | # Copyright 2019-present Jorge Vergara
2 |
3 | [JAVEBRATT](https://javebratt.com/)
4 |
5 | MIT License
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining
8 | a copy of this software and associated documentation files (the
9 | "Software"), to deal in the Software without restriction, including
10 | without limitation the rights to use, copy, modify, merge, publish,
11 | distribute, sublicense, and/or sell copies of the Software, and to
12 | permit persons to whom the Software is furnished to do so, subject to
13 | the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be
16 | included in all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "authentication-starter-template",
3 | "version": "0.0.1",
4 | "author": "Jorge Vergara",
5 | "homepage": "https://jsmobiledev.com/",
6 | "scripts": {
7 | "ng": "ng",
8 | "start": "ng serve",
9 | "build": "ng build",
10 | "test": "ng test",
11 | "lint": "ng lint",
12 | "e2e": "ng e2e"
13 | },
14 | "private": true,
15 | "dependencies": {
16 | "@angular/common": "^13.3.11",
17 | "@angular/core": "^13.3.11",
18 | "@angular/fire": "^7.4.1",
19 | "@angular/forms": "^13.3.11",
20 | "@angular/platform-browser": "^13.3.11",
21 | "@angular/platform-browser-dynamic": "^13.3.11",
22 | "@angular/router": "^13.3.11",
23 | "@capacitor/app": "1.1.1",
24 | "@capacitor/core": "3.4.3",
25 | "@capacitor/haptics": "1.1.4",
26 | "@capacitor/keyboard": "1.2.2",
27 | "@capacitor/status-bar": "1.0.8",
28 | "@ionic/angular": "^6.1.10",
29 | "@ngrx/component-store": "^13.2.0",
30 | "firebase": "^9.8.3",
31 | "rxjs": "~6.6.0",
32 | "tslib": "^2.4.0",
33 | "zone.js": "^0.11.6"
34 | },
35 | "devDependencies": {
36 | "@angular-devkit/build-angular": "^13.3.8",
37 | "@angular-eslint/builder": "~13.0.1",
38 | "@angular-eslint/eslint-plugin": "~13.0.1",
39 | "@angular-eslint/eslint-plugin-template": "~13.0.1",
40 | "@angular-eslint/template-parser": "~13.0.1",
41 | "@angular/cli": "^13.3.8",
42 | "@angular/compiler": "^13.3.11",
43 | "@angular/compiler-cli": "^13.3.11",
44 | "@angular/language-service": "^13.3.11",
45 | "@capacitor/cli": "3.4.3",
46 | "@ionic/angular-toolkit": "^6.0.0",
47 | "@types/jasmine": "~3.6.0",
48 | "@types/jasminewd2": "~2.0.3",
49 | "@types/node": "^12.20.55",
50 | "@typescript-eslint/eslint-plugin": "5.3.0",
51 | "@typescript-eslint/parser": "5.3.0",
52 | "eslint": "^7.6.0",
53 | "eslint-plugin-import": "2.22.1",
54 | "eslint-plugin-jsdoc": "30.7.6",
55 | "eslint-plugin-prefer-arrow": "1.2.2",
56 | "jasmine-core": "~3.8.0",
57 | "jasmine-spec-reporter": "~5.0.0",
58 | "karma": "^6.4.0",
59 | "karma-chrome-launcher": "~3.1.0",
60 | "karma-coverage": "~2.0.3",
61 | "karma-coverage-istanbul-reporter": "~3.0.2",
62 | "karma-jasmine": "~4.0.0",
63 | "karma-jasmine-html-reporter": "^1.5.0",
64 | "protractor": "~7.0.0",
65 | "ts-node": "~8.3.0",
66 | "typescript": "~4.4.4"
67 | },
68 | "description": "An authentication starter template"
69 | }
70 |
--------------------------------------------------------------------------------
/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
3 | import { AuthGuard } from './services/auth.guard';
4 |
5 | const routes: Routes = [
6 | { path: '', redirectTo: 'home', pathMatch: 'full' },
7 | {
8 | path: 'home',
9 | loadChildren: () =>
10 | import('./home/home.module').then(m => m.HomePageModule),
11 | canActivate: [AuthGuard]
12 | },
13 | {
14 | path: 'login',
15 | loadChildren: () =>
16 | import('./pages/login/login.module').then(m => m.LoginPageModule)
17 | },
18 | {
19 | path: 'signup',
20 | loadChildren: () =>
21 | import('./pages/signup/signup.module').then(m => m.SignupPageModule)
22 | },
23 | {
24 | path: 'reset-password',
25 | loadChildren: () =>
26 | import('./pages/reset-password/reset-password.module').then(
27 | m => m.ResetPasswordPageModule
28 | )
29 | },
30 | {
31 | path: 'profile',
32 | loadChildren: () =>
33 | import('./pages/profile/profile.module').then(m => m.ProfilePageModule),
34 | canActivate: [AuthGuard]
35 | }
36 | ];
37 |
38 | @NgModule({
39 | imports: [
40 | RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
41 | ],
42 | exports: [RouterModule]
43 | })
44 | export class AppRoutingModule {}
45 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 | // App Styles
2 | // ----------------------------------------------------------------------------
3 | // Put style rules here that you want to apply to the entire application. These
4 | // styles are for the entire app and not just one component. Additionally, this
5 | // file can hold Sass mixins, functions, and placeholder classes to be imported
6 | // and used throughout the application.
--------------------------------------------------------------------------------
/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2 | import { TestBed, async } from '@angular/core/testing';
3 |
4 | import { Platform } from '@ionic/angular';
5 | import { SplashScreen } from '@ionic-native/splash-screen/ngx';
6 | import { StatusBar } from '@ionic-native/status-bar/ngx';
7 |
8 | import { AppComponent } from './app.component';
9 |
10 | describe('AppComponent', () => {
11 |
12 | let statusBarSpy, splashScreenSpy, platformReadySpy, platformSpy;
13 |
14 | beforeEach(async(() => {
15 | statusBarSpy = jasmine.createSpyObj('StatusBar', ['styleDefault']);
16 | splashScreenSpy = jasmine.createSpyObj('SplashScreen', ['hide']);
17 | platformReadySpy = Promise.resolve();
18 | platformSpy = jasmine.createSpyObj('Platform', { ready: platformReadySpy });
19 |
20 | TestBed.configureTestingModule({
21 | declarations: [AppComponent],
22 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
23 | providers: [
24 | { provide: StatusBar, useValue: statusBarSpy },
25 | { provide: SplashScreen, useValue: splashScreenSpy },
26 | { provide: Platform, useValue: platformSpy },
27 | ],
28 | }).compileComponents();
29 | }));
30 |
31 | it('should create the app', () => {
32 | const fixture = TestBed.createComponent(AppComponent);
33 | const app = fixture.debugElement.componentInstance;
34 | expect(app).toBeTruthy();
35 | });
36 |
37 | it('should initialize the app', async () => {
38 | TestBed.createComponent(AppComponent);
39 | expect(platformSpy.ready).toHaveBeenCalled();
40 | await platformReadySpy;
41 | expect(statusBarSpy.styleDefault).toHaveBeenCalled();
42 | expect(splashScreenSpy.hide).toHaveBeenCalled();
43 | });
44 |
45 | // TODO: add more tests!
46 |
47 | });
48 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-root',
5 | templateUrl: 'app.component.html',
6 | styleUrls: ['app.component.scss']
7 | })
8 | export class AppComponent {
9 | constructor() {}
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { RouteReuseStrategy } from '@angular/router';
4 |
5 | import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
6 |
7 | import { AppComponent } from './app.component';
8 | import { AppRoutingModule } from './app-routing.module';
9 | import { provideAuth, getAuth } from '@angular/fire/auth';
10 | import { provideFirestore, getFirestore } from '@angular/fire/firestore';
11 | import { initializeApp, provideFirebaseApp } from '@angular/fire/app';
12 | import { environment } from '../environments/environment';
13 |
14 | @NgModule({
15 | declarations: [AppComponent],
16 | entryComponents: [],
17 | imports: [
18 | BrowserModule,
19 | IonicModule.forRoot(),
20 | AppRoutingModule,
21 | provideFirebaseApp(() => initializeApp(environment.firebase)),
22 | provideAuth(() => getAuth()),
23 | provideFirestore(() => getFirestore()),
24 | ],
25 | providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
26 | bootstrap: [AppComponent],
27 | })
28 | export class AppModule {}
29 |
--------------------------------------------------------------------------------
/src/app/components/auth-form/auth-form.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/components/auth-form/auth-form.component.scss:
--------------------------------------------------------------------------------
1 | form {
2 | padding: 12px;
3 | margin-bottom: 32px;
4 | ion-button {
5 | margin-top: 30px;
6 | }
7 | }
8 |
9 | p {
10 | font-size: 0.8em;
11 | color: #d2d2d2;
12 | }
13 |
14 | ion-label {
15 | margin-left: 5px;
16 | }
17 |
18 | ion-input {
19 | padding: 5px;
20 | }
21 |
22 | .invalid {
23 | border-bottom: 1px solid #ff6153;
24 | }
25 |
26 | .error-message {
27 | min-height: 2.2rem;
28 | ion-label {
29 | margin: 2px 0;
30 | font-size: 60%;
31 | color: #ff6153;
32 | }
33 | .item-inner {
34 | border-bottom: 0;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/app/components/auth-form/auth-form.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
3 |
4 | import { AuthFormComponent } from './auth-form.component';
5 |
6 | describe('AuthFormComponent', () => {
7 | let component: AuthFormComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(async(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ AuthFormComponent ],
13 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
14 | })
15 | .compileComponents();
16 | }));
17 |
18 | beforeEach(() => {
19 | fixture = TestBed.createComponent(AuthFormComponent);
20 | component = fixture.componentInstance;
21 | fixture.detectChanges();
22 | });
23 |
24 | it('should create', () => {
25 | expect(component).toBeTruthy();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/src/app/components/auth-form/auth-form.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
2 | import { FormGroup, Validators, FormBuilder } from '@angular/forms';
3 | import { UserCredential } from 'src/app/models/user';
4 | import { LoadingController, AlertController } from '@ionic/angular';
5 |
6 | @Component({
7 | selector: 'app-auth-form',
8 | templateUrl: './auth-form.component.html',
9 | styleUrls: ['./auth-form.component.scss']
10 | })
11 | export class AuthFormComponent implements OnInit {
12 | public loading: HTMLIonLoadingElement;
13 | public authForm: FormGroup;
14 | @Input() actionButtonText: string;
15 | @Input() isPasswordResetPage = false;
16 | @Output() formSubmitted = new EventEmitter();
17 |
18 | constructor(
19 | private formBuilder: FormBuilder,
20 | private loadingCtrl: LoadingController,
21 | private alertCtrl: AlertController
22 | ) {
23 | this.authForm = this.formBuilder.group({
24 | email: ['', Validators.compose([Validators.required, Validators.email])],
25 | password: ['', Validators.minLength(6)]
26 | });
27 | }
28 |
29 | ngOnInit() {}
30 |
31 | submitCredentials(authForm: FormGroup): void {
32 | if (!authForm.valid) {
33 | console.log('Form is not valid yet, current value:', authForm.value);
34 | } else {
35 | this.showLoading();
36 | const credentials: UserCredential = {
37 | email: authForm.value.email,
38 | password: authForm.value.password
39 | };
40 | this.formSubmitted.emit(credentials);
41 | }
42 | }
43 |
44 | async showLoading(): Promise {
45 | try {
46 | this.loading = await this.loadingCtrl.create();
47 | await this.loading.present();
48 | } catch (error) {
49 | this.handleError(error);
50 | }
51 | }
52 |
53 | hideLoading(): Promise {
54 | return this.loading.dismiss();
55 | }
56 |
57 | async handleError(error): Promise {
58 | const alert = await this.alertCtrl.create({
59 | message: error.message,
60 | buttons: [{ text: 'Ok', role: 'cancel' }]
61 | });
62 | await alert.present();
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/app/home/home.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { IonicModule } from '@ionic/angular';
4 | import { FormsModule } from '@angular/forms';
5 | import { RouterModule } from '@angular/router';
6 |
7 | import { HomePage } from './home.page';
8 |
9 | @NgModule({
10 | imports: [
11 | CommonModule,
12 | FormsModule,
13 | IonicModule,
14 | RouterModule.forChild([
15 | {
16 | path: '',
17 | component: HomePage
18 | }
19 | ])
20 | ],
21 | declarations: [HomePage]
22 | })
23 | export class HomePageModule {}
24 |
--------------------------------------------------------------------------------
/src/app/home/home.page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | MetraRev
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | The world is your oyster.
15 |
If you get lost, the docs will be
16 | your guide.
17 |
18 |
--------------------------------------------------------------------------------
/src/app/home/home.page.scss:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/app/home/home.page.spec.ts:
--------------------------------------------------------------------------------
1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
3 |
4 | import { HomePage } from './home.page';
5 |
6 | describe('HomePage', () => {
7 | let component: HomePage;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(async(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ HomePage ],
13 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
14 | })
15 | .compileComponents();
16 | }));
17 |
18 | beforeEach(() => {
19 | fixture = TestBed.createComponent(HomePage);
20 | component = fixture.componentInstance;
21 | fixture.detectChanges();
22 | });
23 |
24 | it('should create', () => {
25 | expect(component).toBeTruthy();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/src/app/home/home.page.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-home',
5 | templateUrl: 'home.page.html',
6 | styleUrls: ['home.page.scss'],
7 | })
8 | export class HomePage {
9 |
10 | constructor() {}
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/models/user.ts:
--------------------------------------------------------------------------------
1 | export interface UserCredential {
2 | email: string;
3 | password: string;
4 | }
5 |
6 | export interface UserProfile {
7 | email: string;
8 | fullName: string;
9 | }
10 |
--------------------------------------------------------------------------------
/src/app/pages/login/login.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { FormsModule } from '@angular/forms';
4 | import { Routes, RouterModule } from '@angular/router';
5 |
6 | import { IonicModule } from '@ionic/angular';
7 |
8 | import { LoginPage } from './login.page';
9 |
10 | import { AuthModule } from 'src/app/shared-modules/auth.module';
11 |
12 | const routes: Routes = [
13 | {
14 | path: '',
15 | component: LoginPage
16 | }
17 | ];
18 |
19 | @NgModule({
20 | imports: [
21 | CommonModule,
22 | FormsModule,
23 | IonicModule,
24 | RouterModule.forChild(routes),
25 | AuthModule
26 | ],
27 | declarations: [LoginPage]
28 | })
29 | export class LoginPageModule {}
30 |
--------------------------------------------------------------------------------
/src/app/pages/login/login.page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Login
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Create a new account
12 |
13 |
14 |
15 | I forgot my password :(
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/app/pages/login/login.page.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JsMobileDev/ionic-angular-firebase-authentication-starter/b58dc68ce1d9630b594bce8b1969b91db3245c6d/src/app/pages/login/login.page.scss
--------------------------------------------------------------------------------
/src/app/pages/login/login.page.spec.ts:
--------------------------------------------------------------------------------
1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
3 |
4 | import { LoginPage } from './login.page';
5 |
6 | describe('LoginPage', () => {
7 | let component: LoginPage;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(async(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ LoginPage ],
13 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
14 | })
15 | .compileComponents();
16 | }));
17 |
18 | beforeEach(() => {
19 | fixture = TestBed.createComponent(LoginPage);
20 | component = fixture.componentInstance;
21 | fixture.detectChanges();
22 | });
23 |
24 | it('should create', () => {
25 | expect(component).toBeTruthy();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/src/app/pages/login/login.page.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ViewChild } from '@angular/core';
2 | import { UserCredential } from 'src/app/models/user';
3 | import { AuthService } from 'src/app/services/auth.service';
4 | import { AuthFormComponent } from 'src/app/components/auth-form/auth-form.component';
5 | import { Router } from '@angular/router';
6 |
7 | @Component({
8 | selector: 'app-login',
9 | templateUrl: './login.page.html',
10 | styleUrls: ['./login.page.scss'],
11 | })
12 | export class LoginPage implements OnInit {
13 | @ViewChild(AuthFormComponent) loginForm: AuthFormComponent;
14 | constructor(private authService: AuthService, private router: Router) {}
15 |
16 | ngOnInit() {}
17 |
18 | async loginUser(credentials: UserCredential): Promise {
19 | try {
20 | const userCredential = await this.authService.login(credentials.email, credentials.password);
21 | this.authService.userId = userCredential.user.uid;
22 | await this.loginForm.hideLoading();
23 | this.router.navigateByUrl('home');
24 | } catch (error) {
25 | await this.loginForm.hideLoading();
26 | this.loginForm.handleError(error);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/pages/profile/profile.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { FormsModule } from '@angular/forms';
4 | import { Routes, RouterModule } from '@angular/router';
5 |
6 | import { IonicModule } from '@ionic/angular';
7 |
8 | import { ProfilePage } from './profile.page';
9 |
10 | const routes: Routes = [
11 | {
12 | path: '',
13 | component: ProfilePage
14 | }
15 | ];
16 |
17 | @NgModule({
18 | imports: [
19 | CommonModule,
20 | FormsModule,
21 | IonicModule,
22 | RouterModule.forChild(routes)
23 | ],
24 | declarations: [ProfilePage]
25 | })
26 | export class ProfilePageModule {}
27 |
--------------------------------------------------------------------------------
/src/app/pages/profile/profile.page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Profile Page
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Personal Information
18 |
19 |
20 |
21 |
22 |
23 | Name
24 |
25 | {{(userProfile$ | async )?.fullName}}
26 |
27 |
28 | Tap here to edit.
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | Email
40 |
41 | {{(userProfile$ | async )?.email}}
42 |
43 |
44 | Tap here to edit.
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | Password
56 |
57 | Tap here to edit.
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/app/pages/profile/profile.page.scss:
--------------------------------------------------------------------------------
1 | ion-list-header {
2 | background-color: #ececec;
3 | }
4 |
5 | .text-center {
6 | text-align: center;
7 | }
8 |
9 | .text-left {
10 | text-align: left;
11 | }
12 |
13 | .placeholder-profile {
14 | color: #cccccc;
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/pages/profile/profile.page.spec.ts:
--------------------------------------------------------------------------------
1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
3 |
4 | import { ProfilePage } from './profile.page';
5 |
6 | describe('ProfilePage', () => {
7 | let component: ProfilePage;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(async(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ ProfilePage ],
13 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
14 | })
15 | .compileComponents();
16 | }));
17 |
18 | beforeEach(() => {
19 | fixture = TestBed.createComponent(ProfilePage);
20 | component = fixture.componentInstance;
21 | fixture.detectChanges();
22 | });
23 |
24 | it('should create', () => {
25 | expect(component).toBeTruthy();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/src/app/pages/profile/profile.page.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnDestroy, OnInit } from '@angular/core';
2 | import { AuthService } from 'src/app/services/auth.service';
3 | import { Router } from '@angular/router';
4 | import { AlertController } from '@ionic/angular';
5 | import { UserProfile } from 'src/app/models/user';
6 | import { ProfileService } from './profile.service';
7 | import { Observable, Subscription } from 'rxjs';
8 | import { first, tap } from 'rxjs/operators';
9 | import { ProfileStore } from './profile.store';
10 |
11 | @Component({
12 | selector: 'app-profile',
13 | templateUrl: './profile.page.html',
14 | styleUrls: ['./profile.page.scss'],
15 | providers: [ProfileStore],
16 | })
17 | export class ProfilePage implements OnDestroy, OnInit {
18 | public userProfile$: Observable = this.profileStore.userProfile$;
19 | private userProfileSubscription: Subscription;
20 | constructor(
21 | private authService: AuthService,
22 | private router: Router,
23 | private profileService: ProfileService,
24 | private alertCtrl: AlertController,
25 | private readonly profileStore: ProfileStore
26 | ) {}
27 |
28 | ngOnInit(): void {
29 | this.userProfileSubscription = this.profileService
30 | .getUserProfile()
31 | .subscribe((userProfile: UserProfile) => this.profileStore.setState(userProfile));
32 | }
33 |
34 | ngOnDestroy(): void {
35 | this.userProfileSubscription?.unsubscribe();
36 | }
37 |
38 | async logOut(): Promise {
39 | await this.authService.logout();
40 | this.router.navigateByUrl('login');
41 | }
42 |
43 | updateName(): void {
44 | this.userProfileSubscription = this.userProfile$.pipe(first()).subscribe(async userProfile => {
45 | const alert = await this.alertCtrl.create({
46 | subHeader: 'Your name',
47 | inputs: [
48 | {
49 | type: 'text',
50 | name: 'fullName',
51 | placeholder: 'Your full name',
52 | value: userProfile.fullName,
53 | },
54 | ],
55 | buttons: [
56 | { text: 'Cancel' },
57 | {
58 | text: 'Save',
59 | handler: data => {
60 | this.profileStore.updateUserName(data.fullName);
61 | },
62 | },
63 | ],
64 | });
65 | return await alert.present();
66 | });
67 | }
68 |
69 | async updateEmail(): Promise {
70 | const alert = await this.alertCtrl.create({
71 | inputs: [
72 | { type: 'text', name: 'newEmail', placeholder: 'Your new email' },
73 | { name: 'password', placeholder: 'Your password', type: 'password' },
74 | ],
75 | buttons: [
76 | { text: 'Cancel' },
77 | {
78 | text: 'Save',
79 | handler: data => {
80 | this.profileStore.updateUserEmail({ email: data.newEmail, password: data.password });
81 | },
82 | },
83 | ],
84 | });
85 | return await alert.present();
86 | }
87 |
88 | async updatePassword(): Promise {
89 | const alert = await this.alertCtrl.create({
90 | inputs: [
91 | { name: 'newPassword', placeholder: 'New password', type: 'password' },
92 | { name: 'oldPassword', placeholder: 'Old password', type: 'password' },
93 | ],
94 | buttons: [
95 | { text: 'Cancel' },
96 | {
97 | text: 'Save',
98 | handler: data => {
99 | this.profileStore.updateUserPassword({ newPassword: data.newPassword, oldPassword: data.oldPassword });
100 | },
101 | },
102 | ],
103 | });
104 | return await alert.present();
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/app/pages/profile/profile.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { ProfileService } from './profile.service';
4 |
5 | describe('ProfileService', () => {
6 | beforeEach(() => TestBed.configureTestingModule({}));
7 |
8 | it('should be created', () => {
9 | const service: ProfileService = TestBed.get(ProfileService);
10 | expect(service).toBeTruthy();
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/src/app/pages/profile/profile.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { doc, DocumentData, DocumentReference, Firestore, getDoc, setDoc, docData } from '@angular/fire/firestore';
3 | import { User, reauthenticateWithCredential, EmailAuthProvider, updateEmail, updatePassword } from '@angular/fire/auth';
4 | import { AuthService } from '../../services/auth.service';
5 | import { UserProfile } from '../../models/user';
6 | import { map, catchError, switchMap, tap, concatMap, first } from 'rxjs/operators';
7 | import { EMPTY, forkJoin, from, Observable } from 'rxjs';
8 |
9 | @Injectable({
10 | providedIn: 'root',
11 | })
12 | export class ProfileService {
13 | private currentUser: User;
14 | constructor(private firestore: Firestore, private authService: AuthService) {}
15 |
16 | getUserProfileReference(): Observable> {
17 | return this.authService.getUser().pipe(
18 | map(user => {
19 | this.currentUser = user;
20 | return doc(this.firestore, `users/${user.uid}`);
21 | }),
22 | catchError(() => EMPTY)
23 | );
24 | }
25 |
26 | getUserProfile(): Observable {
27 | return this.getUserProfileReference().pipe(
28 | switchMap(userProfileReference => {
29 | return docData(userProfileReference) as Observable;
30 | }),
31 | catchError(() => EMPTY)
32 | );
33 | }
34 |
35 | updateName(fullName: string): Observable> {
36 | return this.getUserProfileReference().pipe(
37 | tap({
38 | next: userProfileReference => setDoc(userProfileReference, { fullName }, { merge: true }),
39 | error: error => console.error(error),
40 | }),
41 | catchError(() => EMPTY)
42 | );
43 | }
44 |
45 | updateEmail(newEmail: string, password: string): Observable {
46 | return forkJoin([
47 | this.getUserProfile().pipe(first()),
48 | this.authService.getUser().pipe(first()),
49 | this.getUserProfileReference().pipe(first()),
50 | ]).pipe(
51 | concatMap(([userProfile, user, userProfileReference]) => {
52 | const credential = EmailAuthProvider.credential(userProfile.email, password);
53 | return from(reauthenticateWithCredential(user, credential)).pipe(
54 | tap({
55 | next: () =>
56 | from(
57 | updateEmail(user, newEmail).then(() =>
58 | setDoc(userProfileReference, { email: newEmail }, { merge: true })
59 | )
60 | ),
61 | error: error => console.error(error),
62 | })
63 | );
64 | })
65 | );
66 | }
67 |
68 | updatePassword(newPassword: string, oldPassword: string): Observable {
69 | return forkJoin([this.getUserProfile().pipe(first()), this.authService.getUser().pipe(first())]).pipe(
70 | concatMap(([userProfile, user]) => {
71 | const credential = EmailAuthProvider.credential(userProfile.email, oldPassword);
72 | return from(reauthenticateWithCredential(user, credential)).pipe(
73 | tap({
74 | next: () => {
75 | return from(updatePassword(user, newPassword));
76 | },
77 | error: error => console.error(error),
78 | })
79 | );
80 | })
81 | );
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/app/pages/profile/profile.store.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ComponentStore } from '@ngrx/component-store';
3 | import { EMPTY, Observable } from 'rxjs';
4 | import { catchError, switchMap, tap } from 'rxjs/operators';
5 | import { ProfileService } from './profile.service';
6 |
7 | export interface ProfileState {
8 | email: string;
9 | fullName: string;
10 | }
11 |
12 | @Injectable()
13 | export class ProfileStore extends ComponentStore {
14 | constructor(private readonly profileService: ProfileService) {
15 | super({ email: '', fullName: '' });
16 | }
17 |
18 | readonly userProfile$: Observable = this.select(state => state);
19 |
20 | readonly updateEmail = this.updater((state, email: string) => ({ ...state, email }));
21 |
22 | readonly updateFullName = this.updater((state, fullName: string) => ({ ...state, fullName }));
23 |
24 | readonly updateUserName = this.effect((fullName$: Observable) => {
25 | return fullName$.pipe(
26 | switchMap(fullName => {
27 | return this.profileService.updateName(fullName).pipe(
28 | tap({
29 | next: () => this.updateFullName(fullName),
30 | error: e => console.log(e),
31 | }),
32 | catchError(() => EMPTY)
33 | );
34 | })
35 | );
36 | });
37 |
38 | readonly updateUserEmail = this.effect((credential$: Observable<{ email: string; password: string }>) => {
39 | return credential$.pipe(
40 | switchMap(({ email, password }) =>
41 | this.profileService.updateEmail(email, password).pipe(
42 | tap({
43 | next: () => this.updateEmail(email),
44 | error: e => console.log(e),
45 | }),
46 | catchError(() => EMPTY)
47 | )
48 | )
49 | );
50 | });
51 |
52 | readonly updateUserPassword = this.effect((passwords$: Observable<{ newPassword: string; oldPassword: string }>) => {
53 | return passwords$.pipe(
54 | switchMap(({ newPassword, oldPassword }) =>
55 | this.profileService.updatePassword(newPassword, oldPassword).pipe(
56 | tap({
57 | next: () => console.log('Updated Passwords'),
58 | error: e => console.log(e),
59 | }),
60 | catchError(() => EMPTY)
61 | )
62 | )
63 | );
64 | });
65 | }
66 |
--------------------------------------------------------------------------------
/src/app/pages/reset-password/reset-password.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { FormsModule } from '@angular/forms';
4 | import { Routes, RouterModule } from '@angular/router';
5 |
6 | import { IonicModule } from '@ionic/angular';
7 |
8 | import { ResetPasswordPage } from './reset-password.page';
9 | import { AuthModule } from 'src/app/shared-modules/auth.module';
10 |
11 | const routes: Routes = [
12 | {
13 | path: '',
14 | component: ResetPasswordPage
15 | }
16 | ];
17 |
18 | @NgModule({
19 | imports: [
20 | CommonModule,
21 | FormsModule,
22 | IonicModule,
23 | RouterModule.forChild(routes),
24 | AuthModule
25 | ],
26 | declarations: [ResetPasswordPage]
27 | })
28 | export class ResetPasswordPageModule {}
29 |
--------------------------------------------------------------------------------
/src/app/pages/reset-password/reset-password.page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Forgot your password?
7 |
8 |
9 |
10 |
11 |
13 |
--------------------------------------------------------------------------------
/src/app/pages/reset-password/reset-password.page.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JsMobileDev/ionic-angular-firebase-authentication-starter/b58dc68ce1d9630b594bce8b1969b91db3245c6d/src/app/pages/reset-password/reset-password.page.scss
--------------------------------------------------------------------------------
/src/app/pages/reset-password/reset-password.page.spec.ts:
--------------------------------------------------------------------------------
1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
3 |
4 | import { ResetPasswordPage } from './reset-password.page';
5 |
6 | describe('ResetPasswordPage', () => {
7 | let component: ResetPasswordPage;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(async(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ ResetPasswordPage ],
13 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
14 | })
15 | .compileComponents();
16 | }));
17 |
18 | beforeEach(() => {
19 | fixture = TestBed.createComponent(ResetPasswordPage);
20 | component = fixture.componentInstance;
21 | fixture.detectChanges();
22 | });
23 |
24 | it('should create', () => {
25 | expect(component).toBeTruthy();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/src/app/pages/reset-password/reset-password.page.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ViewChild } from '@angular/core';
2 | import { UserCredential } from 'src/app/models/user';
3 | import { AuthService } from 'src/app/services/auth.service';
4 | import { AuthFormComponent } from 'src/app/components/auth-form/auth-form.component';
5 | import { AlertController } from '@ionic/angular';
6 | import { Router } from '@angular/router';
7 |
8 | @Component({
9 | selector: 'app-reset-password',
10 | templateUrl: './reset-password.page.html',
11 | styleUrls: ['./reset-password.page.scss']
12 | })
13 | export class ResetPasswordPage implements OnInit {
14 | @ViewChild(AuthFormComponent)
15 | resetPasswordForm: AuthFormComponent;
16 | constructor(
17 | private authService: AuthService,
18 | private alertCtrl: AlertController,
19 | private router: Router
20 | ) {}
21 |
22 | ngOnInit() {}
23 |
24 | async resetPassword(credentials: UserCredential): Promise {
25 | try {
26 | await this.authService.resetPassword(credentials.email);
27 | await this.resetPasswordForm.hideLoading();
28 | const alert = await this.alertCtrl.create({
29 | message: 'Check your inbox for the password reset link',
30 | buttons: [
31 | {
32 | text: 'Ok',
33 | role: 'cancel',
34 | handler: () => {
35 | this.router.navigateByUrl('login');
36 | }
37 | }
38 | ]
39 | });
40 | await alert.present();
41 | } catch (error) {
42 | await this.resetPasswordForm.hideLoading();
43 | this.resetPasswordForm.handleError(error);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/app/pages/signup/signup.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { FormsModule } from '@angular/forms';
4 | import { Routes, RouterModule } from '@angular/router';
5 |
6 | import { IonicModule } from '@ionic/angular';
7 |
8 | import { SignupPage } from './signup.page';
9 | import { AuthModule } from 'src/app/shared-modules/auth.module';
10 |
11 | const routes: Routes = [
12 | {
13 | path: '',
14 | component: SignupPage
15 | }
16 | ];
17 |
18 | @NgModule({
19 | imports: [
20 | CommonModule,
21 | FormsModule,
22 | IonicModule,
23 | RouterModule.forChild(routes),
24 | AuthModule
25 | ],
26 | declarations: [SignupPage]
27 | })
28 | export class SignupPageModule {}
29 |
--------------------------------------------------------------------------------
/src/app/pages/signup/signup.page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Create your account
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | I already have an account
15 |
16 |
--------------------------------------------------------------------------------
/src/app/pages/signup/signup.page.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JsMobileDev/ionic-angular-firebase-authentication-starter/b58dc68ce1d9630b594bce8b1969b91db3245c6d/src/app/pages/signup/signup.page.scss
--------------------------------------------------------------------------------
/src/app/pages/signup/signup.page.spec.ts:
--------------------------------------------------------------------------------
1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
3 |
4 | import { SignupPage } from './signup.page';
5 |
6 | describe('SignupPage', () => {
7 | let component: SignupPage;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(async(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ SignupPage ],
13 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
14 | })
15 | .compileComponents();
16 | }));
17 |
18 | beforeEach(() => {
19 | fixture = TestBed.createComponent(SignupPage);
20 | component = fixture.componentInstance;
21 | fixture.detectChanges();
22 | });
23 |
24 | it('should create', () => {
25 | expect(component).toBeTruthy();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/src/app/pages/signup/signup.page.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ViewChild } from '@angular/core';
2 | import { UserCredential } from 'src/app/models/user';
3 | import { AuthService } from 'src/app/services/auth.service';
4 | import { AuthFormComponent } from 'src/app/components/auth-form/auth-form.component';
5 | import { Router } from '@angular/router';
6 |
7 | @Component({
8 | selector: 'app-signup',
9 | templateUrl: './signup.page.html',
10 | styleUrls: ['./signup.page.scss'],
11 | })
12 | export class SignupPage implements OnInit {
13 | @ViewChild(AuthFormComponent)
14 | signupForm: AuthFormComponent;
15 | constructor(private authService: AuthService, private router: Router) {}
16 |
17 | ngOnInit() {}
18 |
19 | async signupUser(credentials: UserCredential): Promise {
20 | try {
21 | const user = await this.authService.signup(credentials.email, credentials.password);
22 | this.authService.userId = user.uid;
23 | await this.signupForm.hideLoading();
24 | this.router.navigateByUrl('home');
25 | } catch (error) {
26 | await this.signupForm.hideLoading();
27 | this.signupForm.handleError(error);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/app/services/auth.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async, inject } from '@angular/core/testing';
2 |
3 | import { AuthGuard } from './auth.guard';
4 |
5 | describe('AuthGuard', () => {
6 | beforeEach(() => {
7 | TestBed.configureTestingModule({
8 | providers: [AuthGuard]
9 | });
10 | });
11 |
12 | it('should ...', inject([AuthGuard], (guard: AuthGuard) => {
13 | expect(guard).toBeTruthy();
14 | }));
15 | });
16 |
--------------------------------------------------------------------------------
/src/app/services/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
3 | import { Observable } from 'rxjs';
4 | import { first } from 'rxjs/operators';
5 | import { AuthService } from './auth.service';
6 |
7 | @Injectable({
8 | providedIn: 'root',
9 | })
10 | export class AuthGuard implements CanActivate {
11 | constructor(private authService: AuthService, private router: Router) {}
12 | canActivate(
13 | next: ActivatedRouteSnapshot,
14 | state: RouterStateSnapshot
15 | ): Observable | Promise | boolean | UrlTree {
16 | return new Promise(async (resolve, reject) => {
17 | const user = await this.authService.getUser().pipe(first()).toPromise();
18 | if (user) {
19 | resolve(true);
20 | } else {
21 | reject('No user logged in');
22 | this.router.navigateByUrl('/login');
23 | }
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/services/auth.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { AuthService } from './auth.service';
4 |
5 | describe('AuthService', () => {
6 | beforeEach(() => TestBed.configureTestingModule({}));
7 |
8 | it('should be created', () => {
9 | const service: AuthService = TestBed.get(AuthService);
10 | expect(service).toBeTruthy();
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/src/app/services/auth.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import {
3 | Auth,
4 | authState,
5 | createUserWithEmailAndPassword,
6 | sendPasswordResetEmail,
7 | signInWithEmailAndPassword,
8 | signOut,
9 | User,
10 | UserCredential,
11 | } from '@angular/fire/auth';
12 | import { doc, Firestore, setDoc } from '@angular/fire/firestore';
13 | import { Observable } from 'rxjs';
14 |
15 | @Injectable({
16 | providedIn: 'root',
17 | })
18 | export class AuthService {
19 | public userId: string;
20 | constructor(private auth: Auth, private firestore: Firestore) {}
21 |
22 | getUser(): Observable {
23 | return authState(this.auth);
24 | }
25 |
26 | login(email: string, password: string): Promise {
27 | return signInWithEmailAndPassword(this.auth, email, password);
28 | }
29 |
30 | async signup(email: string, password: string): Promise {
31 | try {
32 | const newUserCredential: UserCredential = await createUserWithEmailAndPassword(this.auth, email, password);
33 | const userReference = doc(this.firestore, `users/${newUserCredential.user.uid}`);
34 | await setDoc(userReference, { email }, { merge: true });
35 | return newUserCredential.user;
36 | } catch (error) {
37 | throw error;
38 | }
39 | }
40 |
41 | resetPassword(email: string): Promise {
42 | return sendPasswordResetEmail(this.auth, email);
43 | }
44 |
45 | logout(): Promise {
46 | return signOut(this.auth);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/app/shared-modules/auth.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms';
4 |
5 | import { IonicModule } from '@ionic/angular';
6 |
7 | import { AuthFormComponent } from 'src/app/components/auth-form/auth-form.component';
8 |
9 | @NgModule({
10 | imports: [CommonModule, FormsModule, IonicModule, ReactiveFormsModule],
11 | declarations: [AuthFormComponent],
12 | exports: [AuthFormComponent],
13 | entryComponents: [AuthFormComponent]
14 | })
15 | export class AuthModule {}
16 |
--------------------------------------------------------------------------------
/src/assets/icon/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JsMobileDev/ionic-angular-firebase-authentication-starter/b58dc68ce1d9630b594bce8b1969b91db3245c6d/src/assets/icon/favicon.png
--------------------------------------------------------------------------------
/src/assets/shapes.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | firebase: {
3 | projectId: 'javebratt-examples',
4 | appId: '1:1007228817555:web:f0d4585bf9d7b819b4fd66',
5 | storageBucket: 'javebratt-examples.appspot.com',
6 | locationId: 'us-central',
7 | apiKey: 'AIzaSyBDpbGgRrwOwI5TkGW6KdF5Z0WwmhHIaWU',
8 | authDomain: 'javebratt-examples.firebaseapp.com',
9 | messagingSenderId: '1007228817555',
10 | },
11 | production: true
12 | };
13 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: false,
3 | firebase: {
4 | projectId: 'javebratt-examples',
5 | appId: '1:1007228817555:web:f0d4585bf9d7b819b4fd66',
6 | storageBucket: 'javebratt-examples.appspot.com',
7 | locationId: 'us-central',
8 | apiKey: 'AIzaSyBDpbGgRrwOwI5TkGW6KdF5Z0WwmhHIaWU',
9 | authDomain: 'javebratt-examples.firebaseapp.com',
10 | messagingSenderId: '1007228817555',
11 | }
12 | };
13 |
14 | /*
15 | * For easier debugging in development mode, you can import the following file
16 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
17 | *
18 | * This import should be commented out in production mode because it will have a negative impact
19 | * on performance if an error is thrown.
20 | */
21 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI.
22 |
--------------------------------------------------------------------------------
/src/global.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * App Global CSS
3 | * ----------------------------------------------------------------------------
4 | * Put style rules here that you want to apply globally. These styles are for
5 | * the entire app and not just one component. Additionally, this file can be
6 | * used as an entry point to import other CSS/Sass files to be included in the
7 | * output CSS.
8 | * For more information on global stylesheets, visit the documentation:
9 | * https://ionicframework.com/docs/layout/global-stylesheets
10 | */
11 |
12 | /* Core CSS required for Ionic components to work properly */
13 | @import '~@ionic/angular/css/core.css';
14 |
15 | /* Basic CSS for apps built with Ionic */
16 | @import '~@ionic/angular/css/normalize.css';
17 | @import '~@ionic/angular/css/structure.css';
18 | @import '~@ionic/angular/css/typography.css';
19 | @import '~@ionic/angular/css/display.css';
20 |
21 | /* Optional CSS utils that can be commented out */
22 | @import '~@ionic/angular/css/padding.css';
23 | @import '~@ionic/angular/css/float-elements.css';
24 | @import '~@ionic/angular/css/text-alignment.css';
25 | @import '~@ionic/angular/css/text-transformation.css';
26 | @import '~@ionic/angular/css/flex-utils.css';
27 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Ionic App
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/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/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
22 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
23 |
24 | /**
25 | * Web Animations `@angular/platform-browser/animations`
26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
28 | */
29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
30 |
31 | /**
32 | * By default, zone.js will patch all possible macroTask and DomEvents
33 | * user can disable parts of macroTask/DomEvents patch by setting following flags
34 | * because those flags need to be set before `zone.js` being loaded, and webpack
35 | * will put import in the top of bundle, so user need to create a separate file
36 | * in this directory (for example: zone-flags.ts), and put the following flags
37 | * into that file, and then add the following code before importing zone.js.
38 | * import './zone-flags.ts';
39 | *
40 | * The flags allowed in zone-flags.ts are listed here.
41 | *
42 | * The following flags will work for all browsers.
43 | *
44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
46 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
47 | *
48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
50 | *
51 | * (window as any).__Zone_enable_cross_context_check = true;
52 | *
53 | */
54 |
55 | import './zone-flags.ts';
56 |
57 | /***************************************************************************************************
58 | * Zone JS is required by default for Angular itself.
59 | */
60 |
61 | import 'zone.js/dist/zone'; // Included with Angular CLI.
62 |
63 |
64 | /***************************************************************************************************
65 | * APPLICATION IMPORTS
66 | */
67 |
--------------------------------------------------------------------------------
/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/theme/variables.scss:
--------------------------------------------------------------------------------
1 | // Ionic Variables and Theming. For more info, please see:
2 | // http://ionicframework.com/docs/theming/
3 |
4 | /** Ionic CSS Variables **/
5 | :root {
6 | /** primary **/
7 | --ion-color-primary: #3880ff;
8 | --ion-color-primary-rgb: 56, 128, 255;
9 | --ion-color-primary-contrast: #ffffff;
10 | --ion-color-primary-contrast-rgb: 255, 255, 255;
11 | --ion-color-primary-shade: #3171e0;
12 | --ion-color-primary-tint: #4c8dff;
13 |
14 | /** secondary **/
15 | --ion-color-secondary: #0cd1e8;
16 | --ion-color-secondary-rgb: 12, 209, 232;
17 | --ion-color-secondary-contrast: #ffffff;
18 | --ion-color-secondary-contrast-rgb: 255, 255, 255;
19 | --ion-color-secondary-shade: #0bb8cc;
20 | --ion-color-secondary-tint: #24d6ea;
21 |
22 | /** tertiary **/
23 | --ion-color-tertiary: #7044ff;
24 | --ion-color-tertiary-rgb: 112, 68, 255;
25 | --ion-color-tertiary-contrast: #ffffff;
26 | --ion-color-tertiary-contrast-rgb: 255, 255, 255;
27 | --ion-color-tertiary-shade: #633ce0;
28 | --ion-color-tertiary-tint: #7e57ff;
29 |
30 | /** success **/
31 | --ion-color-success: #10dc60;
32 | --ion-color-success-rgb: 16, 220, 96;
33 | --ion-color-success-contrast: #ffffff;
34 | --ion-color-success-contrast-rgb: 255, 255, 255;
35 | --ion-color-success-shade: #0ec254;
36 | --ion-color-success-tint: #28e070;
37 |
38 | /** warning **/
39 | --ion-color-warning: #ffce00;
40 | --ion-color-warning-rgb: 255, 206, 0;
41 | --ion-color-warning-contrast: #ffffff;
42 | --ion-color-warning-contrast-rgb: 255, 255, 255;
43 | --ion-color-warning-shade: #e0b500;
44 | --ion-color-warning-tint: #ffd31a;
45 |
46 | /** danger **/
47 | --ion-color-danger: #f04141;
48 | --ion-color-danger-rgb: 245, 61, 61;
49 | --ion-color-danger-contrast: #ffffff;
50 | --ion-color-danger-contrast-rgb: 255, 255, 255;
51 | --ion-color-danger-shade: #d33939;
52 | --ion-color-danger-tint: #f25454;
53 |
54 | /** dark **/
55 | --ion-color-dark: #222428;
56 | --ion-color-dark-rgb: 34, 34, 34;
57 | --ion-color-dark-contrast: #ffffff;
58 | --ion-color-dark-contrast-rgb: 255, 255, 255;
59 | --ion-color-dark-shade: #1e2023;
60 | --ion-color-dark-tint: #383a3e;
61 |
62 | /** medium **/
63 | --ion-color-medium: #989aa2;
64 | --ion-color-medium-rgb: 152, 154, 162;
65 | --ion-color-medium-contrast: #ffffff;
66 | --ion-color-medium-contrast-rgb: 255, 255, 255;
67 | --ion-color-medium-shade: #86888f;
68 | --ion-color-medium-tint: #a2a4ab;
69 |
70 | /** light **/
71 | --ion-color-light: #f4f5f8;
72 | --ion-color-light-rgb: 244, 244, 244;
73 | --ion-color-light-contrast: #000000;
74 | --ion-color-light-contrast-rgb: 0, 0, 0;
75 | --ion-color-light-shade: #d7d8da;
76 | --ion-color-light-tint: #f5f6f9;
77 | }
78 |
--------------------------------------------------------------------------------
/src/zone-flags.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Prevents Angular change detection from
3 | * running with certain Web Component callbacks
4 | */
5 | (window as any).__Zone_disable_customElements = true;
6 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./out-tsc/app",
5 | "types": []
6 | },
7 | "files": ["src/main.ts", "src/polyfills.ts"],
8 | "include": ["src/**/*.d.ts", "src/zone-flags.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "outDir": "./dist/out-tsc",
6 | "sourceMap": true,
7 | "declaration": false,
8 | "module": "esnext",
9 | "moduleResolution": "node",
10 | "emitDecoratorMetadata": true,
11 | "experimentalDecorators": true,
12 | "importHelpers": true,
13 | "target": "es2015",
14 | "typeRoots": [
15 | "node_modules/@types"
16 | ],
17 | "lib": [
18 | "es2018",
19 | "dom"
20 | ]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./out-tsc/spec",
5 | "types": [
6 | "jasmine",
7 | "node"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts",
12 | "src/zone-flags.ts",
13 | "src/polyfills.ts"
14 | ],
15 | "include": [
16 | "src/**/*.spec.ts",
17 | "src/**/*.d.ts"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tslint:recommended",
3 | "rulesDirectory": [
4 | "codelyzer"
5 | ],
6 | "rules": {
7 | "array-type": false,
8 | "arrow-parens": false,
9 | "deprecation": {
10 | "severity": "warn"
11 | },
12 | "import-blacklist": [
13 | true,
14 | "rxjs/Rx"
15 | ],
16 | "interface-name": false,
17 | "max-classes-per-file": false,
18 | "max-line-length": [
19 | true,
20 | 140
21 | ],
22 | "member-access": false,
23 | "member-ordering": [
24 | true,
25 | {
26 | "order": [
27 | "static-field",
28 | "instance-field",
29 | "static-method",
30 | "instance-method"
31 | ]
32 | }
33 | ],
34 | "no-consecutive-blank-lines": false,
35 | "no-console": [
36 | true,
37 | "debug",
38 | "info",
39 | "time",
40 | "timeEnd",
41 | "trace"
42 | ],
43 | "no-empty": false,
44 | "no-inferrable-types": [
45 | true,
46 | "ignore-params"
47 | ],
48 | "no-non-null-assertion": true,
49 | "no-redundant-jsdoc": true,
50 | "no-switch-case-fall-through": true,
51 | "no-use-before-declare": true,
52 | "no-var-requires": false,
53 | "object-literal-key-quotes": [
54 | true,
55 | "as-needed"
56 | ],
57 | "object-literal-sort-keys": false,
58 | "ordered-imports": false,
59 | "quotemark": [
60 | true,
61 | "single"
62 | ],
63 | "trailing-comma": false,
64 | "no-output-on-prefix": true,
65 | "no-inputs-metadata-property": true,
66 | "no-inputs-metadata-property": true,
67 | "no-host-metadata-property": true,
68 | "no-input-rename": true,
69 | "no-output-rename": true,
70 | "use-lifecycle-interface": true,
71 | "use-pipe-transform-interface": true,
72 | "one-variable-per-declaration": false,
73 | "component-class-suffix": [true, "Page", "Component"],
74 | "directive-class-suffix": true,
75 | "directive-selector": [
76 | true,
77 | "attribute",
78 | "app",
79 | "camelCase"
80 | ],
81 | "component-selector": [
82 | true,
83 | "element",
84 | "app",
85 | "page",
86 | "kebab-case"
87 | ]
88 | }
89 | }
90 |
--------------------------------------------------------------------------------