Skip to content

Commit 10d5f3d

Browse files
101577: Ensure renderComponentInstance is called in ngOnChanges if the component is not initialised yet
1 parent 0559e1e commit 10d5f3d

3 files changed

Lines changed: 59 additions & 29 deletions

File tree

src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { By } from '@angular/platform-browser';
1414
import { RouterTestingModule } from '@angular/router/testing';
1515
import { getCollectionEditRoute } from '../../../../../collection-page/collection-page-routing-paths';
1616
import { LinkService } from '../../../../../core/cache/builders/link.service';
17+
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
18+
import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock';
1719

1820
describe('CollectionAdminSearchResultGridElementComponent', () => {
1921
let component: CollectionAdminSearchResultGridElementComponent;
@@ -45,7 +47,8 @@ describe('CollectionAdminSearchResultGridElementComponent', () => {
4547
providers: [
4648
{ provide: TruncatableService, useValue: mockTruncatableService },
4749
{ provide: BitstreamDataService, useValue: {} },
48-
{ provide: LinkService, useValue: linkService }
50+
{ provide: LinkService, useValue: linkService },
51+
{ provide: ThemeService, useValue: getMockThemeService() },
4952
]
5053
})
5154
.compileComponents();

src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { CommunitySearchResult } from '../../../../../shared/object-collection/s
1616
import { Community } from '../../../../../core/shared/community.model';
1717
import { getCommunityEditRoute } from '../../../../../community-page/community-page-routing-paths';
1818
import { LinkService } from '../../../../../core/cache/builders/link.service';
19+
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
20+
import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock';
1921

2022
describe('CommunityAdminSearchResultGridElementComponent', () => {
2123
let component: CommunityAdminSearchResultGridElementComponent;
@@ -47,7 +49,8 @@ describe('CommunityAdminSearchResultGridElementComponent', () => {
4749
providers: [
4850
{ provide: TruncatableService, useValue: mockTruncatableService },
4951
{ provide: BitstreamDataService, useValue: {} },
50-
{ provide: LinkService, useValue: linkService }
52+
{ provide: LinkService, useValue: linkService },
53+
{ provide: ThemeService, useValue: getMockThemeService() },
5154
],
5255
schemas: [NO_ERRORS_SCHEMA]
5356
})

src/app/shared/theme-support/themed.component.ts

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import {
1010
ChangeDetectorRef,
1111
OnChanges
1212
} from '@angular/core';
13-
import { hasValue, isNotEmpty } from '../empty.util';
14-
import { from as fromPromise, Observable, of as observableOf, Subscription } from 'rxjs';
13+
import { hasNoValue, hasValue, isNotEmpty } from '../empty.util';
14+
import { combineLatest, from as fromPromise, Observable, of as observableOf, Subscription } from 'rxjs';
1515
import { ThemeService } from './theme.service';
1616
import { catchError, switchMap, map } from 'rxjs/operators';
1717
import { GenericConstructor } from '../../core/shared/generic-constructor';
@@ -25,6 +25,7 @@ export abstract class ThemedComponent<T> implements OnInit, OnDestroy, OnChanges
2525
@ViewChild('vcr', { read: ViewContainerRef }) vcr: ViewContainerRef;
2626
protected compRef: ComponentRef<T>;
2727

28+
protected lazyLoadObs: Observable<any>;
2829
protected lazyLoadSub: Subscription;
2930
protected themeSub: Subscription;
3031

@@ -43,50 +44,73 @@ export abstract class ThemedComponent<T> implements OnInit, OnDestroy, OnChanges
4344
protected abstract importUnthemedComponent(): Promise<any>;
4445

4546
ngOnChanges(changes: SimpleChanges): void {
46-
// if an input or output has changed
47-
if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) {
48-
this.connectInputsAndOutputs();
47+
if (hasNoValue(this.compRef)) {
48+
// sometimes the component has not been initialized yet, so it first needs to be initialized
49+
// before being called again
50+
this.initComponentInstance(changes);
51+
} else {
52+
// if an input or output has changed
53+
if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) {
54+
this.connectInputsAndOutputs();
55+
if (this.compRef?.instance && 'ngOnChanges' in this.compRef.instance) {
56+
(this.compRef.instance as any).ngOnChanges(changes);
57+
}
58+
}
4959
}
5060
}
5161

5262
ngOnInit(): void {
5363
this.destroyComponentInstance();
54-
this.themeSub = this.themeService.getThemeName$().subscribe(() => {
55-
this.renderComponentInstance();
56-
});
64+
this.initComponentInstance();
5765
}
5866

5967
ngOnDestroy(): void {
6068
[this.themeSub, this.lazyLoadSub].filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
6169
this.destroyComponentInstance();
6270
}
6371

64-
protected renderComponentInstance(): void {
65-
this.destroyComponentInstance();
72+
initComponentInstance(changes?: SimpleChanges) {
73+
this.themeSub = this.themeService?.getThemeName$().subscribe(() => {
74+
this.renderComponentInstance(changes);
75+
});
76+
}
6677

78+
protected renderComponentInstance(changes?: SimpleChanges): void {
6779
if (hasValue(this.lazyLoadSub)) {
6880
this.lazyLoadSub.unsubscribe();
6981
}
7082

71-
this.lazyLoadSub = this.resolveThemedComponent(this.themeService.getThemeName()).pipe(
72-
switchMap((themedFile: any) => {
73-
if (hasValue(themedFile) && hasValue(themedFile[this.getComponentName()])) {
74-
// if the file is not null, and exports a component with the specified name,
75-
// return that component
76-
return [themedFile[this.getComponentName()]];
77-
} else {
78-
// otherwise import and return the default component
79-
return fromPromise(this.importUnthemedComponent()).pipe(
80-
map((unthemedFile: any) => {
81-
return unthemedFile[this.getComponentName()];
82-
})
83-
);
84-
}
85-
}),
86-
).subscribe((constructor: GenericConstructor<T>) => {
83+
if (hasNoValue(this.lazyLoadObs)) {
84+
this.destroyComponentInstance();
85+
86+
this.lazyLoadObs = combineLatest([
87+
observableOf(changes),
88+
this.resolveThemedComponent(this.themeService.getThemeName()).pipe(
89+
switchMap((themedFile: any) => {
90+
if (hasValue(themedFile) && hasValue(themedFile[this.getComponentName()])) {
91+
// if the file is not null, and exports a component with the specified name,
92+
// return that component
93+
return [themedFile[this.getComponentName()]];
94+
} else {
95+
// otherwise import and return the default component
96+
return fromPromise(this.importUnthemedComponent()).pipe(
97+
map((unthemedFile: any) => {
98+
return unthemedFile[this.getComponentName()];
99+
})
100+
);
101+
}
102+
})),
103+
]);
104+
}
105+
106+
this.lazyLoadSub = this.lazyLoadObs.subscribe(([simpleChanges, constructor]: [SimpleChanges, GenericConstructor<T>]) => {
87107
const factory = this.resolver.resolveComponentFactory(constructor);
88108
this.compRef = this.vcr.createComponent(factory);
89-
this.connectInputsAndOutputs();
109+
if (hasValue(simpleChanges)) {
110+
this.ngOnChanges(simpleChanges);
111+
} else {
112+
this.connectInputsAndOutputs();
113+
}
90114
this.cdr.markForCheck();
91115
});
92116
}

0 commit comments

Comments
 (0)