Book Image

MongoDB, Express, Angular, and Node.js Fundamentals

By : Paul Oluyege
Book Image

MongoDB, Express, Angular, and Node.js Fundamentals

By: Paul Oluyege

Overview of this book

MongoDB, Express, Angular and Node.js Fundamentals is a practical guide to the tried-and-true production-ready MEAN stack, with tips and best practices. The book begins by demystifying the MEAN architecture. You’ll take a look at the features of the JavaScript libraries, technologies, and frameworks that make up a MEAN stack. With this book, you'll not only learn how to develop highly scalable, asynchronous, and event-driven APIs quickly with Express and Node.js, but you'll also be able put your full-stack skills to use by building two full-fledged MEAN applications from scratch. You’ll understand how to build a blogging application using the MEAN stack and get to grips with user authentication using MEAN. As you progress through the chapters, you’ll explore some old and new features of Angular, such as pipes, reactive forms, modules and optimizing apps, animations and unit testing, and much more. By the end of the book, you’ll get ready to take control of the MEAN stack and transform into a full-stack JavaScript developer, developing efficient web applications using Javascript technologies.
Table of Contents (9 chapters)
MongoDB, Express, Angular, and Node.js Fundamentals
Preface

Chapter 6: Testing and Optimizing Angular Applications


Activity 15: Animating the Route Transition Between the Blog Post Page and the View Post Page of the Blogging Application

  1. Import the routing module into the app.routing.module file using the following code:

    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
    import { BrowserModule } from '@angular/platform-browser';
    ………………..
    imports: [
        BrowserModule,
        BrowserAnimationsModule
      ],
  2. Create an animation.ts file using touch, and then import animation classes and define animation properties:

    touch animation.ts

    Here is the code for importing and defining animation classes:

    import {trigger,state,style,animate,transition,query,animateChild,group} from '@angular/animations';
    
    export const slideInAnimation =
      trigger('routeAnimations', [
        transition('HomePage <=> PostPage', [
          style({ position: 'relative' }),
          query(':enter, :leave', [
            style({
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%'
            })
          ]),
    //[…]
          query(':enter', animateChild()),
        ])
      ]);
  3. Update the animated route in the lazy loading ap.route.module.tsrouting configuration, as shown in the following code:

    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
    const routes: Routes = [
      {
        path: 'blog',
        loadChildren: './blog-home/blog-home.module#BlogHomeModule',
        data: { animation: 'HomePage' }
    //[…]
    
    @NgModule({
      imports: [RouterModule.forChild(routes), SharedModule, BrowserAnimationsModule],
      exports: [RouterModule]
    })
    export class AppRoutingModule { }
  4. Import the animation and router outlet of the root components class in the app.component.ts file:

    import { Component } from '@angular/core';
    import { RouterOutlet } from '@angular/router'
    import { slideInAnimation } from './animation'
    //[…]
    
    prepareRoute(outlet: RouterOutlet) {
      return outlet && outlet.activatedRouteData && outlet.activatedRouteData['animation'];
    }}
  5. Update the root component template file (app.component.html) with the following code:

    <div id="main-content" class="bg-color-gray">
      <app-header></app-header>
      <div [@routeAnimations]="prepareRoute(outlet)" class="page-container scene-main scene-main--fade_In">
        <router-outlet #outlet="outlet"></router-outlet>
        <app-footer></app-footer>
      </div>
    </div>
  6. Run the application using ng serve –o on the CLI, and then test and observe the page transition on the browser by typing in localhost:4200/blog/post in the browser. You will obtain the following output:

    Figure 6.12: Route transition animation between the Blog-Post and View-Post pages

Activity 16: Implementing Router Guard, Constant Storage, and Updating the Application Functions of the Blogging Application

File name: app.routing.module.ts
File name: article.service.ts
File name: blog-home.component.ts
File name: blog-home.component.html
Live link: http://bit.ly/2IAhQsY
File name: view-post.component.ts
Live link: http://bit.ly/2ExYdhd
File name: view-post.component.html
File name: login.component.ts
File name: login.component.html
Live link: http://bit.ly/2UaQATe
File name: register.component.ts
File name: register.component.html
File name: create.component.ts
File name: create.component.html
File name: edit.component.ts
File name: edit.component.html
  1. Define a constant for AuthService and ArticleService in the environment.ts file with local URL's as shown in the following snippet:

    export const environment = {
      production: false,
      articlesUrl: 'http://localhost:3000/articles',
      articleUrl: 'http://localhost:3000/article/',
      registerUrl: "http://localhost:3000/auth/register",
      loginUrl: "http://localhost:3000/auth/sign_in"
    };
  2. Import and declare the environment in the auth.service.ts file as shown in the following snippet:

    import { Injectable } from '@angular/core';
    import { HttpClient } from '@angular/common/http'
    import { Router } from '@angular/router'
    import { map } from 'rxjs/operators';
    import { environment } from '../../environments/environment'
    
    @Injectable({
      providedIn: 'root'
    })
    export class AuthService {
      config = environment;
  3. Import the BehaviorSubject class in the auth.service.ts file using the following command:

    import { BehaviorSubject } from 'rxjs';
  4. Declare user as an instance of the BehaviorSubject class and then observe using the Angular asObservable() method as shown in the following snippet:

    private user = new BehaviorSubject<boolean>(false);
      cast = this.user.asObservable();
  5. Write an authentication function in the auth.service.ts file to check if any tokens exist as shown in the following snippet:

      constructor(private http: HttpClient,
        private router: Router) { }
    
      public isAuthenticated(): boolean {
        const token = localStorage.getItem('currentUser');
        if (token) return true
        else return false
      }
  6. Update the login, register, and logout functions with the constant variable and then observe the behavioral variable as shown in the following snippet:

      registerUser(user) {
        return this.http.post<any>('${this.config.registerUrl}', user)
      }
    
      loginUser(user) {
        return this.http.post<any>('${this.config.loginUrl}', { 'email': user.email, 'password': user.password })
          .pipe(map(user => {
            // login successful if there's a jwt token in the response
            if (user && user.token) {
              // store user details and jwt token in local storage to keep user logged in between page refreshes
              localStorage.setItem('currentUser', JSON.stringify('JWT ' + user.token));
            }
            this.user.next(true);
            return user;
          }));
      }
    
      logoutUser() {
        // remove user from local storage to log user out
        localStorage.removeItem('currentUser');
        this.user.next(false);
        this.router.navigate(['/blog'])
      }
    }
  7. Create a new auth-guard.service.ts service file to implement the router guard as shown in the following snippet:

    import { Injectable } from '@angular/core';
    import { Router, CanActivate } from '@angular/router';
    import { AuthService } from './auth.service';
    @Injectable()
    export class AuthGuardService implements CanActivate {
      constructor(public auth: AuthService, public router: Router) {}
      canActivate(): boolean {
        if (!this.auth.isAuthenticated()) {
          this.router.navigate(['login']);
          return false;
        }
        return true;
      }
    }
  8. Apply the router guard service to the app.routing.module.ts route file as shown in the following snippet:

    import { NgModule } from '@angular/core';
    import { Routes, RouterModule,CanActivate } from '@angular/router';
    import { SharedModule } from './shared/shared/shared.module'
    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
    import { AuthGuardService as AuthGuard } from './service/auth-guard.service';
      //[…]
    
      // { path: '**', component: PageNotFoundComponent }
    ];
    
    @NgModule({
      imports: [RouterModule.forChild(routes), SharedModule, BrowserAnimationsModule],
      exports: [RouterModule]
    })
    export class AppRoutingModule { }
  9. Import and declare the environment in the article.service.ts file as shown in the following snippet:

    import { Injectable } from '@angular/core';
    import { HttpClient, HttpHeaders } from '@angular/common/http';
    import { Post } from '../posts'
    import { Observable } from 'rxjs';
    import { environment } from '../../environments/environment'
    
    @Injectable()
    
    export class ArticleService {
      config = environment;
  10. Declare the token and header in the article.service.ts file:

    article: any;
      token = JSON.parse( localStorage.getItem('currentUser') ) ;
      httpOptions:any;
    
      constructor(private http: HttpClient) { 
        this.httpOptions = new HttpHeaders({  
          'Authorization': this.token,
          'Access-Control-Allow-Origin':'*',
          'Access-Control-Allow-Methods':'PUT, POST, GET, DELETE, OPTIONS',
         });
      }
  11. Update the functions in the article.service.ts file with constant variables and headers as shown in the following snippet:

    getArticles(): Observable<Post> {
        this.article = this.http.get<Post>('${this.config.articlesUrl}');
        return this.article;
      }
    //[…]
    
      updateArticle(id: number, article: Post): Observable<Post> {
        console.log(this.token)
        return this.http.put<Post>('${this.config.articleUrl}' + id, { 'title': article.title, 'body': article.body, 'tag': article.tag, 'photo': article.photo },{
          headers: this.httpOptions
        })
      }
    }
  12. Update the blog-home component class (blog-home.component.ts), template (blog-home.component.html), and style (blog-home.component.css) using the following code snippets:

    The code for updating the blog-home.component.ts file is as follows:

    //blog-home.component.ts
    import { Component, OnInit } from '@angular/core';
    import { ArticleService } from '../service/article.service';
    import { AuthService } from '../service//auth.service';
    
    @Component({
      selector: 'app-blog-home',
      templateUrl: './blog-home.component.html',
      styleUrls: ['./blog-home.component.css']
    })
    export class BlogHomeComponent implements OnInit {
    //[…]
      }
    
      logOut() {
        this.authService.logoutUser()
      }
    
    }

    The code for updating the blog-home.component.html (template) file is as follows:

    //blog-home.component.html
    <app-title-header></app-title-header>
    <div *ngIf='isLoggedIn'>
    <a routerLink="/create" style="float: left;margin:-50px 0px 0px 100px;background-color: orangered;color: white" class="button btn">New Post</a>
    <button (click)="logOut()" style="float: right;margin:-50px 100px 0px 0px;background-color: black;color: white" class="button btn">Logout</button>
    </div>
    
    //[…]
                           
                        </article>
                    </div>
                </div>
            </div>
        </div>
    </div>

    The code snippet for the style (blog-home.component.css) file is as follows:

    .button{
    border-style: solid;
    border-width : 1px 1px 1px 1px;
    text-decoration : none;
    padding : 8px;
    font-size:12px;
    margin: 0 auto;
    }
  13. Update the view-post component class (view-post.component.ts) and the template (view-post.component.html) using the following code:

    The code for updating the view-post.component.ts file is as follows:

    //view-post.component.ts
    import { Component, OnInit } from '@angular/core';
    import { Router, ActivatedRoute } from '@angular/router';
    import { ArticleService } from '../service/article.service';
    
    @Component({
      selector: 'app-view-post',
      templateUrl: './view-post.component.html',
      styleUrls: ['./view-post.component.css']
    })
    
    export class ViewPostComponent implements OnInit {
    //[…]
              }
            );
        });
      }
    }

    The code for updating the view-post.component.html file is as follows:

        <!-- View post -->
    
            <div  class="page-container scene-main scene-main--fade_In">
                <!-- Blog post -->
                <div class="container"> 
    //[…]
                                        </p>
                                    </div>
                                    <div class="separator-line"></div>
                                </article>
                            </div>
                        </div>
                    </div>
                </div> 
               
            </div> 
  14. Update the login component class (login.component.ts) and the template (login.component.html) using the following code:

    The code for updating the login.component.ts file is as follows:

    import { Component, OnInit } from '@angular/core';
    import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms';
    import { AuthService } from '../service//auth.service';
    import { Router } from '@angular/router'
    import { first } from 'rxjs/operators';
    
    //[…]
      }
    
    }

    The code for updating the login.component.html file is as follows:

    <app-title-header></app-title-header>
    <div class="" style="padding-bottom: 3rem!important;">
      <div class="row">
        <div class="col-md-6 mx-auto">
          <!-- form card login -->
          <div class="card rounded-0">
            <h3 class="mb-0" style="text-align:center" class="mb-0">Login</h3>
            <div class="card-header">
              <a href="#" class="btn" style="float:left;margin-right:10px;color:darkblue;border: 1px solid darkblue">
                <i class="fa fa-facebook-official"></i>
                Facebook
    //[…]
    
                </div>
                <div *ngIf="success" class="alert alert-success">{{success}}</div>
                <div *ngIf="error" class="alert alert-danger">{{error}}</div>
              </form>
            </div>
            <!--/card-block-->
          </div>
          <!-- /form card login -->
    
        </div>
    
      </div>
    </div>
  15. Update the register component class (register.component.ts) and the template (register.component.html) using the following code:

    The code for updating the register.component.ts is as follows:

    import { Component, OnInit } from '@angular/core';
    import { AuthService } from '../service/auth.service';
    import { Router } from '@angular/router'
    import { Users } from '../users';
    import { first } from 'rxjs/operators';
    
    @Component({
      selector: 'app-register',
      templateUrl: './register.component.html',
      styleUrls: ['./register.component.css']
    })
    //[…]
      navigateToLogin() {
        this.router.navigate(['/login']);
      }
    }

    The code for updating the register.component.html is as follows:

    <app-title-header></app-title-header>
    <div  style="padding-bottom: 3rem!important;">
      <div class="row">
        <div class="col-md-6 mx-auto">
          <!-- form card login -->
          <div class="card rounded-0">
              <h3 class="mb-0" style="text-align:center">Register  Admin User</h3>
    //[…]
    
        </div>
    
      </div>
    </div>
  16. Update the create component class (create.component.ts) and the template (create.component.html) using the following code snippets:

    The code for updating the create.component.ts file is as follows:

    import { Component, OnInit } from '@angular/core';
    import { Posts } from '../post';
    import { ArticleService } from '../service/article.service';
    import { Router } from '@angular/router'
    import { first } from 'rxjs/operators';
    
    //[…]
      navigateToBlogHome() {
        this.router.navigate(['/blog']);
      }
    }

    The code for updating the create.component.html file is as follows:

    <app-title-header></app-title-header>
    <div style="padding-bottom: 3rem!important;">
      <div class="row">
        <div class="col-md-6 mx-auto">
          <!-- form card login -->
          <div class="card rounded-0">
            <h3 style="text-align:center" class="mb-0">Create Post</h3>
            <div class="card-header">
            </div>
            <div class="card-body">
              <form (ngSubmit)="postForm.form.valid && onSubmit()" #postForm="ngForm" novalidate>
                <div class="form-group">
                  <label for="title">Title</label>
                  <input type="text" class="form-control" id="title" [(ngModel)]="model.title" name="title" #title="ngModel"
                    [ngClass]="{ 'is-invalid': postForm.submitted && title.invalid }" required>
                  <div *ngIf="postForm.submitted && title.invalid" class="alert alert-danger">
                    Title is required
                  </div>
                </div>
    //[…]
                <div *ngIf="error" class="alert alert-danger">{{error}}</div>
              </form>
            </div>
          </div>
    
        </div>
    
      </div>
    </div>
  17. Update the edit component class (edit.component.ts) and the template (edit.component.html) using the following code snippets:

    The code for updating the edit.component.ts file is as follows:

    import { Component, OnInit } from '@angular/core';
    import { Router, ActivatedRoute } from '@angular/router';
    import { ArticleService } from '../service/article.service';
    import { Posts } from '../post';
    import { first } from 'rxjs/operators';
    
    @Component({
      selector: 'app-edit',
      templateUrl: './edit.component.html',
      styleUrls: ['./edit.component.css']
    })
    //[…]
    
      navigateToBlogHome() {
        this.router.navigate(['/blog']);
      }
    }

    The code for updating the edit.component.html file is as follows:

    <app-title-header></app-title-header>
    <div style="padding-bottom: 3rem!important;">
      <div class="row">
        <div class="col-md-6 mx-auto">
          <!-- form card login -->
          <div class="card rounded-0">
            <h3 style="text-align:center" class="mb-0">Edit Post</h3>
            <div class="card-header">
            </div>
            <div class="card-body">
              <form (ngSubmit)="postForm.form.valid && onSubmit()" #postForm="ngForm" novalidate *ngIf="article.length != 0">
                <div class="form-group">
                  <label for="title">Title</label>
                  <input type="text" class="form-control" id="title" [(ngModel)]="article.title" name="title" #title="ngModel"
                    [ngClass]="{ 'is-invalid': postForm.submitted && title.invalid }" required>
                  <div *ngIf="postForm.submitted && title.invalid" class="alert alert-danger">
                    Title is required
                  </div>
                </div>
    //[…]
              </form>
            </div>
          </div>
        </div>
      </div>
    </div>
  18. Run ng serve to test the '/blog' route before and after logging in.

    You will obtain the following output once you test the '/blog' route before logging in:

    Figure 6.13: The output obtained for the test on the '/blog' route before logging in.

    When you test the '/blog' route after logging in, you will obtain the following output:

    Figure 6.14: The output obtained for the test on the '/blog' route after logging in.

