Skip to content

docs: snippet-based examples not showing after clicking away and coming back #31415

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 16 additions & 13 deletions docs/src/app/shared/doc-viewer/doc-viewer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,12 @@ describe('DocViewer', () => {

http.expectOne(testUrl).flush(FAKE_DOCS[testUrl]);

const exampleViewer = fixture.debugElement.query(By.directive(ExampleViewer));
expect(exampleViewer.componentInstance.file).toBe('some-example.html');
expect(exampleViewer.componentInstance.showCompactToggle).toBeTrue();
expect(exampleViewer.componentInstance.region).toBe('some-region');
expect(exampleViewer.componentInstance.view).toBe('snippet');
const exampleViewer = fixture.debugElement.query(By.directive(ExampleViewer))
.componentInstance as ExampleViewer;
expect(exampleViewer.file()).toBe('some-example.html');
expect(exampleViewer.showCompactToggle()).toBeTrue();
expect(exampleViewer.region()).toBe('some-region');
expect(exampleViewer.view()).toBe('snippet');
});

it('should instantiate example viewer in demo view', () => {
Expand All @@ -111,10 +112,11 @@ describe('DocViewer', () => {

http.expectOne(testUrl).flush(FAKE_DOCS[testUrl]);

const exampleViewer = fixture.debugElement.query(By.directive(ExampleViewer));
expect(exampleViewer.componentInstance.file).toBeUndefined();
expect(exampleViewer.componentInstance.showCompactToggle).toBeFalse();
expect(exampleViewer.componentInstance.view).toBe('demo');
const exampleViewer = fixture.debugElement.query(By.directive(ExampleViewer))
.componentInstance as ExampleViewer;
expect(exampleViewer.file()).toBeUndefined();
expect(exampleViewer.showCompactToggle()).toBeFalse();
expect(exampleViewer.view()).toBe('demo');
});

it('should instantiate example viewer in snippet view with whole snippet', () => {
Expand All @@ -126,10 +128,11 @@ describe('DocViewer', () => {

http.expectOne(testUrl).flush(FAKE_DOCS[testUrl]);

const exampleViewer = fixture.debugElement.query(By.directive(ExampleViewer));
expect(exampleViewer.componentInstance.file).toBe('whole-snippet-example.ts');
expect(exampleViewer.componentInstance.showCompactToggle).toBeTrue();
expect(exampleViewer.componentInstance.view).toBe('snippet');
const exampleViewer = fixture.debugElement.query(By.directive(ExampleViewer))
.componentInstance as ExampleViewer;
expect(exampleViewer.file()).toBe('whole-snippet-example.ts');
expect(exampleViewer.showCompactToggle()).toBeTrue();
expect(exampleViewer.view()).toBe('snippet');
});

it('should show error message when doc not found', () => {
Expand Down
24 changes: 15 additions & 9 deletions docs/src/app/shared/doc-viewer/doc-viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
ViewContainerRef,
input,
inject,
Type,
} from '@angular/core';
import {Observable, Subscription} from 'rxjs';
import {shareReplay, take, tap} from 'rxjs/operators';
Expand Down Expand Up @@ -113,17 +114,17 @@ export class DocViewer implements OnDestroy {
if (file) {
// if the html div has field `file` then it should be in compact view to show the code
// snippet
exampleViewerComponent.view = 'snippet';
exampleViewerComponent.showCompactToggle = true;
exampleViewerComponent.file = file;
exampleViewerComponent.view.set('snippet');
exampleViewerComponent.showCompactToggle.set(true);
exampleViewerComponent.file.set(file);
if (region) {
// `region` should only exist when `file` exists but not vice versa
// It is valid for embedded example snippets to show the whole file (esp short files)
exampleViewerComponent.region = region;
exampleViewerComponent.region.set(region);
}
} else {
// otherwise it is an embedded demo
exampleViewerComponent.view = 'demo';
exampleViewerComponent.view.set('demo');
}
}

Expand Down Expand Up @@ -175,7 +176,7 @@ export class DocViewer implements OnDestroy {
}

/** Instantiate a ExampleViewer for each example. */
private _loadComponents(componentName: string, componentClass: any) {
private _loadComponents(componentName: string, componentClass: Type<ExampleViewer | HeaderLink>) {
const exampleElements = this._elementRef.nativeElement.querySelectorAll(`[${componentName}]`);

[...exampleElements].forEach((element: Element) => {
Expand All @@ -185,9 +186,14 @@ export class DocViewer implements OnDestroy {
const portalHost = new DomPortalOutlet(element, this._appRef, this._injector);
const examplePortal = new ComponentPortal(componentClass, this._viewContainerRef);
const exampleViewer = portalHost.attach(examplePortal);
const exampleViewerComponent = exampleViewer.instance as ExampleViewer;
if (example !== null) {
DocViewer._initExampleViewer(exampleViewerComponent, example, file, region);
const exampleViewerComponent = exampleViewer.instance;
if (example !== null && componentClass === ExampleViewer) {
DocViewer._initExampleViewer(
exampleViewerComponent as ExampleViewer,
example,
file,
region,
);
}
this._portalHosts.push(portalHost);
});
Expand Down
25 changes: 15 additions & 10 deletions docs/src/app/shared/example-viewer/example-viewer.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
@let exampleData = this.exampleData();
@let fileUrl = this.fileUrl();

<div class="docs-example-viewer-wrapper">
@if (view === 'snippet') {
@if (view() === 'snippet') {
<div class="docs-example-viewer-source-compact">
<div class="docs-button-bar">
<button mat-icon-button type="button" (click)="copySource(snippet())"
Expand Down Expand Up @@ -37,7 +40,7 @@
<mat-icon>link</mat-icon>
</button>

@if (showCompactToggle) {
@if (showCompactToggle()) {
<button mat-icon-button
(click)="toggleCompactView()"
matTooltip="View snippet only"
Expand All @@ -52,37 +55,39 @@
}

<button mat-icon-button type="button" (click)="toggleSourceView()"
[matTooltip]="view === 'demo' ? 'View code' : 'Hide code'" aria-label="View source">
[matTooltip]="view() === 'demo' ? 'View code' : 'Hide code'" aria-label="View source">
<mat-icon>code</mat-icon>
</button>

<stackblitz-button [example]="example"></stackblitz-button>
</div>

@if (view === 'full') {
@if (view() === 'full') {
<div class="docs-example-viewer-source">
<mat-tab-group animationDuration="0ms" [(selectedIndex)]="selectedTab" mat-stretch-tabs="false">
@for (tabName of _getExampleTabNames(); track tabName) {
@for (tabName of _exampleTabNames(); track tabName) {
<mat-tab [label]="tabName">
<div class="docs-button-bar">
<button mat-icon-button type="button" (click)="copySource(snippet(), selectedTab)"
<button mat-icon-button type="button" (click)="copySource(snippet(), selectedTab())"
class="docs-example-source-copy docs-example-button" matTooltip="Copy example source"
title="Copy example source" aria-label="Copy example source to clipboard">
<mat-icon>content_copy</mat-icon>
</button>
</div>
<code-snippet [source]="exampleTabs[tabName]"></code-snippet>
<code-snippet [source]="exampleTabs()[tabName]"></code-snippet>
</mat-tab>
}
</mat-tab-group>
</div>
}
}

@if (view !== 'snippet') {
@if (view() !== 'snippet') {
<div class="docs-example-viewer-body">
@if (_exampleComponentType && !example?.includes('harness')) {
<ng-template [ngComponentOutlet]="_exampleComponentType"/>
@let componentType = _exampleComponentType();

@if (componentType && !example?.includes('harness')) {
<ng-template [ngComponentOutlet]="componentType"/>
} @else {
<div>This example contains tests. Open in Stackblitz to run the tests.</div>
}
Expand Down
Loading
Loading