Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
MongoDB, Express, Angular, and Node.js Fundamentals

You're reading from   MongoDB, Express, Angular, and Node.js Fundamentals Become a MEAN master and rule the world of web applications

Arrow left icon
Product type Paperback
Published in Mar 2019
Publisher
ISBN-13 9781789808735
Length 362 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Paul Oluyege Paul Oluyege
Author Profile Icon Paul Oluyege
Paul Oluyege
Arrow right icon
View More author details
Toc

Table of Contents (9) Chapters Close

MongoDB, Express, Angular, and Node.js Fundamentals
Preface
1. Introduction to the MEAN Stack FREE CHAPTER 2. Developing RESTful APIs to Perform CRUD Operations 3. Beginning Frontend Development with Angular CLI 4. The MEAN Stack Security 5. Angular Declarables, Bootstrapping, and Modularity 6. Testing and Optimizing Angular Applications Appendix

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.

lock icon The rest of the chapter is locked
arrow left Previous Section
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image