Skip to content

Commit

Permalink
fix(content): use PendingTasks to ensure content has been rendered be…
Browse files Browse the repository at this point in the history
…fore SSR serialization (#1044)
  • Loading branch information
brandonroberts authored Apr 18, 2024
1 parent e63bab1 commit 2f5abbd
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 20 deletions.
2 changes: 2 additions & 0 deletions packages/content/src/lib/content.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Observable, of } from 'rxjs';
import { CONTENT_FILES_TOKEN } from './content-files-token';
import { injectContent } from './content';
import { ContentFile } from './content-file';
import { RenderTaskService } from './render-task.service';

describe('injectContent', () => {
type TestAttributes = {
Expand Down Expand Up @@ -236,6 +237,7 @@ Test agx Content`),
) {
TestBed.configureTestingModule({
providers: [
RenderTaskService,
{
provide: ActivatedRoute,
useValue: {
Expand Down
10 changes: 7 additions & 3 deletions packages/content/src/lib/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
import { inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable, of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { map, switchMap, tap } from 'rxjs/operators';

import { ContentFile } from './content-file';
import { CONTENT_FILES_TOKEN } from './content-files-token';
import { parseRawContentFile } from './parse-raw-content-file';
import { waitFor } from './utils/zone-wait-for';
import { RenderTaskService } from './render-task.service';

function getContentFile<
Attributes extends Record<string, any> = Record<string, any>
Expand Down Expand Up @@ -88,6 +89,8 @@ export function injectContent<
fallback = 'No Content Found'
): Observable<ContentFile<Attributes | Record<string, never>>> {
const contentFiles = inject(CONTENT_FILES_TOKEN);
const renderTaskService = inject(RenderTaskService);
const task = renderTaskService.addRenderTask();

if (typeof param === 'string' || 'param' in param) {
const prefix = typeof param === 'string' ? '' : `${param.subdirectory}/`;
Expand All @@ -110,14 +113,15 @@ export function injectContent<
attributes: {},
content: fallback,
});
})
}),
tap(() => renderTaskService.clearRenderTask(task))
);
} else {
return getContentFile<Attributes>(
contentFiles,
'',
param.customFilename,
fallback
);
).pipe(tap(() => renderTaskService.clearRenderTask(task)));
}
}
4 changes: 4 additions & 0 deletions packages/content/src/lib/inject-content-files.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
injectContentFiles,
InjectContentFilesFilterFunction,
} from './inject-content-files';
import { RenderTaskService } from './render-task.service';

describe('injectContentFiles', () => {
it('should provide empty files if no files provided', () => {
Expand Down Expand Up @@ -78,6 +79,9 @@ describe('injectContentFiles', () => {
contentFiles: ContentFile[] = [],
filterFn?: InjectContentFilesFilterFunction<Attributes>
) {
TestBed.configureTestingModule({
providers: [RenderTaskService],
});
TestBed.overrideProvider(CONTENT_FILES_LIST_TOKEN, {
useValue: contentFiles,
});
Expand Down
5 changes: 5 additions & 0 deletions packages/content/src/lib/inject-content-files.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { ContentFile } from './content-file';
import { inject } from '@angular/core';
import { CONTENT_FILES_LIST_TOKEN } from './content-files-list-token';
import { RenderTaskService } from './render-task.service';

export function injectContentFiles<Attributes extends Record<string, any>>(
filterFn?: InjectContentFilesFilterFunction<Attributes>
): ContentFile<Attributes>[] {
const renderTaskService = inject(RenderTaskService);
const task = renderTaskService.addRenderTask();
const allContentFiles = inject(
CONTENT_FILES_LIST_TOKEN
) as ContentFile<Attributes>[];
renderTaskService.clearRenderTask(task);

if (filterFn) {
const filteredContentFiles = allContentFiles.filter(filterFn);

return filteredContentFiles;
}

return allContentFiles;
}

Expand Down
14 changes: 5 additions & 9 deletions packages/content/src/lib/markdown-content-renderer.service.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import {
inject,
Injectable,
InjectionToken,
PLATFORM_ID,
Provider,
} from '@angular/core';
import { inject, Injectable, InjectionToken, Provider } from '@angular/core';
import { getHeadingList } from 'marked-gfm-heading-id';

import { ContentRenderer, TableOfContentItem } from './content-renderer';
import { MarkedSetupService } from './marked-setup.service';
import { RenderTaskService } from './render-task.service';

@Injectable()
export class MarkdownContentRendererService implements ContentRenderer {
platformId = inject(PLATFORM_ID);
#marked = inject(MarkedSetupService, { self: true });

async render(content: string): Promise<string> {
Expand All @@ -38,6 +32,8 @@ export function withMarkdownRenderer(
options?: MarkdownRendererOptions
): Provider {
return [
MarkedSetupService,
RenderTaskService,
{
provide: ContentRenderer,
useFactory: () => new MarkdownContentRendererService(),
Expand All @@ -55,7 +51,7 @@ export function withMarkdownRenderer(
}

export function provideContent(...features: Provider[]) {
return [...features, MarkedSetupService];
return [...features];
}

export const MERMAID_IMPORT_TOKEN = new InjectionToken<
Expand Down
19 changes: 11 additions & 8 deletions packages/content/src/lib/markdown.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { AsyncPipe, isPlatformBrowser } from '@angular/common';
import {
AfterViewChecked,
ChangeDetectorRef,
Component,
Input,
NgZone,
Expand Down Expand Up @@ -48,7 +47,7 @@ export default class AnalogMarkdownComponent
});
private mermaid: typeof import('mermaid') | undefined;

public content$: Observable<SafeHtml> = of('');
public content$: Observable<SafeHtml> = this.getContentSource();

@Input() content!: string | object | undefined | null;
@Input() classes = 'analog-markdown';
Expand Down Expand Up @@ -79,15 +78,19 @@ export default class AnalogMarkdownComponent
const componentRef = this.container.createComponent(this.content as any);
componentRef.changeDetectorRef.detectChanges();
} else {
this.content$ = this.route.data.pipe(
map<Data, string>((data) => this.content ?? data['_analogContent']),
mergeMap((contentString) => this.renderContent(contentString)),
map((content) => this.sanitizer.bypassSecurityTrustHtml(content)),
catchError((e) => of(`There was an error ${e}`))
);
this.content$ = this.getContentSource();
}
}

getContentSource() {
return this.route.data.pipe(
map<Data, string>((data) => this.content ?? data['_analogContent']),
mergeMap((contentString) => this.renderContent(contentString)),
map((content) => this.sanitizer.bypassSecurityTrustHtml(content)),
catchError((e) => of(`There was an error ${e}`))
);
}

async renderContent(content: string): Promise<string> {
return this.contentRenderer.render(content);
}
Expand Down
18 changes: 18 additions & 0 deletions packages/content/src/lib/render-task.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {
Injectable,
inject,
ɵPendingTasks as PendingTasks,
} from '@angular/core';

@Injectable()
export class RenderTaskService {
#pendingTasks = inject(PendingTasks);

addRenderTask() {
return this.#pendingTasks.add();
}

clearRenderTask(id: number) {
this.#pendingTasks.remove(id);
}
}

0 comments on commit 2f5abbd

Please sign in to comment.