Skip to content

Commit b8a920d

Browse files
committed
build: fix chips drag-drop example not working and improve example-module generation
Loading the chips-drag-drop example in the docs site is currently not possible, as the example files fail to load. This is because the example id does not match up with the directory that the example is stored within. This highlights an issue as the docs site assumes a certain folder structure. This is not ideal and an implementation detail specific to the Angular Components repository. The docs site should not need to make any assumptions about where code lives, but instead should just retrieve this information through the example information. That way we are free to change layout/file conventions in the components repository without having to worry about it breaking the docs site. This is implemented as part of this commit. Also a few sanity checks that enforce consistency (for improved code health) have been added. The new concept for examples also allows us to remove empty CSS files for examples without CSS.
1 parent 49de56c commit b8a920d

23 files changed

+90
-32
lines changed

src/components-examples/cdk/stepper/cdk-linear-stepper-with-form/cdk-linear-stepper-with-form-example.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {FormBuilder, FormGroup, Validators} from '@angular/forms';
44

55
/** @title A custom CDK linear stepper with forms */
66
@Component({
7-
selector: 'cdk-linear-stepper-with-form',
7+
selector: 'cdk-linear-stepper-with-form-example',
88
templateUrl: './cdk-linear-stepper-with-form-example.html',
99
styleUrls: ['./cdk-linear-stepper-with-form-example.css']
1010
})

src/components-examples/material-experimental/column-resize/index.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,34 @@
11
import {NgModule} from '@angular/core';
2-
import {OptInColumnResizeExampleModule} from './opt-in/opt-in-column-resize-example-module';
32
import {
4-
DefaultEnabledColumnResizeExampleModule,
5-
} from './default-enabled/default-enabled-column-resize-example-module';
3+
DefaultEnabledColumnResizeFlexExampleModule
4+
} from './default-enabled-column-resize-flex/default-enabled-column-resize-flex-example-module';
5+
import {
6+
DefaultEnabledColumnResizeExampleModule
7+
} from './default-enabled-column-resize/default-enabled-column-resize-example-module';
68
import {
7-
DefaultEnabledColumnResizeFlexExampleModule,
8-
} from './default-enabled-flex/default-enabled-column-resize-flex-example-module';
9+
OptInColumnResizeExampleModule
10+
} from './opt-in-column-resize/opt-in-column-resize-example-module';
911

1012
export {
1113
DefaultEnabledColumnResizeExample
12-
} from './default-enabled/default-enabled-column-resize-example';
14+
} from './default-enabled-column-resize/default-enabled-column-resize-example';
1315
export {
1416
DefaultEnabledColumnResizeExampleModule
15-
} from './default-enabled/default-enabled-column-resize-example-module';
17+
} from './default-enabled-column-resize/default-enabled-column-resize-example-module';
1618

1719
export {
1820
DefaultEnabledColumnResizeFlexExample
19-
} from './default-enabled-flex/default-enabled-column-resize-flex-example';
21+
} from './default-enabled-column-resize-flex/default-enabled-column-resize-flex-example';
2022
export {
2123
DefaultEnabledColumnResizeFlexExampleModule
22-
} from './default-enabled-flex/default-enabled-column-resize-flex-example-module';
24+
} from './default-enabled-column-resize-flex/default-enabled-column-resize-flex-example-module';
2325

2426
export {
2527
OptInColumnResizeExample
26-
} from './opt-in/opt-in-column-resize-example';
28+
} from './opt-in-column-resize/opt-in-column-resize-example';
2729
export {
2830
OptInColumnResizeExampleModule
29-
} from './opt-in/opt-in-column-resize-example-module';
31+
} from './opt-in-column-resize/opt-in-column-resize-example-module';
3032

3133