Activity 17: Performing Unit Testing on the App Root Component and Blog-Post Component

  1. Open the root component test file, app.components.spec.ts, and import the modules, as shown:

    import { TestBed, async,ComponentFixture } from '@angular/core/testing';
    import { AppComponent } from './app.component';
    import { Component, OnInit, DebugElement } from '@angular/core';
    import { RouterTestingModule } from '@angular/router/testing';
    import { RouterLinkWithHref } from '@angular/router';
    import { By } from '@angular/platform-browser';
  2. Mock the app-header, router-outlet, and app-footer components into the app.components.spec.ts file the with the following code:

    @Component({selector: 'app-header', template: ''})
    class HeaderStubComponent {}
    
    @Component({selector: 'router-outlet', template: ''})
    class RouterOutletStubComponent { }
    
    @Component({selector: 'app-footer', template: ''})
    class FooterStubComponent {}
  3. Write the suits functions for AppComponent, as shown in the following code:

    describe('AppComponent', () => {
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          imports: [ RouterTestingModule.withRoutes([])],
          declarations: [
            AppComponent,
            HeaderStubComponent,
            RouterOutletStubComponent,
            FooterStubComponent
          ]
        }).compileComponents();
      }));
  4. Write the assertion and matcher functions to evaluate true and false conditions:

    it('should create the app', async(() => {
        const fixture = TestBed.createComponent(AppComponent);
        const app = fixture.debugElement.componentInstance;
        expect(app).toBeTruthy();
      }));
      it('should have a link to /', () => {
        const fixture = TestBed.createComponent(AppComponent);
        const debugElements = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
        const index = debugElements.findIndex(de => {
          return de.properties['href'] === '/';
        });
        expect(index).toBeGreaterThanOrEqual(-1);
      });
    });
  5. Open the blog-home.component.spec.ts file (in the blog-home folder) and import the modules:

    import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing';
    import { HttpClientModule } from '@angular/common/http';
    import { Component, OnInit } from '@angular/core';
    import { BlogHomeComponent } from './blog-home.component';
    import { NO_ERRORS_SCHEMA } from '@angular/core';
    import { ArticleService } from '../service/article.service';
  6. Mock the app-title-header components in the blog-home.component.spec.ts file, as shown:

    @Component({ selector: 'app-title-header', template: '' })
    class TitleHeaderStubComponent { }
  7. Write the suits functions in the blog-home.component.spec.ts file, as shown:

    describe('BlogHomeComponent', () => {
      let component: BlogHomeComponent;
      let fixture: ComponentFixture<BlogHomeComponent>;
      let testBedService: ArticleService;
    
      beforeEach(async(() => {
        // refine the test module by declaring the test component
        TestBed.configureTestingModule({
          declarations: [BlogHomeComponent, TitleHeaderStubComponent],
          providers: [ArticleService],
          imports: [HttpClientModule],
          schemas: [NO_ERRORS_SCHEMA]
        })
          .compileComponents();
      }));
    
      
    beforeEach(() => {
    // create component and test fixture
    
        fixture = TestBed.createComponent(BlogHomeComponent);
        // AuthService provided to the TestBed
        testBedService = TestBed.get(ArticleService);
        // get test component from the fixture
    
        component = fixture.componentInstance;
        fixture.detectChanges();
  8. Write the assertion and matcher functions to evaluate true and false, as shown:

      it('should create', () => {
        expect(component).toBeTruthy();
      });
    
      it('Service injected via inject(...) and TestBed.get(...) should be the same instance',
        inject([ArticleService], (injectService: ArticleService) => {
          expect(injectService).toBe(testBedService);
        })
      );
    });
    });
  9. Run ng test in the command line. You will obtain an output similar to the following:

    Figure 6.15: Final output on e2e testing on student app

As can be seen in the preceding output, we have successfully performed e2e testing on the student app.