09 Feb 2020
cat: Angular 2+
0 Comments

Laravel API and Angular Client Tutorial – Part 4 Client Get Photos and Videos

Introduction

In the last few parts, we had created the authentication API, get the photos and videos, and was able to have the Angular app login work. Now we can update the client app to show the photos or videos. In this tutorial we show the photos and videos in an infinite scrolling page. Lastly, we will update the app to only show the posts when the client is actually logged in.

HTTP Interceptor

Before we create our service to fetch the posts, we will create an HTTP Interceptor. What this does is act like a middleware to the HTTP requests. With this, we can check if the access token exists and add it to the requests without having to do it on all HTTP calls that we do in an Angular application. You can read more about this at https://angular.io/guide/http#http-interceptors.

The HTTP interceptor is a service so we can run this command to create one.

ng generate service services/AppHttpInterceptor --skip-tests

src\app\services\app-http-interceptor.service.ts

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
import { LocalStorageService } from 'angular-2-local-storage';

@Injectable()
export class AppHttpInterceptor implements HttpInterceptor {

  constructor(private storage: LocalStorageService) {
  }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    const accessToken = this.storage.get('accessToken');

    if (!req.headers.has('Authorization') && accessToken) {
      const request = req.clone({
        headers: req.headers.set('Authorization', 'Bearer ' + accessToken)
      });
      return next.handle(request);
    }

    return next.handle(req);
  }
}

In our HTTP Interceptor, we checked if our local storage has the access token and add it to the request headers.

Then to register the HTTP interceptor, we update the add an entry to the providers array in  our module.

src\app\app.module.ts

providers: [
  { provide: HTTP_INTERCEPTORS, useClass: AppHttpInterceptor, multi: true },
],

Getting the Data

Now we create another service to get the photos and videos. To do that we can run this command.

ng generate service services/Post --skip-tests

src\app\services\post.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from 'src/environments/environment';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class PostService {

  constructor(private http: HttpClient) { }

  public getPosts(page: number): Observable<any> {
    return this.http.get(environment.apiUrl + '/posts?include=user&page=' + page)
      .pipe(map((response: any) => {

        const posts = [];

        for (const p of response.data) {
          p.user = response.included.find(u => u.id === p.relationships.user.data.id);
          posts.push(p);
        }

        response.data = posts;
        return response;
      }));
  }
}

In the post service we not just get the photos and videos, we get the users as well but it is in the included array in the response. It is not directly under the each post object so we have to map it to the post objects when we get the data.

Now we can use this service in our src\app\components\home\home.component.ts.

import { Component, OnInit, HostListener } from '@angular/core';
import { PostService } from 'src/app/services/post.service';
import { environment } from 'src/environments/environment';

@Component({
  templateUrl: './home.component.html'
})
export class HomeComponent implements OnInit {

  posts: any[] = [];
  isLoading = false;
  page = 1;
  last = 1;
  fileUrl = '';

  constructor(
    private postService: PostService
  ) {
    this.fileUrl = environment.fileUrl;
  }

  ngOnInit() {
    this.loadPosts();
  }

  loadPosts() {
    this.isLoading = true;
    this.postService.getPosts(this.page)
      .subscribe((response: any) => {
        for (const p of response.data) {
          this.posts.push(p);
        }
        this.last = response.meta.pagination.total_pages;
        this.isLoading = false;
      });
  }

}

Notice in our constructor, we use the have updated fileUrl with the value from the environment.ts. We also have page and last property to keep track of the pagination which we will use for the infinite scroll feature.

src\app\components\home\home.component.html

<div class="row">
    <div class="col-md-6 offset-md-3">
        <div class="card mb-3"
             *ngFor="let p of posts">
            <video controls="true"
                   *ngIf="p.attributes.file.includes('.mp4'); else elseBlock"
                   [src]="p.attributes.file"></video>
            <ng-template #elseBlock>
                <img [src]="fileUrl + '/' + p.attributes.file"
                     class="card-img-top"
                     [alt]="p.attributes.title">
            </ng-template>
            <div class="card-body">
                <h5 class="card-title">{{ p.attributes.title }}</h5>
                <p class="card-text">
                    <span>by {{ p.user.attributes.name }}</span>
                    <br>
                    <small>{{ p.attributes.created_at }}</small>
                </p>
            </div>
        </div>
    </div>
