diff --git a/src/app/child-dev-project/schools/display-participants-count/display-participants-count.component.html b/src/app/child-dev-project/schools/display-participants-count/display-participants-count.component.html
new file mode 100644
index 0000000000..6f5d22364d
--- /dev/null
+++ b/src/app/child-dev-project/schools/display-participants-count/display-participants-count.component.html
@@ -0,0 +1,3 @@
+
+ {{ participantRelationsCount() }}
+
diff --git a/src/app/child-dev-project/schools/display-participants-count/display-participants-count.component.spec.ts b/src/app/child-dev-project/schools/display-participants-count/display-participants-count.component.spec.ts
new file mode 100644
index 0000000000..64d2a15b2f
--- /dev/null
+++ b/src/app/child-dev-project/schools/display-participants-count/display-participants-count.component.spec.ts
@@ -0,0 +1,62 @@
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+
+import { DisplayParticipantsCountComponent } from "./display-participants-count.component";
+import { ChildrenService } from "../../children/children.service";
+import { School } from "../model/school";
+import { ChildSchoolRelation } from "../../children/model/childSchoolRelation";
+
+describe("DisplayParticipantsCountComponent", () => {
+ let component: DisplayParticipantsCountComponent;
+ let fixture: ComponentFixture;
+
+ let mockChildrenService: jasmine.SpyObj;
+
+ const childSchoolRelations: ChildSchoolRelation[] = [
+ new ChildSchoolRelation("r-1"),
+ new ChildSchoolRelation("r-2"),
+ new ChildSchoolRelation("r-3"),
+ ];
+
+ beforeEach(async () => {
+ mockChildrenService = jasmine.createSpyObj(["queryActiveRelationsOf"]);
+ mockChildrenService.queryActiveRelationsOf.and.resolveTo(
+ childSchoolRelations,
+ );
+
+ await TestBed.configureTestingModule({
+ imports: [DisplayParticipantsCountComponent],
+ providers: [{ provide: ChildrenService, useValue: mockChildrenService }],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(DisplayParticipantsCountComponent);
+ component = fixture.componentInstance;
+ component.entity = new School("s-1");
+ fixture.detectChanges();
+ });
+
+ it("should create", () => {
+ expect(component).toBeTruthy();
+ });
+
+ it("should count correct number of active students for school", async () => {
+ expect(component.participantRelationsCount()).toBeNull();
+ await component.ngOnChanges();
+ expect(component.participantRelationsCount()).toBeDefined();
+ expect(component.participantRelationsCount()).toBe(3);
+ });
+
+ it("should handle empty response from ChildrenService", async () => {
+ mockChildrenService.queryActiveRelationsOf.and.resolveTo([]);
+ expect(component.participantRelationsCount()).toBeNull();
+ await component.ngOnChanges();
+ expect(component.participantRelationsCount()).toBeDefined();
+ expect(component.participantRelationsCount()).toBe(0);
+ });
+
+ it("should handle error response from ChildrenService", async () => {
+ mockChildrenService.queryActiveRelationsOf.and.rejectWith(new Error());
+ expect(component.participantRelationsCount()).toBeNull();
+ await component.ngOnChanges();
+ expect(component.participantRelationsCount()).toBeNull();
+ });
+});
diff --git a/src/app/child-dev-project/schools/display-participants-count/display-participants-count.component.ts b/src/app/child-dev-project/schools/display-participants-count/display-participants-count.component.ts
new file mode 100644
index 0000000000..25481090fd
--- /dev/null
+++ b/src/app/child-dev-project/schools/display-participants-count/display-participants-count.component.ts
@@ -0,0 +1,44 @@
+import { Component, OnChanges, signal, WritableSignal } from "@angular/core";
+import { CommonModule } from "@angular/common";
+import { ChildrenService } from "../../children/children.service";
+import { ViewDirective } from "../../../core/entity/default-datatype/view.directive";
+import { DynamicComponent } from "../../../core/config/dynamic-components/dynamic-component.decorator";
+import { ChildSchoolRelation } from "../../children/model/childSchoolRelation";
+import { LoggingService } from "../../../core/logging/logging.service";
+
+@DynamicComponent("DisplayParticipantsCount")
+@Component({
+ selector: "app-display-participants-count",
+ standalone: true,
+ imports: [CommonModule],
+ templateUrl: "./display-participants-count.component.html",
+})
+export class DisplayParticipantsCountComponent
+ extends ViewDirective
+ implements OnChanges
+{
+ participantRelationsCount: WritableSignal = signal(null);
+
+ constructor(
+ private _childrenService: ChildrenService,
+ private _loggingService: LoggingService,
+ ) {
+ super();
+ }
+
+ override async ngOnChanges(): Promise {
+ super.ngOnChanges();
+
+ return this._childrenService
+ .queryActiveRelationsOf("school", this.entity.getId())
+ .then((relations: ChildSchoolRelation[]) => {
+ this.participantRelationsCount.set(relations.length);
+ })
+ .catch((reason) => {
+ this._loggingService.error(
+ "Could not calculate participantRelationsCount, error response from ChildrenService." +
+ reason,
+ );
+ });
+ }
+}
diff --git a/src/app/child-dev-project/schools/schools-components.ts b/src/app/child-dev-project/schools/schools-components.ts
index 69357634fb..170780ce10 100644
--- a/src/app/child-dev-project/schools/schools-components.ts
+++ b/src/app/child-dev-project/schools/schools-components.ts
@@ -29,4 +29,11 @@ export const schoolsComponents: ComponentTuple[] = [
(c) => c.SchoolBlockComponent,
),
],
+ [
+ "DisplayParticipantsCount",
+ () =>
+ import(
+ "./display-participants-count/display-participants-count.component"
+ ).then((c) => c.DisplayParticipantsCountComponent),
+ ],
];
diff --git a/src/app/core/config/config-fix.ts b/src/app/core/config/config-fix.ts
index ea1144d0d1..611643676f 100644
--- a/src/app/core/config/config-fix.ts
+++ b/src/app/core/config/config-fix.ts
@@ -309,6 +309,7 @@ export const defaultJsonConfig = {
"entity": "School",
"columns": [
"name",
+ { id: "DisplayParticipantsCount", viewComponent: "DisplayParticipantsCount", label: $localize`Children` },
"privateSchool",
"language"
],
diff --git a/src/app/core/entity/default-datatype/view.directive.ts b/src/app/core/entity/default-datatype/view.directive.ts
index cb155918e3..c65852f02c 100644
--- a/src/app/core/entity/default-datatype/view.directive.ts
+++ b/src/app/core/entity/default-datatype/view.directive.ts
@@ -12,6 +12,12 @@ export abstract class ViewDirective implements OnChanges {
/** indicating that the value is not in its original state, so that components can explain this to the user */
isPartiallyAnonymized: boolean;
+ /**
+ * Attention:
+ * When content is loaded async in your child component, you need to manually trigger the change detection
+ * See: https://angularindepth.com/posts/1054/here-is-what-you-need-to-know-about-dynamic-components-in-angular#ngonchanges
+ *
+ */
ngOnChanges() {
this.isPartiallyAnonymized =
this.entity?.anonymized &&
diff --git a/src/app/core/entity/model/entity.ts b/src/app/core/entity/model/entity.ts
index 50fd05b385..1a05718d53 100644
--- a/src/app/core/entity/model/entity.ts
+++ b/src/app/core/entity/model/entity.ts
@@ -43,7 +43,7 @@ export type EntityConstructor = (new (
*
* Entity classes do not deal with database actions, use {@link EntityMapperService} with its find/save/delete functions.
*
- * Do not use the Entity class directly. Instead implement your own Entity types, writing classes that extend "Entity".
+ * Do not use the Entity class directly. Instead, implement your own Entity types, writing classes that extend "Entity".
* A How-To Guide on how to implement your own types is available:
* - [How to Create a new Entity Type]{@link /additional-documentation/how-to-guides/create-a-new-entity-type.html}
*/