Fix and improve custom titles, add course support, fix things

Implemented hasManyThrough repository with count() method
This commit is contained in:
Norbi Peti 2022-05-07 20:19:08 +02:00
parent 38b06f54e6
commit d076e0a013
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
20 changed files with 293 additions and 100 deletions

View file

@ -1,5 +1,5 @@
import { repository } from '@loopback/repository';
import { CourseRepository } from '../repositories';
import { CourseRepository, SubjectRepository, UserRepository } from '../repositories';
import { arg, ID, Int, mutation, query, resolver } from '@loopback/graphql';
import { Course } from '../models';
import { CourseUpdateInput } from '../graphql-types/input/course-update.input';
@ -9,13 +9,20 @@ import { CourseList } from '../graphql-types/course';
@resolver(of => Course)
export class CourseResolver {
constructor(
@repository('CourseRepository') private courseRepo: CourseRepository
@repository('CourseRepository') private courseRepo: CourseRepository,
@repository('SubjectRepository') private subjectRepo: SubjectRepository,
@repository('UserRepository') private userRepo: UserRepository
) {
}
@query(returns => CourseList)
async courses(@arg('offset', returns => Int) offset: number, @arg('limit', returns => Int) limit: number): Promise<ListResponse<Course>> {
return listResponse(this.courseRepo, offset, limit, CourseList);
async coursesBySubject(@arg('subject', returns => ID) subject: number, @arg('offset', returns => Int) offset: number, @arg('limit', returns => Int) limit: number): Promise<ListResponse<Course>> {
return listResponse(this.subjectRepo.courses(subject), offset, limit, CourseList);
}
@query(returns => CourseList)
async coursesByUser(@arg('user', returns => ID) user: number, @arg('offset', returns => Int) offset: number, @arg('limit', returns => Int) limit: number): Promise<ListResponse<Course>> {
return listResponse(this.userRepo.courses(user), offset, limit, CourseList);
}
@query(returns => Course)

View file

@ -13,7 +13,7 @@ export class SubjectResolver {
@query(returns => SubjectList)
async subjects(@arg('offset', returns => Int) offset: number, @arg('limit', returns => Int) limit: number): Promise<ListResponse<Subject>> {
return listResponse(this.subjectRepo, offset, limit, SubjectList);
return await listResponse(this.subjectRepo, offset, limit, SubjectList);
}
@query(returns => Subject)

View file

@ -75,7 +75,7 @@ export class UserResolver {
}
@authorized()
@query(returns => [User])
@query(returns => UserList)
async users(@arg('limit', returns => Int) limit: number, @arg('offset', returns => Int) offset: number) {
return await listResponse(this.userRepository, offset, limit, UserList);
}

View file

@ -14,7 +14,9 @@ export interface ListResponse<T> {
list: T[];
}
export async function listResponse<T extends Entity, U extends ListResponse<Partial<T>>>(repo: DefaultCrudRepository<T, number>, offset: number, limit: number, listType: ClassType<U>) {
export type ListRepository<T extends Entity> = Pick<DefaultCrudRepository<T, number>, 'find' | 'count'>;
export async function listResponse<T extends Entity, U extends ListResponse<Partial<T>>>(repo: ListRepository<T>, offset: number, limit: number, listType: ClassType<U>) {
const list = new listType();
list.list = await repo.find({offset, limit});
list.count = (await repo.count()).count;

View file

@ -1,29 +0,0 @@
import { constrainWhere, Count, DefaultHasManyRepository, Entity, EntityCrudRepository, Where } from '@loopback/repository';
import { Options } from '@loopback/repository/src/common-types';
import { HasManyRepository } from '@loopback/repository/src/relations/has-many/has-many.repository';
import { InclusionResolver } from '@loopback/repository/src/relations/relation.types';
export interface CustomHasManyRepositoryFactory<Target extends Entity,
ForeignKeyType,
> {
/**
* Invoke the function to obtain HasManyRepository.
*/
(fkValue: ForeignKeyType): CustomHasManyRepository<Target>;
/**
* Use `resolver` property to obtain an InclusionResolver for this relation.
*/
inclusionResolver: InclusionResolver<Entity, Target>;
}
export interface CustomHasManyRepository<Target extends Entity> extends HasManyRepository<Target> {
count(where?: Where<Target>, options?: Options): Promise<Count>;
}
export class DefaultCustomHasManyRepository<TEntity extends Entity, TID, TRepository extends EntityCrudRepository<TEntity, TID>> extends DefaultHasManyRepository<TEntity, TID, TRepository> implements CustomHasManyRepository<TEntity> {
async count(where?: Where<TEntity>, options?: Options) {
const targetRepository = await this.getTargetRepository();
return targetRepository.count(constrainWhere(where, this.constraint), options);
}
}

View file

@ -0,0 +1,149 @@
import {
constrainFilter,
constrainWhere,
Count,
DataObject,
DefaultHasManyRepository,
DefaultHasManyThroughRepository,
DefaultTransactionalRepository,
Entity,
EntityCrudRepository,
HasManyRepository,
HasManyThroughRepository,
InclusionResolver,
Options,
Where
} from '@loopback/repository';
import { Getter } from '@loopback/core';
export interface CustomHasManyRepositoryFactory<Target extends Entity,
ForeignKeyType,
> {
/**
* Invoke the function to obtain HasManyRepository.
*/
(fkValue: ForeignKeyType): CustomHasManyRepository<Target>;
/**
* Use `resolver` property to obtain an InclusionResolver for this relation.
*/
inclusionResolver: InclusionResolver<Entity, Target>;
}
export interface CustomHasManyRepository<Target extends Entity> extends HasManyRepository<Target> {
count(where?: Where<Target>, options?: Options): Promise<Count>;
}
export class DefaultCustomHasManyRepository<TEntity extends Entity, TID, TRepository extends EntityCrudRepository<TEntity, TID>> extends DefaultHasManyRepository<TEntity, TID, TRepository> implements CustomHasManyRepository<TEntity> {
async count(where?: Where<TEntity>, options?: Options) {
const targetRepository = await this.getTargetRepository();
return targetRepository.count(constrainWhere(where, this.constraint), options);
}
}
export interface CustomHasManyThroughRepositoryFactory<TargetEntity extends Entity, TargetID, ThroughEntity extends Entity, SourceID> {
(fkValue: SourceID): CustomHasManyThroughRepository<TargetEntity, TargetID, ThroughEntity>;
/**
* Use `resolver` property to obtain an InclusionResolver for this relation.
*/
inclusionResolver: InclusionResolver<Entity, TargetEntity>;
}
export interface CustomHasManyThroughRepository<Target extends Entity, TID, Through extends Entity> extends HasManyThroughRepository<Target, TID, Through> {
count(where?: Where<Target>, options?: Options): Promise<Count>;
}
export class DefaultCustomHasManyThroughRepository<TargetEntity extends Entity,
TargetID,
TargetRepository extends EntityCrudRepository<TargetEntity, TargetID>,
ThroughEntity extends Entity,
ThroughID,
ThroughRepository extends EntityCrudRepository<ThroughEntity, ThroughID>,
> extends DefaultHasManyThroughRepository<TargetEntity, TargetID, TargetRepository, ThroughEntity, ThroughID, ThroughRepository>
implements CustomHasManyThroughRepository<TargetEntity, TargetID, ThroughEntity> {
async count(where?: Where<TargetEntity>, options?: Options): Promise<Count> {
const targetRepository = await this.getTargetRepository();
const throughRepository = await this.getThroughRepository();
const sourceConstraint = this.getThroughConstraintFromSource();
const throughInstances = await throughRepository.find(
constrainFilter(undefined, sourceConstraint),
options?.throughOptions,
);
const targetConstraint =
this.getTargetConstraintFromThroughModels(throughInstances);
return targetRepository.count(
constrainWhere(where, targetConstraint),
options,
);
}
}
export class CustomCrudRepository<T extends Entity, ID, Relations extends object = {}> extends DefaultTransactionalRepository<T, ID, Relations> {
createCustomHasManyRepositoryFactoryFor<Target extends Entity, TargetID, ForeignKeyType extends Target[ForeignKeyName], ForeignKeyName extends keyof Target>(
relationName: string,
targetRepositoryGetter: Getter<EntityCrudRepository<Target, TargetID>>,
fkName: ForeignKeyName
): CustomHasManyRepositoryFactory<Target, ForeignKeyType | undefined> {
const origEntities = this.createHasManyRepositoryFactoryFor(relationName, targetRepositoryGetter);
this.registerInclusionResolver(relationName, origEntities.inclusionResolver);
const entities = function(fkValue: ForeignKeyType | undefined) {
return new DefaultCustomHasManyRepository(targetRepositoryGetter, {[fkName]: fkValue} as unknown as DataObject<Target>);
};
entities.inclusionResolver = origEntities.inclusionResolver;
return entities;
}
createCustomHasManyThroughFactoryFor<Target extends Entity, Through extends Entity, ThroughID, SourceKeyName extends keyof Through, TargetKeyName extends keyof Through>(
relationName: string,
targetRepoGetter: Getter<EntityCrudRepository<Target, Through[TargetKeyName]>>,
throughRepoGetter: Getter<EntityCrudRepository<Through, ThroughID>>,
sourceKeyName: SourceKeyName,
targetKeyName: TargetKeyName
): CustomHasManyThroughRepositoryFactory<Target, Through[TargetKeyName] | undefined, Through, Through[SourceKeyName] | undefined> {
const origEntities = this.createHasManyThroughRepositoryFactoryFor(relationName, targetRepoGetter, throughRepoGetter);
this.registerInclusionResolver(relationName, origEntities.inclusionResolver);
const entities = function(fkValue: Through[SourceKeyName] | undefined) {
function getTargetConstraintFromThroughModels(
throughInstances: Through[],
): DataObject<Target> {
const keys = throughInstances.map(instance => instance.getId());
return {id: keys.length > 1 ? {inq: keys} : keys[0]} as unknown as DataObject<Target>;
}
function getTargetKeys(throughInstances: Through[]): Through[TargetKeyName][] {
return throughInstances.map(instance => instance[targetKeyName]);
}
function getThroughConstraintFromSource(): DataObject<Through> {
return {[sourceKeyName]: fkValue} as unknown as DataObject<Through>;
}
function getTargetIds(targetInstances: Target[]): Through[TargetKeyName][] {
return targetInstances.map(target => target.getId());
}
function getThroughConstraintFromTarget(fkValues: Through[TargetKeyName][]): DataObject<Through> {
return {[targetKeyName]: fkValues.length > 1 ? {inq: fkValues} : fkValues[0]} as unknown as DataObject<Through>;
}
return new DefaultCustomHasManyThroughRepository<Target,
Through[TargetKeyName],
EntityCrudRepository<Target, Through[TargetKeyName]>,
Through,
ThroughID,
EntityCrudRepository<Through, ThroughID>>(
targetRepoGetter,
throughRepoGetter,
getTargetConstraintFromThroughModels,
getTargetKeys,
getThroughConstraintFromSource,
getTargetIds,
getThroughConstraintFromTarget,
);
};
entities.inclusionResolver = origEntities.inclusionResolver;
return entities;
}
}

View file

@ -1,14 +1,12 @@
import { Getter, inject } from '@loopback/core';
import { DataObject, DefaultCrudRepository, repository } from '@loopback/repository';
import { repository } from '@loopback/repository';
import { DatabaseDataSource } from '../datasources';
import { Course, Subject, SubjectRelations } from '../models';
import { UserRepository } from './user.repository';
import { CourseRepository } from './course.repository';
import { CustomHasManyRepositoryFactory, DefaultCustomHasManyRepository } from './CustomHasManyRepository';
import { CustomCrudRepository, CustomHasManyRepositoryFactory } from './custom-has-many-repository';
export class SubjectRepository extends DefaultCrudRepository<Subject,
typeof Subject.prototype.id,
SubjectRelations> {
export class SubjectRepository extends CustomCrudRepository<Subject, typeof Subject.prototype.id, SubjectRelations> {
public readonly courses: CustomHasManyRepositoryFactory<Course, typeof Subject.prototype.id>;
@ -16,12 +14,6 @@ export class SubjectRepository extends DefaultCrudRepository<Subject,
@inject('datasources.database') dataSource: DatabaseDataSource, @repository.getter('UserRepository') protected userRepositoryGetter: Getter<UserRepository>, @repository.getter('CourseRepository') protected courseRepositoryGetter: Getter<CourseRepository>,
) {
super(Subject, dataSource);
const origCourses = this.createHasManyRepositoryFactoryFor('courses', courseRepositoryGetter,);
this.registerInclusionResolver('courses', origCourses.inclusionResolver);
const courses = function(fkValue: number | undefined) {
return new DefaultCustomHasManyRepository(courseRepositoryGetter, {subject_id: fkValue} as DataObject<Course>);
};
courses.inclusionResolver = origCourses.inclusionResolver;
this.courses = courses;
this.courses = this.createCustomHasManyRepositoryFactoryFor('courses', courseRepositoryGetter, 'subjectId');
}
}

View file

@ -1,16 +1,17 @@
import { inject, Getter } from '@loopback/core';
import { repository, HasManyThroughRepositoryFactory, DefaultTransactionalRepository } from '@loopback/repository';
import { Getter, inject } from '@loopback/core';
import { repository } from '@loopback/repository';
import { DatabaseDataSource } from '../datasources';
import { User, UserRelations, Course, CourseUser } from '../models';
import { Course, CourseUser, User, UserRelations } from '../models';
import { SubjectRepository } from './subject.repository';
import { CourseUserRepository } from './course-user.repository';
import { CourseRepository } from './course.repository';
import { CustomCrudRepository, CustomHasManyThroughRepositoryFactory } from './custom-has-many-repository';
export class UserRepository extends DefaultTransactionalRepository<User,
export class UserRepository extends CustomCrudRepository<User,
typeof User.prototype.id,
UserRelations> {
public readonly courses: HasManyThroughRepositoryFactory<Course, typeof Course.prototype.id,
public readonly courses: CustomHasManyThroughRepositoryFactory<Course, typeof Course.prototype.id,
CourseUser,
typeof User.prototype.id>;
@ -18,7 +19,6 @@ export class UserRepository extends DefaultTransactionalRepository<User,
@inject('datasources.database') dataSource: DatabaseDataSource, @repository.getter('SubjectRepository') protected subjectRepositoryGetter: Getter<SubjectRepository>, @repository.getter('CourseUserRepository') protected courseUserRepositoryGetter: Getter<CourseUserRepository>, @repository.getter('CourseRepository') protected courseRepositoryGetter: Getter<CourseRepository>,
) {
super(User, dataSource);
this.courses = this.createHasManyThroughRepositoryFactoryFor('courses', courseRepositoryGetter, courseUserRepositoryGetter,);
this.registerInclusionResolver('courses', this.courses.inclusionResolver);
this.courses = this.createCustomHasManyThroughFactoryFor('courses', courseRepositoryGetter, courseUserRepositoryGetter, 'userId', 'courseId');
}
}

View file

@ -3,7 +3,7 @@ import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { Observable } from 'rxjs';
import { filter, map, shareReplay } from 'rxjs/operators';
import { LoginService } from './auth/login.service';
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Route, Router, Routes } from '@angular/router';
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Params, Route, Router, Routes } from '@angular/router';
import { RouteData } from './app-routing.module';
import { Title } from '@angular/platform-browser';
@ -27,7 +27,7 @@ export class AppComponent implements OnInit {
pageTitle: string;
private activeRouteTitle: string;
private activeComponentVars: object;
constructor(private breakpointObserver: BreakpointObserver, public loginService: LoginService,
private router: Router, private activeRoute: ActivatedRoute, private title: Title) {
@ -43,7 +43,9 @@ export class AppComponent implements OnInit {
item.title = 'NOTFOUND';
}
}
this.setPageTitle();
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
this.setPageTitle();
});
}
getRoutes(data: { path: string, routes: Routes }[]): { routes: Routes, path: string, currentRoute: Route }[] {
@ -82,32 +84,44 @@ export class AppComponent implements OnInit {
return routeParts;
}
const data = snapshot.data as RouteData;
if (data.title && snapshot.url.length) {
if (data.title) {
routeParts.push({
title: data.title,
url: snapshot.url[0].path
url: snapshot.url.length ? snapshot.url[0].path : undefined,
params: snapshot.params
});
}
}
return routeParts;
}
getTitleParts(): string[] {
const routeParts = this.getRouteSegments(this.activeRoute.snapshot);
if (!routeParts.length) {
return [];
}
function replaceVars(replacements: object, title: string): string {
return Object.keys(replacements).reduce((pv, cv) => pv.replace(':' + cv, replacements[cv]), title);
}
let titleParts = routeParts.reverse().map(part => replaceVars(part.params, part.title));
if (this.activeComponentVars) {
titleParts = titleParts.map(part => replaceVars(this.activeComponentVars, part));
}
return titleParts.map(part => part.startsWith(':') ? '~' : part);
}
setPageTitle(): void {
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
const routeParts = this.getRouteSegments(this.activeRoute.snapshot);
if (!routeParts.length) {
this.pageTitle = 'Szakdolgozat';
return this.title.setTitle('Szakdolgozat');
}
const titleParts = routeParts.reverse().map(part => part.title);
if (this.activeRouteTitle) { // TODO: Get title parts from parent components even if parent wasn't opened
titleParts[titleParts.length - 1] = this.activeRouteTitle;
}
let pageTitle = titleParts.reduce((partA, partI) => `${partA} > ${partI}`);
this.pageTitle = pageTitle;
pageTitle += ` | Szakdolgozat`;
this.title.setTitle(pageTitle);
});
const titleParts = this.getTitleParts();
if (!titleParts.length) {
this.pageTitle = 'Szakdolgozat';
return this.title.setTitle('Szakdolgozat');
}
let pageTitle = titleParts.reduce((partA, partI) => `${partA} > ${partI}`);
this.pageTitle = pageTitle;
pageTitle += ` | Szakdolgozat`;
this.title.setTitle(pageTitle);
}
async logout(): Promise<void> {
@ -119,23 +133,25 @@ export class AppComponent implements OnInit {
return this.menu.filter(item => item.requiredRole === 'admin' ? this.loginService.user?.isAdmin : true); // TODO: Roles
}
routeActivated($event: any): void {
async routeActivated($event: any): Promise<void> {
if (this.isCustomTitleComponent($event)) {
this.activeRouteTitle = $event.getPageTitle();
const title = $event.getPageTitleVars();
this.activeComponentVars = title instanceof Promise ? await title : title;
} else {
this.activeRouteTitle = null;
this.activeComponentVars = null;
}
this.setPageTitle();
}
isCustomTitleComponent(obj: any): obj is CustomTitleComponent {
return obj?.getPageTitle instanceof Function;
return obj?.getPageTitleVars instanceof Function;
}
}
type MenuItem = { path: string, requiredRole: 'admin', title?: string }; // TODO: Role
type RouteSegment = { title: string, url: string };
type RouteSegment = { title: string, url: string, params: Params };
export interface CustomTitleComponent {
getPageTitle(): string;
getPageTitleVars(): object | Promise<object>;
}

View file

@ -0,0 +1,33 @@
query CourseListBySubject($subject: ID!, $limit: Int!, $offset: Int!) {
coursesBySubject(subject: $subject, limit: $limit, offset: $offset) {
count
list {
id
alias
semester
}
}
}
query CourseListByUser($user: ID!, $limit: Int!, $offset: Int!) {
coursesByUser(user: $user, limit: $limit, offset: $offset) {
list {
id
alias
semester
}
count
}
}
query Course($id: ID!) {
course(id: $id) {
id
semester
alias
}
}
mutation EditCourse($input: CourseUpdateInput!) {
courseUpdate(course: $input)
}

View file

@ -16,10 +16,13 @@ mutation Logout {
query UserList($limit: Int!, $offset: Int!) {
users(limit: $limit, offset: $offset) {
id
name
email
isAdmin
list {
id
name
email
isAdmin
}
count
}
}

View file

@ -55,6 +55,11 @@ export class EditComponent<T extends HasID, QT extends QueryResult<T>, UT extend
} else {
this.item = {} as T;
this.creating = true;
if (!this.createMutation) {
alert('Nem hozható létre új elem ezzel a tipussal');
await this.router.navigate(['..'], {relativeTo: this.route});
return;
}
}
this.isLoading = false;
}