</div>
<div class="text-center"
     *ngIf="isLoading">
    <div class="spinner-border"
         role="status">
        <span class="sr-only">Loading...</span>
    </div>
</div>

In our template, we kept it simple. We just check whether the file ends with .mp4 and use a video tag, otherwise use img tag.

Laravel API and Angular Client Tutorial – Part 3 Client Get Photos and Videos

 

Infinite Scroll

We will implement an infinite scroll to our app. Basically the pages will be loaded when the user scrolls down to the bottom of the page.

First is to import HostListener annotation from angular/core. We can add this annotations to methods and call it when an event is triggered which in our case is window:scroll.

src\app\components\home\home.component.ts

import { Component, HostListener, OnInit } from '@angular/core';
@HostListener('window:scroll', ['$event'])
onWindowScroll() {
  if (window.innerHeight + window.scrollY === document.body.scrollHeight &&
    !this.isLoading &&
    this.page < this.last
  ) {
    this.page++;
    this.loadPosts();
  }
}

If added a check to make sure we don’t call the loadPosts unnecessarily.

Checking Authentication

Since GET /api/posts is a protected resource, logging out and loading the posts will respond with error. Ideally we put this logic to the root component and redirect to just homepage but our app is pretty small and loads the content in homepage as well. So we need to check if there is a logged in user in the homepage.

Let us add an isAuthenticated property and update it when the user has logged-in or logged-out then we can show or hide elements.

Here is the full src\app\components\home\home.component.ts.

import { Component, OnInit, HostListener } from '@angular/core';
import { PostService } from 'src/app/services/post.service';
import { environment } from 'src/environments/environment';
import { AuthService } from 'src/app/services/auth.service';

@Component({
  templateUrl: './home.component.html'
})
export class HomeComponent implements OnInit {

  posts: any[] = [];
  isLoading = false;
  page = 1;
  last = 1;
  isAuthenticated = false;
  fileUrl = '';

  constructor(
    private authService: AuthService,
    private postService: PostService
  ) {
    this.fileUrl = environment.fileUrl;
  }

  ngOnInit() {
    this.isAuthenticated = this.authService.isAuthenticated;
    this.authService.onLogout.subscribe(b => {
      this.isAuthenticated = b;
      this.posts = [];
      this.page = 1;
      this.last = 1;
    });
    this.loadPosts();
  }

  loadPosts() {
    if (!this.isAuthenticated) {
      return;
    }

    this.isLoading = true;
    this.postService.getPosts(this.page)
      .subscribe((response: any) => {
        for (const p of response.data) {
          this.posts.push(p);
        }
        this.last = response.meta.pagination.total_pages;
        this.isLoading = false;
      });
  }


  @HostListener('window:scroll', ['$event'])
  onWindowScroll() {
    if (window.innerHeight + window.scrollY === document.body.scrollHeight &&
      !this.isLoading &&
      this.page < this.last
    ) {
      this.page++;
      this.loadPosts();
    }
  }
}

First we declared the new isAuthenticated property, then initialized it in the ngOnInit method using the injected AuthService‘s property isAuthenticated.

Then AuthService offers an onLogout event which we can subscribe to and update the isAuthenticated property.

Additionally, we can add this at the top of our home template.

src\app\components\home\home.component.html

<div class="row"
     *ngIf="!isAuthenticated">
    <div class="col-md-6 offset-md-3">
        <h1>Please login first</h1>
    </div>
</div>

 

Laravel API and Angular Client Tutorial – Part 3 Client Get Photos and Videos

That is it for this tutorial. Thanks for reading!

References

Be the first to write a comment.

Post A Comment