Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: add app routing and lazy load home feature #23

Merged
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/app/_core/services/youtube-data.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { TestBed } from '@angular/core/testing';

import { YoutubeDataService } from './youtube-data.service';
import {HttpClientTestingModule} from '@angular/common/http/testing';

describe('YoutubeDataService', () => {
let service: YoutubeDataService;

beforeEach(() => {
TestBed.configureTestingModule({});
TestBed.configureTestingModule({
imports: [HttpClientTestingModule]
});
service = TestBed.inject(YoutubeDataService);
});

Expand Down
2 changes: 1 addition & 1 deletion src/app/_core/services/youtube-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { PlaylistItemListResponse, YtVideoItem } from '../models';
import { BaseUrlService } from '../services/base-url.service';
import { BaseUrlService } from './base-url.service';

@Injectable({
providedIn: 'root',
Expand Down
15 changes: 15 additions & 0 deletions src/app/_shared/components/error/error.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<div *ngIf="errorType === 'something-went-wrong'" class="flex flex-col items-center justify-center">
<img
class="w-1/3"
src="assets/undraw_bug_fixing.svg" alt="Person fixing bugs">
<h2 class="text-lg mt-2">Aaaaah! Something went wrong</h2>
<p class="mt-2 text-sm text-gray-400">
Brace yourself till we get the error fixed.<br>
You may also refresh the page or try again later.
</p>
</div>

<ng-container *ngIf="errorType === 'not-found'">
<img
src="assets/undraw_page_not_found.svg" alt="Page not found">
</ng-container>
11 changes: 11 additions & 0 deletions src/app/_shared/components/error/error.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {Component, Input} from '@angular/core';

export type ErrorType = 'not-found' | 'something-went-wrong';

@Component({
selector: 'app-ui-error',
templateUrl: './error.component.html'
})
export class ErrorComponent {
@Input() errorType!: ErrorType;
}
19 changes: 19 additions & 0 deletions src/app/_shared/components/error/error.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {Input, NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ErrorComponent} from './error.component';

const DECLARATIONS = [
ErrorComponent
];

/**
* This module provides a set of components and directives
* to render nice error content when needed
*/
@NgModule({
imports: [CommonModule],
declarations: DECLARATIONS,
exports: DECLARATIONS
})
export class ErrorModule {
}
4 changes: 2 additions & 2 deletions src/app/_shared/footer/footer.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="bg-gray-100 overflow-hidden">
<footer class="bg-gray-100 overflow-hidden">
<div class="max-w-7xl py-6 px-4 sm:px-6 lg:px-8 mx-auto">
© 2021 ngMorocco | Angular in darija
</div>
</div>
</footer>
6 changes: 3 additions & 3 deletions src/app/_shared/navbar/navbar.component.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<div class="relative bg-white overflow-hidden">
<header class="relative bg-white overflow-hidden">
<div class="max-w-7xl mx-auto z-10 py-3 lg:py-5 px-4 sm:px-6 lg:px-8">
<nav class="relative flex items-center justify-between sm:h-10 lg:justify-start" aria-label="Global">
<div class="flex items-center flex-grow flex-shrink-0 lg:flex-grow-0">
<a href="#">
<a routerLink="/">
<img class="h-10 w-auto" src="assets/logo-small.png">
</a>
</div>
Expand All @@ -11,4 +11,4 @@
</div>
</nav>
</div>
</div>
</header>
3 changes: 2 additions & 1 deletion src/app/_shared/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NavbarComponent } from './navbar/navbar.component';
import { FooterComponent } from './footer/footer.component';
import {RouterModule} from "@angular/router";

const DECLARATIONS = [NavbarComponent, FooterComponent];

@NgModule({
declarations: DECLARATIONS,
imports: [CommonModule],
imports: [CommonModule, RouterModule],
exports: DECLARATIONS,
})
export class SharedModule {}
13 changes: 12 additions & 1 deletion src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [];
const routes: Routes = [
{
path: '',
loadChildren: () => import('./home/home.module').then(m => m.HomeModule)
},
// Redirect all unknown paths to home
{
path: '**',
redirectTo: '',
pathMatch: 'full'
}
];
@NgModule({
imports: [RouterModule.forRoot(routes, {
initialNavigation: 'enabled'
Expand Down
24 changes: 8 additions & 16 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
<app-navbar></app-navbar>
<ng-container *ngIf="ytVideos$ | async as ytVideo">
<div class="relative bg-gray-200 overflow-hidden">
<div class="max-w-7xl mx-auto py-8 px-4 sm:px-6 lg:px-8">
<app-video-banner
[ytVideo]="ytVideo.lastVideo || null"
></app-video-banner>
</div>
</div>
<div class="relative bg-white overflow-hidden">
<main class="mx-auto max-w-7xl pb-8 pt-6 px-4 sm:px-6 lg:px-8">
<app-video-listing
[ytVideos]="ytVideo.videoList || null"
></app-video-listing>
<div class="flex flex-col h-screen">
<app-navbar></app-navbar>
<div class="flex flex-col flex-1">
<main class="flex-1">
<router-outlet></router-outlet>
</main>
<app-footer></app-footer>
</div>
</ng-container>
<app-footer></app-footer>
</div>

16 changes: 2 additions & 14 deletions src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import {HttpClientTestingModule} from '@angular/common/http/testing';

describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule
HttpClientTestingModule
],
declarations: [
AppComponent
Expand All @@ -19,17 +20,4 @@ describe('AppComponent', () => {
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});

it(`should have as title 'ngx-darija'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('ngx-darija');
});

it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain('ngx-darija app is running!');
});
});
25 changes: 1 addition & 24 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,4 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { YtVideoItem } from './_core/models/models';
import { ServerStateService } from './_core/services/server-state.service';
import { YoutubeDataService } from './_core/services/youtube-data.service';

interface HomeVideos {
lastVideo: YtVideoItem;
videoList: YtVideoItem[];
}
import {Component, ViewEncapsulation} from '@angular/core';

@Component({
selector: 'app-root',
Expand All @@ -17,17 +7,4 @@ interface HomeVideos {
encapsulation: ViewEncapsulation.None
})
export class AppComponent {
ytVideos$: Observable<HomeVideos>;

constructor(
private youtubeDataService: YoutubeDataService,
private serverStateService: ServerStateService
) {
this.ytVideos$ = this.youtubeDataService.getAngularInDarijaVideos().pipe(
map((res) => {
return { lastVideo: res[res.length - 1], videoList: res };
}),
this.serverStateService.hydrate('videos')
);
}
}
2 changes: 0 additions & 2 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
} from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeModule } from './home/home.module';
import { SharedModule } from './_shared/shared.module';

@NgModule({
Expand All @@ -17,7 +16,6 @@ import { SharedModule } from './_shared/shared.module';
AppRoutingModule,
HttpClientModule,
SharedModule,
HomeModule,
],
bootstrap: [AppComponent],
})
Expand Down
34 changes: 34 additions & 0 deletions src/app/home/home-base/home-base.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div class="h-full flex flex-col">
<ng-container *ngIf="ytVideos$ | async as ytVideo">
<!-- Main content when data fetched -->
<!-- Main content when data fetched -->
<!-- Main content when data fetched -->
<!-- Main content when data fetched -->
<ng-container *ngIf="(ytVideo?.videoList || []).length > 0">
<div class="relative bg-gray-200 overflow-hidden">
<div class="max-w-7xl mx-auto py-8 px-4 sm:px-6 lg:px-8">
<app-video-banner
[ytVideo]="ytVideo.lastVideo || null"
></app-video-banner>
</div>
</div>
<div class="relative bg-white overflow-hidden">
<main class="mx-auto max-w-7xl pb-8 pt-6 px-4 sm:px-6 lg:px-8">
<app-video-listing
[ytVideos]="ytVideo.videoList || null"
></app-video-listing>
</main>
</div>
</ng-container>
<!-- Error Message when request failed-->
<!-- Error Message when request failed-->
<!-- Error Message when request failed-->
<!-- Error Message when request failed-->
<div class="relative bg-gray-200 overflow-hidden h-full flex justify-center items-center"
*ngIf="ytVideo.videoList?.length === 0">
<div>
<app-ui-error [errorType]="'something-went-wrong'"></app-ui-error>
</div>
</div>
</ng-container>
</div>
96 changes: 96 additions & 0 deletions src/app/home/home-base/home-base.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import {HomeBaseComponent} from './home-base.component';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {YtVideoItem} from '../../_core/models';
import {Observable, of} from 'rxjs';
import {YoutubeDataService} from '../../_core/services/youtube-data.service';
import {ServerStateService} from '../../_core/services/server-state.service';
import {Component, Input} from '@angular/core';
import {By} from '@angular/platform-browser';


const YtVideoMock: YtVideoItem[] = [
{
videoId: 'dummy-id',
title: 'Angular in Darija is awesome',
description: 'Angular in Darija well explain in Darija',
thumbnailUrl: 'https://www.youtube.com/watch?v=ToaZsdRCc0s',
publishedAt: new Date().toString()
},
{
videoId: 'dummy-id-1',
title: 'Angular in Darija is awesome part 2',
description: 'Angular in Darija well explain in Darija',
thumbnailUrl: 'https://www.youtube.com/watch?v=ToaZsdRCc0s',
publishedAt: new Date().toString()
},
];

@Component({
selector: 'app-video-banner',
template: ''
})
export class VideoBannerStubComponent {
@Input() ytVideo: YtVideoItem | null = null;
}

@Component({
selector: 'app-video-listing',
template: ''
})
export class AppVideoListingStubComponent {
@Input() ytVideos: YtVideoItem[] = [];
}

@Component({
selector: 'app-ui-error',
template: 'Error'
})
export class ErrorStubComponent {
@Input() errorType!: string;
}

describe('HomeBaseComponent', () => {
let component: HomeBaseComponent;
let fixture: ComponentFixture<HomeBaseComponent>;
let youtubeDataService: any;
let serverStateServiceStub: Partial<ServerStateService>;
serverStateServiceStub = {
hydrate(key: string): (obs: Observable<any>) => Observable<any> {
return obs => {
return obs;
};
}
};
beforeEach(async () => {
Mubramaj marked this conversation as resolved.
Show resolved Hide resolved
// Emulate call to `getAngularInDarijaVideos` and return mock value
youtubeDataService = jasmine.createSpyObj('YoutubeDataService', ['getAngularInDarijaVideos']);
youtubeDataService.getAngularInDarijaVideos.and.returnValue(of(YtVideoMock));
await TestBed.configureTestingModule({
providers: [
{provide: YoutubeDataService, useValue: youtubeDataService},
{provide: ServerStateService, useValue: serverStateServiceStub}],
declarations: [HomeBaseComponent, AppVideoListingStubComponent,
VideoBannerStubComponent,
ErrorStubComponent]
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(HomeBaseComponent);
component = fixture.componentInstance;
});

it('should create', () => {
fixture.detectChanges();
expect(component).toBeTruthy();
});

it('should pipe into service observable and resolve with homeVideos', (done) => {
fixture.detectChanges();
component?.ytVideos$?.subscribe(result => {
expect(result.lastVideo.videoId).toEqual(YtVideoMock[1].videoId);
expect(result.videoList.length).toEqual(2);
done();
});
});
});
Loading