setup project

This commit is contained in:
thomas
2022-11-03 15:37:58 +01:00
parent 8002a3f9ed
commit cd95f9f774
74 changed files with 2263 additions and 1653 deletions

View File

@@ -0,0 +1,73 @@
import { AsyncPipe, NgFor } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
inject,
OnInit,
} from '@angular/core';
import { Store } from '@ngrx/store';
import { loadActivities } from './store/activity/activity.actions';
import { ActivityType } from './store/activity/activity.model';
import { selectActivities } from './store/activity/activity.selectors';
import { loadStatuses } from './store/status/status.actions';
import { selectAllTeachersByActivityType } from './store/status/status.selectors';
import { loadUsers } from './store/user/user.actions';
@Component({
selector: 'app-root',
standalone: true,
imports: [NgFor, AsyncPipe],
template: `
<h1>Activity Board</h1>
<section>
<div class="card" *ngFor="let activity of activities$ | async">
<h2>Activity Name: {{ activity.name }}</h2>
<p>Main teacher: {{ activity.teacher.name }}</p>
<span>All teachers available for : {{ activity.type }} are</span>
<ul>
<li
*ngFor="
let teacher of getAllTeachersForActivityType$(activity.type)
| async
"
>
{{ teacher.name }}
</li>
</ul>
</div>
</section>
`,
styles: [
`
section {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 2px;
}
.card {
display: flex;
flex-direction: column;
border: solid;
border-width: 1px;
border-color: black;
padding: 2px;
}
`,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent implements OnInit {
private store = inject(Store);
activities$ = this.store.select(selectActivities);
ngOnInit(): void {
this.store.dispatch(loadActivities());
this.store.dispatch(loadUsers());
this.store.dispatch(loadStatuses());
}
getAllTeachersForActivityType$ = (type: ActivityType) =>
this.store.select(selectAllTeachersByActivityType(type));
}

View File

@@ -0,0 +1,14 @@
import { createAction, props } from '@ngrx/store';
import { Activity } from './activity.model';
export const loadActivities = createAction('[AppComponent] Load Activitys');
export const loadActivitiesSuccess = createAction(
'[Activity Effect] Load Activitys Success',
props<{ activities: Activity[] }>()
);
export const loadActivitiesFailure = createAction(
'[Activity] Load Activitys Failure',
props<{ error: any }>()
);

View File

@@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { catchError, concatMap, map } from 'rxjs/operators';
import * as ActivityActions from './activity.actions';
import { ActivityService } from './activity.service';
@Injectable()
export class ActivityEffects {
loadActivities$ = createEffect(() => {
return this.actions$.pipe(
ofType(ActivityActions.loadActivities),
concatMap(() =>
this.ActivityService.fetchActivities().pipe(
map((activities) =>
ActivityActions.loadActivitiesSuccess({ activities })
),
catchError((error) =>
of(ActivityActions.loadActivitiesFailure({ error }))
)
)
)
);
});
constructor(
private actions$: Actions,
private ActivityService: ActivityService
) {}
}

View File

@@ -0,0 +1,55 @@
import {
incrementalNumber,
rand,
randFirstName,
randText,
} from '@ngneat/falso';
export const activityType = [
'Sport',
'Sciences',
'History',
'Maths',
'Physics',
] as const;
export type ActivityType = typeof activityType[number];
export interface Person {
id: number;
name: string;
}
export interface Activity {
id: number;
name: string;
type: ActivityType;
teacher: Person;
}
const factoryPerson = incrementalNumber();
export const randPerson = () => ({
id: factoryPerson(),
name: randFirstName(),
});
const factoryActivity = incrementalNumber();
export const randActivity = (): Activity => ({
id: factoryActivity(),
name: randText(),
type: rand(activityType),
teacher: randPerson(),
});
export const activities: Activity[] = [
randActivity(),
randActivity(),
randActivity(),
randActivity(),
randActivity(),
randActivity(),
randActivity(),
randActivity(),
randActivity(),
];

View File

@@ -0,0 +1,25 @@
import { createReducer, on } from '@ngrx/store';
import * as ActivityActions from './activity.actions';
import { Activity } from './activity.model';
export const activityFeatureKey = 'Activity';
export interface ActivityState {
activities: Activity[];
}
export const initialState: ActivityState = {
activities: [],
};
export const activityReducer = createReducer(
initialState,
on(ActivityActions.loadActivitiesSuccess, (state, { activities }) => ({
...state,
activities,
})),
on(ActivityActions.loadActivitiesFailure, (state) => ({
state,
activities: [],
}))
);

View File

@@ -0,0 +1,10 @@
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { activityFeatureKey, ActivityState } from './activity.reducer';
export const selectActivityState =
createFeatureSelector<ActivityState>(activityFeatureKey);
export const selectActivities = createSelector(
selectActivityState,
(state) => state.activities
);

View File

@@ -0,0 +1,10 @@
import { Injectable } from '@angular/core';
import { map, timer } from 'rxjs';
import { activities } from './activity.model';
@Injectable({
providedIn: 'root',
})
export class ActivityService {
fetchActivities = () => timer(500).pipe(map(() => activities));
}

View File

@@ -0,0 +1,9 @@
import { createAction, props } from '@ngrx/store';
import { Status } from './status.model';
export const loadStatuses = createAction('[Status] Load Statuses');
export const loadStatusesSuccess = createAction(
'[Status] Load Statuses Success',
props<{ statuses: Status[] }>()
);

View File

@@ -0,0 +1,49 @@
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { combineLatest, concatMap, map } from 'rxjs';
import { selectActivities } from '../activity/activity.selectors';
import { selectUser } from '../user/user.selectors';
import * as StatusActions from './status.actions';
import { Status } from './status.model';
@Injectable()
export class StatusEffects {
loadStatuses$ = createEffect(() => {
return this.actions$.pipe(
ofType(StatusActions.loadStatuses),
concatMap(() =>
combineLatest([
this.store.select(selectUser),
this.store.select(selectActivities),
]).pipe(
map(([user, activities]): Status[] => {
if (user?.isAdmin) {
return activities.reduce(
(status: Status[], activity): Status[] => {
const index = status.findIndex(
(s) => s.name === activity.type
);
if (index === -1) {
return [
...status,
{ name: activity.type, teachers: [activity.teacher] },
];
} else {
status[index].teachers.push(activity.teacher);
return status;
}
},
[]
);
}
return [];
}),
map((statuses) => StatusActions.loadStatusesSuccess({ statuses }))
)
)
);
});
constructor(private actions$: Actions, private store: Store) {}
}

View File

@@ -0,0 +1,6 @@
import { ActivityType, Person } from '../activity/activity.model';
export interface Status {
name: ActivityType;
teachers: Person[];
}

View File

@@ -0,0 +1,29 @@
import { createReducer, on } from '@ngrx/store';
import { ActivityType, Person } from '../activity/activity.model';
import * as StatusActions from './status.actions';
import { Status } from './status.model';
export const statusFeatureKey = 'status';
export interface StatusState {
statuses: Status[];
teachersMap: Map<ActivityType, Person[]>;
}
export const initialState: StatusState = {
statuses: [],
teachersMap: new Map(),
};
export const statusReducer = createReducer(
initialState,
on(StatusActions.loadStatusesSuccess, (state, { statuses }): StatusState => {
const map = new Map();
statuses.forEach((s) => map.set(s.name, s.teachers));
return {
...state,
statuses,
teachersMap: map,
};
})
);

View File

@@ -0,0 +1,17 @@
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { ActivityType } from '../activity/activity.model';
import { statusFeatureKey, StatusState } from './status.reducer';
export const selectStatusState =
createFeatureSelector<StatusState>(statusFeatureKey);
export const selectStatuses = createSelector(
selectStatusState,
(state) => state.statuses
);
export const selectAllTeachersByActivityType = (name: ActivityType) =>
createSelector(
selectStatusState,
(state) => state.teachersMap.get(name) ?? []
);

View File

@@ -0,0 +1,14 @@
import { createAction, props } from '@ngrx/store';
import { User } from './user.model';
export const loadUsers = createAction('[User] Load Users');
export const loadUsersSuccess = createAction(
'[User] Load Users Success',
props<{ user: User }>()
);
export const loadUsersFailure = createAction(
'[User] Load Users Failure',
props<{ error: any }>()
);

View File

@@ -0,0 +1,23 @@
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { catchError, concatMap, map } from 'rxjs/operators';
import * as UserActions from './user.actions';
import { UserService } from './user.service';
@Injectable()
export class UserEffects {
loadUsers$ = createEffect(() => {
return this.actions$.pipe(
ofType(UserActions.loadUsers),
concatMap(() =>
this.userService.fetchUser().pipe(
map((user) => UserActions.loadUsersSuccess({ user })),
catchError((error) => of(UserActions.loadUsersFailure({ error })))
)
)
);
});
constructor(private actions$: Actions, private userService: UserService) {}
}

View File

@@ -0,0 +1,15 @@
import { randFirstName, randLastName, randText } from '@ngneat/falso';
export interface User {
firstname: string;
lastname: string;
isAdmin: boolean;
apiKey: string;
}
export const user: User = {
firstname: randFirstName(),
lastname: randLastName(),
isAdmin: true,
apiKey: randText(),
};

View File

@@ -0,0 +1,19 @@
import { createReducer, on } from '@ngrx/store';
import * as UserActions from './user.actions';
import { User } from './user.model';
export const userFeatureKey = 'user';
export interface UserState {
user?: User;
}
export const initialState: UserState = {
user: undefined,
};
export const userReducer = createReducer(
initialState,
on(UserActions.loadUsersSuccess, (state, { user }) => ({ ...state, user })),
on(UserActions.loadUsersFailure, (state) => ({ ...state, user: undefined }))
);

View File

@@ -0,0 +1,9 @@
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { userFeatureKey, UserState } from './user.reducer';
export const selectUserState = createFeatureSelector<UserState>(userFeatureKey);
export const selectUser = createSelector(
selectUserState,
(state) => state.user
);

View File

@@ -0,0 +1,10 @@
import { Injectable } from '@angular/core';
import { map, timer } from 'rxjs';
import { user } from './user.model';
@Injectable({
providedIn: 'root',
})
export class UserService {
fetchUser = () => timer(500).pipe(map(() => user));
}