3234
@NgModule({

src/components-examples/material-experimental/mdc-form-field/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ import {ReactiveFormsModule} from '@angular/forms';
44
import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field';
55
import {MatIconModule} from '@angular/material/icon';
66
import {
7-
FormFieldCustomControlExample,
7+
MdcFormFieldCustomControlExample,
88
MyTelInput
99
} from './mdc-form-field-custom-control/form-field-custom-control-example';
1010

1111
export {
12-
FormFieldCustomControlExample,
12+
MdcFormFieldCustomControlExample,
1313
MyTelInput,
1414
};
1515

1616
const EXAMPLES = [
17-
FormFieldCustomControlExample,
17+
MdcFormFieldCustomControlExample,
1818
];
1919

2020
@NgModule({

src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import {Subject} from 'rxjs';
77

88
/** @title Form field with custom telephone number input control. */
99
@Component({
10-
selector: 'form-field-custom-control-example',
10+
selector: 'mdc-form-field-custom-control-example',
1111
templateUrl: 'form-field-custom-control-example.html',
1212
styleUrls: ['form-field-custom-control-example.css'],
1313
})
14-
export class FormFieldCustomControlExample {}
14+
export class MdcFormFieldCustomControlExample {}
1515

1616
/** Data structure for holding telephone number. */
1717
export class MyTel {

src/components-examples/material/chips/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {MatChipsModule} from '@angular/material/chips';
77
import {MatFormFieldModule} from '@angular/material/form-field';
88
import {MatIconModule} from '@angular/material/icon';
99
import {ChipsAutocompleteExample} from './chips-autocomplete/chips-autocomplete-example';
10-
import {ChipsDragDropExample} from './chips-drag-and-drop/chips-drag-drop-example';
10+
import {ChipsDragDropExample} from './chips-drag-drop/chips-drag-drop-example';
1111
import {ChipsInputExample} from './chips-input/chips-input-example';
1212
import {ChipsOverviewExample} from './chips-overview/chips-overview-example';
1313
import {ChipsStackedExample} from './chips-stacked/chips-stacked-example';

tools/example-module/example-module.template

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
/**
22
******************************************************************************
33
* DO NOT MANUALLY EDIT THIS FILE. THIS FILE IS AUTOMATICALLY GENERATED.
4-
******************************************************************************/
4+
******************************************************************************
5+
*/
56

67
export interface LiveExample {
78
/** Title of the example. */
89
title: string;
910
/** Name of the example component. */
1011
componentName: string;
12+
/** Selector to match the component of this example. */
13+
selector: string;
14+
/** List of files which are part of the example. */
15+
files: string[];
16+
/** Path to the directory containing the example. */
17+
packagePath: string;
1118
/** List of additional components which are part of the example. */
1219
additionalComponents: string[];
13-
/** List of additional files which are part of the example. */
14-
additionalFiles: string[];
1520
/** NgModule that declares this example. */
1621
module: NgModuleInfo;
1722
}
@@ -27,4 +32,4 @@ export interface NgModuleInfo {
2732
importSpecifier: string;
2833
}
2934

30-
export const EXAMPLE_COMPONENTS: {[key: string]: LiveExample} = ${exampleComponents};
35+
export const EXAMPLE_COMPONENTS: {[id: string]: LiveExample} = ${exampleComponents};

tools/example-module/generate-example-module.ts

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ interface ExampleMetadata {
88
componentName: string;
99
/** Path to the source file that declares this example. */
1010
sourcePath: string;
11-
/** Module import that can be used to load this example. */
12-
importPath: string;
11+
/** Path to the directory containing this example. */
12+
packagePath: string;
13+
/** Selector to match the component of this example. */
14+
selector: string;
1315
/** Unique id for this example. */
1416
id: string;
1517
/** Title of the example. */
1618
title: string;
1719
/** Additional components for this example. */
1820
additionalComponents: string[];
19-
/** Additional files for this example. */
20-
additionalFiles: string[];
21+
/** Files for this example. */
22+
files: string[];
2123
/** Reference to the module that declares this example. */
2224
module: ExampleModule;
2325
}
@@ -37,10 +39,16 @@ interface AnalyzedExamples {
3739
function inlineExampleModuleTemplate(parsedData: AnalyzedExamples): string {
3840
const {exampleMetadata} = parsedData;
3941
const exampleComponents = exampleMetadata.reduce((result, data) => {
42+
if (result[data.id] !== undefined) {
43+
throw Error(`Multiple examples with the same id have been discovered: ${data.id}`);
44+
}
45+
4046
result[data.id] = {
47+
packagePath: data.packagePath,
4148
title: data.title,
4249
componentName: data.componentName,
43-
additionalFiles: data.additionalFiles,
50+
files: data.files,
51+
selector: data.selector,
4452
additionalComponents: data.additionalComponents,
4553
module: {
4654
name: data.module.name,
@@ -73,13 +81,14 @@ function analyzeExamples(sourceFiles: string[], baseDir: string): AnalyzedExampl
7381
for (const sourceFile of sourceFiles) {
7482
const relativePath = path.relative(baseDir, sourceFile).replace(/\\/g, '/');
7583
const importPath = relativePath.replace(/\.ts$/, '');
84+
const packagePath = path.dirname(relativePath);
7685

7786
// Collect all individual example modules.
7887
if (path.basename(sourceFile) === 'index.ts') {
7988
exampleModules.push(...parseExampleModuleFile(sourceFile).map(name => ({
8089
name,
8190
importPath,
82-
packagePath: path.dirname(relativePath),
91+
packagePath,
8392
})));
8493
}
8594

@@ -96,25 +105,46 @@ function analyzeExamples(sourceFiles: string[], baseDir: string): AnalyzedExampl
96105
const exampleId = convertToDashCase(primaryComponent.componentName.replace('Example', ''));
97106
const example: ExampleMetadata = {
98107
sourcePath: relativePath,
108+
packagePath,
99109
id: exampleId,
110+
selector: primaryComponent.selector,
100111
componentName: primaryComponent.componentName,
101112
title: primaryComponent.title.trim(),
102-
additionalFiles: [],
103113
additionalComponents: [],
114+
files: [],
104115
module: null,
105-
importPath,
106116
};
117+
118+
// For consistency, we expect the example component selector to match
119+
// the id of the example.
120+
const expectedSelector = `${exampleId}-example`;
121+
if (primaryComponent.selector !== expectedSelector) {
122+
throw Error(`Example ${exampleId} uses selector: ${primaryComponent.selector}, ` +
123+
`but expected: ${expectedSelector}`);
124+
}
125+
126+
example.files.push(path.basename(relativePath));
127+
if (primaryComponent.templateUrl) {
128+
example.files.push(primaryComponent.templateUrl);
129+
}
130+
if (primaryComponent.styleUrls) {
131+
example.files.push(...primaryComponent.styleUrls);
132+
}
133+
107134
if (secondaryComponents.length) {
108135
for (const meta of secondaryComponents) {
109136
example.additionalComponents.push(meta.componentName);
110137
if (meta.templateUrl) {
111-
example.additionalFiles.push(meta.templateUrl);
138+
example.files.push(meta.templateUrl);
112139
}
113140
if (meta.styleUrls) {
114-
example.additionalFiles.push(...meta.styleUrls);
141+
example.files.push(...meta.styleUrls);
115142
}
116143
}
117144
}
145+
146+
// Ensure referenced files actually exist in the example.
147+
example.files.forEach(f => assertReferencedExampleFileExists(baseDir, packagePath, f));
118148
exampleMetadata.push(example);
119149
} else {
120150
throw Error(`Could not find a primary example component in ${sourceFile}. ` +
@@ -132,12 +162,32 @@ function analyzeExamples(sourceFiles: string[], baseDir: string): AnalyzedExampl
132162
throw Error(`Could not determine example module for: ${example.id}`);
133163
}
134164

165+
const actualPath = path.dirname(example.sourcePath);
166+
const expectedPath = path.posix.join(parentModule.packagePath, example.id);
167+
168+
// Ensures that example ids match with the directory they are stored in. This is not
169+
// necessary for the docs site, but it helps with consistency and makes it easy to
170+
// determine an id for an example. We also ensures for consistency that example
171+
// folders are direct siblings of the module file.
172+
if (actualPath !== expectedPath) {
173+
throw Error(`Example is stored in: ${actualPath}, but expected: ${expectedPath}`);
174+
}
175+
135176
example.module = parentModule;
136177
});
137178

138179
return {exampleMetadata};
139180
}
140181

182+
/** Asserts that the given file exists for the specified example. */
183+
function assertReferencedExampleFileExists(baseDir: string, examplePackagePath: string,
184+
relativePath: string) {
185+
if (!fs.existsSync(path.join(baseDir, examplePackagePath, relativePath))) {
186+
throw Error(
187+
`Example "${examplePackagePath}" references "${relativePath}", but file does not exist.`);
188+
}
189+
}
190+
141191
/**
142192
* Generates the example module from the given source files and writes it to a specified output
143193
* file.

tools/example-module/parse-example-file.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ interface ParsedMetadata {
44
isPrimary: boolean;
55
componentName: string;
66
title: string;
7+
selector: string;
78
templateUrl: string;
89
styleUrls: string[];
910
}

0 commit comments

Comments
 (0)