Skip to content

Commit 5523f7a

Browse files
committed
feat(): add support for standalone pages
this introduces a new standalone flag which can generate a standalone page for routing
1 parent 009079a commit 5523f7a

File tree

6 files changed

+279
-162
lines changed

6 files changed

+279
-162
lines changed

packages/schematics/page/files/__name@dasherize@if-flat__/__name@dasherize__.page.spec.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
1-
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2-
import { IonicModule } from '@ionic/angular';
3-
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
42
import { <%= classify(name) %>Page } from './<%= dasherize(name) %>.page';
53

64
describe('<%= classify(name) %>Page', () => {
75
let component: <%= classify(name) %>Page;
86
let fixture: ComponentFixture<<%= classify(name) %>Page>;
97

10-
beforeEach(waitForAsync(() => {
11-
TestBed.configureTestingModule({
12-
declarations: [ <%= classify(name) %>Page ],
13-
imports: [IonicModule.forRoot()]
14-
}).compileComponents();
15-
8+
beforeEach(async(() => {
169
fixture = TestBed.createComponent(<%= classify(name) %>Page);
1710
component = fixture.componentInstance;
1811
fixture.detectChanges();

packages/schematics/page/files/__name@dasherize@if-flat__/__name@dasherize__.page.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import { Component, OnInit } from '@angular/core';<% if(routePath) { %>
2-
import { ActivatedRoute, Params } from '@angular/router';<% } %>
2+
import { ActivatedRoute, Params } from '@angular/router';<% } %><% if(standalone) {%>
3+
import { CommonModule } from '@angular/common';
4+
import { FormsModule } from '@angular/forms';
5+
import { IonicModule } from '@ionic/angular';<%} %>
36

47
@Component({
58
selector: '<%= selector %>',
69
templateUrl: './<%= dasherize(name) %>.page.html',
7-
styleUrls: ['./<%= dasherize(name) %>.page.<%= styleext %>'],
10+
styleUrls: ['./<%= dasherize(name) %>.page.<%= styleext %>'],<% if(standalone) {%>
11+
standalone: true,
12+
imports: [IonicModule, CommonModule, FormsModule]<%} %>
813
})
914
export class <%= classify(name) %>Page implements OnInit {<% if(routePath) { %>
1015

packages/schematics/page/index.ts

Lines changed: 12 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import type { Path } from '@angular-devkit/core';
2-
import { join, normalize, strings } from '@angular-devkit/core';
3-
import type { DirEntry, Rule, Tree } from '@angular-devkit/schematics';
1+
import { strings } from '@angular-devkit/core';
2+
import type { Rule, Tree } from '@angular-devkit/schematics';
43
import {
54
SchematicsException,
65
apply,
@@ -13,160 +12,15 @@ import {
1312
template,
1413
url,
1514
} from '@angular-devkit/schematics';
16-
import type { ModuleOptions } from '@schematics/angular/utility/find-module';
17-
import { buildRelativePath } from '@schematics/angular/utility/find-module';
1815
import { parseName } from '@schematics/angular/utility/parse-name';
1916
import { validateHtmlSelector } from '@schematics/angular/utility/validation';
2017
import { buildDefaultPath, getWorkspace } from '@schematics/angular/utility/workspace';
21-
import * as ts from 'typescript';
2218

2319
import { buildSelector } from '../util';
24-
import { findNodes } from '../util/ast-util';
25-
import type { Change } from '../util/change';
26-
import { InsertChange } from '../util/change';
2720

21+
import { addRoute, addRouteToNgModule, findRoutingModuleFromOptions } from './route-utils';
2822
import type { Schema as PageOptions } from './schema';
2923

30-
function findRoutingModuleFromOptions(host: Tree, options: ModuleOptions): Path | undefined {
31-
// eslint-disable-next-line no-prototype-builtins
32-
if (options.hasOwnProperty('skipImport') && options.skipImport) {
33-
return undefined;
34-
}
35-
36-
if (!options.module) {
37-
const pathToCheck = (options.path || '') + (options.flat ? '' : '/' + strings.dasherize(options.name));
38-
39-
return normalize(findRoutingModule(host, pathToCheck));
40-
} else {
41-
const modulePath = normalize('/' + options.path + '/' + options.module);
42-
const moduleBaseName = normalize(modulePath).split('/').pop();
43-
44-
if (host.exists(modulePath)) {
45-
return normalize(modulePath);
46-
} else if (host.exists(modulePath + '.ts')) {
47-
return normalize(modulePath + '.ts');
48-
} else if (host.exists(modulePath + '.module.ts')) {
49-
return normalize(modulePath + '.module.ts');
50-
} else if (host.exists(modulePath + '/' + moduleBaseName + '.module.ts')) {
51-
return normalize(modulePath + '/' + moduleBaseName + '.module.ts');
52-
} else {
53-
throw new Error('Specified module does not exist');
54-
}
55-
}
56-
}
57-
58-
function findRoutingModule(host: Tree, generateDir: string): Path {
59-
let dir: DirEntry | null = host.getDir('/' + generateDir);
60-
61-
const routingModuleRe = /-routing\.module\.ts/;
62-
63-
while (dir) {
64-
const matches = dir.subfiles.filter((p) => routingModuleRe.test(p));
65-
66-
if (matches.length === 1) {
67-
return join(dir.path, matches[0]);
68-
} else if (matches.length > 1) {
69-
throw new Error(
70-
'More than one module matches. Use skip-import option to skip importing ' +
71-
'the component into the closest module.'
72-
);
73-
}
74-
75-
dir = dir.parent;
76-
}
77-
78-
throw new Error('Could not find an NgModule. Use the skip-import ' + 'option to skip importing in NgModule.');
79-
}
80-
81-
function addRouteToNgModule(options: PageOptions): Rule {
82-
const { module } = options;
83-
84-
if (!module) {
85-
throw new SchematicsException('module option is required.');
86-
}
87-
88-
return (host) => {
89-
const text = host.read(module);
90-
91-
if (!text) {
92-
throw new SchematicsException(`File ${module} does not exist.`);
93-
}
94-
95-
const sourceText = text.toString('utf8');
96-
const source = ts.createSourceFile(module, sourceText, ts.ScriptTarget.Latest, true);
97-
98-
const pagePath =
99-
`/${options.path}/` +
100-
(options.flat ? '' : `${strings.dasherize(options.name)}/`) +
101-
`${strings.dasherize(options.name)}.module`;
102-
103-
const relativePath = buildRelativePath(module, pagePath);
104-
105-
const routePath = strings.dasherize(options.routePath ? options.routePath : options.name);
106-
const ngModuleName = `${strings.classify(options.name)}PageModule`;
107-
const changes = addRouteToRoutesArray(source, module, routePath, relativePath, ngModuleName);
108-
const recorder = host.beginUpdate(module);
109-
110-
for (const change of changes) {
111-
if (change instanceof InsertChange) {
112-
recorder.insertLeft(change.pos, change.toAdd);
113-
}
114-
}
115-
116-
host.commitUpdate(recorder);
117-
118-
return host;
119-
};
120-
}
121-
122-
function addRouteToRoutesArray(
123-
source: ts.SourceFile,
124-
ngModulePath: string,
125-
routePath: string,
126-
routeLoadChildren: string,
127-
ngModuleName: string
128-
): Change[] {
129-
const keywords = findNodes(source, ts.SyntaxKind.VariableStatement);
130-
131-
for (const keyword of keywords) {
132-
if (ts.isVariableStatement(keyword)) {
133-
const [declaration] = keyword.declarationList.declarations;
134-
135-
if (ts.isVariableDeclaration(declaration) && declaration.initializer && declaration.name.getText() === 'routes') {
136-
const node = declaration.initializer.getChildAt(1);
137-
const lastRouteNode = node.getLastToken();
138-
139-
if (!lastRouteNode) {
140-
return [];
141-
}
142-
143-
const changes: Change[] = [];
144-
let trailingCommaFound = false;
145-
146-
if (lastRouteNode.kind === ts.SyntaxKind.CommaToken) {
147-
trailingCommaFound = true;
148-
} else {
149-
changes.push(new InsertChange(ngModulePath, lastRouteNode.getEnd(), ','));
150-
}
151-
152-
changes.push(
153-
new InsertChange(
154-
ngModulePath,
155-
lastRouteNode.getEnd() + 1,
156-
` {\n path: '${routePath}',\n loadChildren: () => import('${routeLoadChildren}').then( m => m.${ngModuleName})\n }${
157-
trailingCommaFound ? ',' : ''
158-
}\n`
159-
)
160-
);
161-
162-
return changes;
163-
}
164-
}
165-
}
166-
167-
return [];
168-
}
169-
17024
export default function (options: PageOptions): Rule {
17125
return async (host: Tree) => {
17226
if (!options.project) {
@@ -179,7 +33,9 @@ export default function (options: PageOptions): Rule {
17933
options.path = buildDefaultPath(project);
18034
}
18135

182-
options.module = findRoutingModuleFromOptions(host, options);
36+
if (!options.standalone) {
37+
options.module = findRoutingModuleFromOptions(host, options);
38+
}
18339

18440
const parsedPath = parseName(options.path as string, options.name);
18541
options.name = parsedPath.name;
@@ -190,6 +46,7 @@ export default function (options: PageOptions): Rule {
19046

19147
const templateSource = apply(url('./files'), [
19248
options.spec ? noop() : filter((p) => !p.endsWith('.spec.ts')),
49+
options.standalone ? filter((p) => !p.endsWith('module.ts')) : noop(),
19350
template({
19451
...strings,
19552
'if-flat': (s: string) => (options.flat ? '' : s),
@@ -198,6 +55,10 @@ export default function (options: PageOptions): Rule {
19855
move(parsedPath.path),
19956
]);
20057

201-
return chain([branchAndMerge(chain([addRouteToNgModule(options), mergeWith(templateSource)]))]);
58+
return chain([
59+
branchAndMerge(
60+
chain([options.standalone ? addRoute(options) : addRouteToNgModule(options), mergeWith(templateSource)])
61+
),
62+
]);
20263
};
20364
}

0 commit comments

Comments
 (0)