View file

@ -3,15 +3,18 @@ import { PageEvent } from '@angular/material/paginator';
import { PaginationData } from '../../utility/pagination-data';
import { Router } from '@angular/router';
import { Query } from 'apollo-angular';
import { ListVariableType } from '../../utility/types';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent<T extends { id: number }, U extends { [entityName: string]: { count: number; list: T[] } }> implements OnInit {
export class ListComponent<T extends { id: number }, U extends { [entityName: string]: { count: number; list: T[] } },
V extends object | undefined> implements OnInit {
@Input() gql: Query<U, { limit: number; offset: number }>;
@Input() gql: Query<U, ListVariableType<V>>;
@Input() queryVariables: V;
@Input() itemType: T; // TODO: Remove
@Input() columns: { title: string, prop: keyof T }[];
@Input() allowNew = false;
@ -36,7 +39,8 @@ export class ListComponent<T extends { id: number }, U extends { [entityName: st
async getItems(limit: number, page: number): Promise<void> {
try {
this.loading = true;
const {data} = await this.gql.fetch({limit, offset: (page - 1) * limit}).toPromise(); // TODO: Watch
const vars = (this.queryVariables === undefined ? {} : this.queryVariables) as V extends object ? V : {};
const {data} = await this.gql.fetch({...vars, limit, offset: (page - 1) * limit}).toPromise(); // TODO: Watch
const key = Object.keys(data).filter(k => k !== '__typename')[0];
this.paginationData.total = data[key].count;
this.paginationData.page = page;

View file

@ -1,4 +1,4 @@
<!-- <app-edit [beforeSubmit]="beforeSubmit" [fields]="[
<app-edit [beforeSubmit]="beforeSubmit" [gql]="itemGQL" [itemType]="itemType" [updateMutation]="editGQL" [fields]="[
{title: 'Félév', name: 'semester'},
{title: 'Név', name: 'alias'}
]"></app-edit> -->
]"></app-edit>

View file

@ -1,18 +1,27 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { CustomTitleComponent } from '../../../../app.component';
import { Course, CourseGQL, EditCourseGQL, SubjectGQL } from '../../../../services/graphql';
@Component({
selector: 'app-course-edit',
templateUrl: './course-edit.component.html',
styleUrls: ['./course-edit.component.css']
})
export class CourseEditComponent implements OnInit {
export class CourseEditComponent implements OnInit, CustomTitleComponent {
subjectId: string;
itemType: Course;
beforeSubmit = () => ({subjectId: +this.route.snapshot.params.subjectId});
constructor(private route: ActivatedRoute) {
constructor(private route: ActivatedRoute, public subjectGQL: SubjectGQL, public itemGQL: CourseGQL, public editGQL: EditCourseGQL) {
this.subjectId = route.snapshot.params.subjectId;
}
ngOnInit(): void {
}
getPageTitleVars(): object | Promise<object> {
return this.subjectGQL.fetch({id: this.subjectId}).toPromise().then(subject => ({subjectName: subject.data.subject.name}));
}
}

View file

@ -1,4 +1,4 @@
<app-list [itemType]="itemType" allowNew="true" [columns]="[
<app-list [itemType]="itemType" allowNew="true" [gql]="listGQL" [queryVariables]="{subject: subjectId}" [columns]="[
{prop: 'semester', title: 'Félév'},
{prop: 'alias', title: 'Név'}
]"></app-list>

View file

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { CustomTitleComponent } from '../../../../app.component';
import { Course, SubjectGQL } from '../../../../services/graphql';
import { Course, CourseListBySubjectGQL, SubjectGQL } from '../../../../services/graphql';
@Component({
selector: 'app-courses',
@ -12,14 +12,14 @@ export class CourseListComponent implements OnInit, CustomTitleComponent {
subjectId: string;
itemType: Course;
constructor(route: ActivatedRoute, public listGQL: SubjectGQL) {
constructor(route: ActivatedRoute, public subjectGQL: SubjectGQL, public listGQL: CourseListBySubjectGQL) {
this.subjectId = route.snapshot.params.subjectId;
}
ngOnInit(): void {
}
getPageTitle(): string {
return 'Custom title'; //TODO: SubjectGQL
getPageTitleVars(): object | Promise<object> {
return this.subjectGQL.fetch({id: this.subjectId}).toPromise().then(subject => ({subjectName: subject.data.subject.name}));
}
}

View file

@ -9,10 +9,10 @@ import { CourseListComponent } from './subject-edit/courses/course-list/course-l
import { CourseEditComponent } from './subject-edit/courses/course-edit/course-edit.component';
const routes: Routes = [
{path: '', component: SubjectListComponent, data: {title: 'Tárgyak'} as RouteData},
{path: '', component: SubjectListComponent, data: {title: ''} as RouteData},
{path: ':id', component: SubjectEditComponent, data: {title: 'Szerkesztés'}},
{
path: ':subjectId/courses', data: {title: 'Kurzusok'}, children: [
path: ':subjectId/courses', data: {title: ':subjectName'}, children: [
{path: ':id', component: CourseEditComponent, data: {title: 'Szerkesztés'} as RouteData},
{path: '', component: CourseListComponent, data: {title: 'Kurzusok'}}
]

View file

@ -7,7 +7,7 @@ import { SharedComponentsModule } from '../shared-components/shared-components.m
import { UserEditComponent } from './user-edit/user-edit.component';
const routes: Routes = [
{path: '', component: UserListComponent, data: {title: 'Felhasználók'} as RouteData},
{path: '', component: UserListComponent, data: {title: ''} as RouteData},
{path: ':id', component: UserEditComponent, data: {title: 'Szerkesztés'}}
];

View file

@ -3,3 +3,5 @@ import { Scalars } from '../services/graphql';
export type HasID = { id: Scalars['ID'] };
export type QueryResult<T extends HasID> = { [entityName: string]: T };
export type MutationInput<T extends Partial<U>, U extends HasID> = { input: T };
export type ListInput = { limit: number; offset: number };
export type ListVariableType<V extends object | undefined> = (V extends object ? V : {}) & ListInput;