feat(challenge 7): begin ngrx notification work

This commit is contained in:
thomas laforge
2022-11-22 22:25:27 +01:00
parent d774abf3b1
commit cc1c6e8b26
25 changed files with 596 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
import { Component } from '@angular/core';
import { StudentComponent } from './student.component';
import { TeacherComponent } from './teacher.component';
@Component({
standalone: true,
imports: [TeacherComponent, StudentComponent],
selector: 'app-root',
template: `
<teacher></teacher>
<student></student>
`,
styles: [
`
:host {
display: flex;
gap: 20px;
}
`,
],
})
export class AppComponent {}

View File

@@ -0,0 +1,27 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, tap, timer } from 'rxjs';
import { randStudent } from '../model/student.model';
import { randTeacher } from '../model/teacher.model';
@Injectable({ providedIn: 'root' })
export class PushService {
private notificationSubject = new BehaviorSubject<any>(undefined);
notification$ = this.notificationSubject.asObservable();
init() {
this.startTeacherNotification();
this.startStudentNotification();
}
private startTeacherNotification() {
timer(0, 4000)
.pipe(tap(() => this.notificationSubject.next(randTeacher())))
.subscribe();
}
private startStudentNotification() {
timer(1000, 3000)
.pipe(tap(() => this.notificationSubject.next(randStudent())))
.subscribe();
}
}

View File

@@ -0,0 +1,31 @@
import { Injectable } from '@angular/core';
import { filter } from 'rxjs';
import { PushService } from '../backend/push.service';
import { Push } from '../model/push.model';
import { isStudent } from '../model/student.model';
import { isTeacher } from '../model/teacher.model';
import { StudentStore } from './student.store';
import { TeacherStore } from './teacher.store';
@Injectable({ providedIn: 'root' })
export class NotificationService {
constructor(
private pushService: PushService,
private teacherStore: TeacherStore,
private studentStore: StudentStore
) {}
init() {
this.pushService.notification$
.pipe(filter(Boolean))
.subscribe((notification: Push) => {
console.log(notification);
if (isTeacher(notification)) {
this.teacherStore.addOne(notification);
}
if (isStudent(notification)) {
this.studentStore.addOne(notification);
}
});
}
}

View File

@@ -0,0 +1,23 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { Student } from '../model/student.model';
@Injectable({
providedIn: 'root',
})
export class StudentStore {
private students = new BehaviorSubject<Student[]>([]);
students$ = this.students.asObservable();
addAll(students: Student[]) {
this.students.next(students);
}
addOne(student: Student) {
this.students.next([...this.students.value, student]);
}
deleteOne(id: number) {
this.students.next(this.students.value.filter((s) => s.id !== id));
}
}

View File

@@ -0,0 +1,23 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { Teacher } from '../model/teacher.model';
@Injectable({
providedIn: 'root',
})
export class TeacherStore {
private teachers = new BehaviorSubject<Teacher[]>([]);
teachers$ = this.teachers.asObservable();
addAll(teachers: Teacher[]) {
this.teachers.next(teachers);
}
addOne(teacher: Teacher) {
this.teachers.next([...this.teachers.value, teacher]);
}
deleteOne(id: number) {
this.teachers.next(this.teachers.value.filter((t) => t.id !== id));
}
}

View File

@@ -0,0 +1,5 @@
export type PushType = 'teacher' | 'student';
export interface Push {
type: PushType;
}

View File

@@ -0,0 +1,28 @@
import {
incrementalNumber,
randFirstName,
randLastName,
randWord,
} from '@ngneat/falso';
import { Push } from './push.model';
export interface Student extends Push {
id: number;
firstname: string;
lastname: string;
school: string;
}
const factoryStudent = incrementalNumber();
export const randStudent = (): Student => ({
id: factoryStudent(),
firstname: randFirstName(),
lastname: randLastName(),
school: randWord(),
type: 'student',
});
export const isStudent = (notif: Push): notif is Student => {
return notif.type === 'student';
};

View File

@@ -0,0 +1,37 @@
import {
incrementalNumber,
rand,
randFirstName,
randLastName,
} from '@ngneat/falso';
import { Push } from './push.model';
export const subject = [
'Sciences',
'History',
'English',
'Maths',
'Sport',
] as const;
export type Subject = typeof subject[number];
export interface Teacher extends Push {
id: number;
firstname: string;
lastname: string;
subject: Subject;
}
const factoryTeacher = incrementalNumber();
export const randTeacher = () => ({
id: factoryTeacher(),
firstname: randFirstName(),
lastname: randLastName(),
subject: rand(subject),
type: 'teacher',
});
export const isTeacher = (notif: Push): notif is Teacher => {
return notif.type === 'teacher';
};

View File

@@ -0,0 +1,32 @@
/* eslint-disable @angular-eslint/component-selector */
import { AsyncPipe, NgFor } from '@angular/common';
import { Component } from '@angular/core';
import { StudentStore } from './data-access/student.store';
@Component({
standalone: true,
imports: [NgFor, AsyncPipe],
selector: 'student',
template: `
<h3>STUDENTS</h3>
<div *ngFor="let student of students$ | async">
{{ student.firstname }}
</div>
`,
styles: [
`
:host {
display: block;
width: fit-content;
height: fit-content;
border: 1px solid red;
padding: 4px;
}
`,
],
})
export class StudentComponent {
students$ = this.studentStore.students$;
constructor(private studentStore: StudentStore) {}
}

View File

@@ -0,0 +1,31 @@
import { AsyncPipe, NgFor } from '@angular/common';
import { Component } from '@angular/core';
import { TeacherStore } from './data-access/teacher.store';
@Component({
standalone: true,
imports: [NgFor, AsyncPipe],
selector: 'teacher',
template: `
<h3>TEACHERS</h3>
<div *ngFor="let teacher of teacher$ | async">
{{ teacher.firstname }}
</div>
`,
styles: [
`
:host {
display: block;
width: fit-content;
height: fit-content;
border: 1px solid red;
padding: 4px;
}
`,
],
})
export class TeacherComponent {
teacher$ = this.teacherStore.teachers$;
constructor(private teacherStore: TeacherStore) {}
}

View File

@@ -0,0 +1,3 @@
export const environment = {
production: true,
};

View File

@@ -0,0 +1,16 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false,
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>NgrxNotification</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@@ -0,0 +1,32 @@
import { APP_INITIALIZER, enableProdMode, inject } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { of } from 'rxjs';
import { AppComponent } from './app/app.component';
import { PushService } from './app/backend/push.service';
import { NotificationService } from './app/data-access/notification.service';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
bootstrapApplication(AppComponent, {
providers: [
{
provide: APP_INITIALIZER,
multi: true,
useFactory: () => {
inject(PushService).init();
return () => of(true);
},
},
{
provide: APP_INITIALIZER,
multi: true,
useFactory: () => {
inject(NotificationService).init();
return () => of(true);
},
},
],
}).catch((err) => console.error(err));

View File

@@ -0,0 +1,52 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes recent versions of Safari, Chrome (including
* Opera), Edge on the desktop, and iOS and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

@@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */