Skip to content

Commit

Permalink
feat: add migration for self-closing tags (#416)
Browse files Browse the repository at this point in the history
docs: add docs for self-closing-tags migration

Co-authored-by: Chau Tran <nartc7789@gmail.com>
  • Loading branch information
eneajaho and nartc committed Jul 15, 2024
1 parent 3a81480 commit 8687a39
Show file tree
Hide file tree
Showing 10 changed files with 640 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/src/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import { Card, CardGrid, Steps } from '@astrojs/starlight/components';
- [<strong>output() Migration</strong> - migrate to the new output()](/utilities/migrations/new-outputs-migration/)
- [<strong>inject() Migration</strong> - migrate to inject() based DI](/utilities/migrations/inject-migration/)
- [<strong>viewChild(), contentChild(), viewChildren(), contentChildren() Migration</strong> - migrate to the new template queries()](/utilities/migrations/queries-migration/)
- [<strong>self-closing tag</strong> - migrate to the new self-closing tag](/utilities/migrations/self-closing-tags/)
</div>
</Card>

Expand Down
47 changes: 47 additions & 0 deletions docs/src/content/docs/utilities/Migrations/self-closing-tags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
title: Self Closing Tags Migration
description: Schematics for migrating non-self-closing tags to self-closing tags.
entryPoint: convert-to-self-closing-tag
badge: stable
contributors: ['enea-jahollari']
---

Angular supports self-closing tags. This means that you can write tags like `<app-component />` instead of `<app-component></app-component>`.
This is a feature that was introduced in Angular 16.

### How it works?

The moment you run the schematics, it will look for all the tags that are not self-closing and convert them to self-closing tags.

- It will look for all the tags that don't have any content inside them.
- It will only convert components that have "-" in their name.

### Usage

In order to run the schematics for all the project in the app you have to run the following script:

```bash
ng g ngxtension:convert-to-self-closing-tag
```

If you want to specify the project name you can pass the `--project` param.

```bash
ng g ngxtension:convert-to-self-closing-tag --project=<project-name>
```

If you want to run the schematic for a specific component or directive you can pass the `--path` param.

```bash
ng g ngxtension:convert-to-self-closing-tag --path=<path-to-ts-file>
```

### Usage with Nx

To use the schematics on a Nx monorepo you just swap `ng` with `nx`

Example:

```bash
nx g ngxtension:convert-to-self-closing-tag --project=<project-name>
```
1 change: 1 addition & 0 deletions libs/ngxtension/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
{
"files": ["*.html"],
"extends": ["plugin:@nx/angular-template"],
"excludedFiles": ["*inline-template-*.component.html"],
"rules": {}
},
{
Expand Down
10 changes: 10 additions & 0 deletions libs/plugin/generators.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
"factory": "./src/generators/convert-queries/generator",
"schema": "./src/generators/convert-queries/schema.json",
"description": "libs/plugin/src/generators/convert-queries/ generator"
},
"convert-to-self-closing-tag": {
"factory": "./src/generators/convert-to-self-closing-tag/generator",
"schema": "./src/generators/convert-to-self-closing-tag/schema.json",
"description": "libs/plugin/src/generators/convert-to-self-closing-tag/ generator"
}
},
"schematics": {
Expand Down Expand Up @@ -60,6 +65,11 @@
"factory": "./src/generators/convert-queries/compat",
"schema": "./src/generators/convert-queries/schema.json",
"description": "libs/plugin/src/generators/convert-queries/ generator"
},
"convert-to-self-closing-tag": {
"factory": "./src/generators/convert-to-self-closing-tag/compat",
"schema": "./src/generators/convert-to-self-closing-tag/schema.json",
"description": "libs/plugin/src/generators/convert-to-self-closing-tag/ generator"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`convertToSelfClosingTagGenerator should convert properly for inline template 1`] = `
"
import { Component, Input } from '@angular/core';
@Component({
template: \`
<router-outlet />
\`
})
export class MyCmp {
}
"
`;
exports[`convertToSelfClosingTagGenerator should convert properly for inline template 2`] = `undefined`;
exports[`convertToSelfClosingTagGenerator should convert properly for templateUrl 1`] = `
"
import { Component, Input } from '@angular/core';
@Component({
templateUrl: './my-file.html'
})
export class MyCmp {
}
"
`;
exports[`convertToSelfClosingTagGenerator should convert properly for templateUrl 2`] = `
"
<div>Hello</div>
<app-my-cmp1>123</app-my-cmp1>
<app-my-cmp1>123</app-my-cmp1>
<app-my-cmp2 test="hello">123</app-my-cmp2>
<app-my-cmp3
test="hello">
123
</app-my-cmp3>
<app-my-cmp4
test="hello"
>
123
</app-my-cmp4>
<app-my-cmp5
test="hello"
>
123
</app-my-cmp5>
<app-my-cmp10 test="hello"
[test]="hello"
(test)="hello()"
/>
<app-my-cmp11 test="hello"
[test]="hello"
(test)="hello()"
/>
<app-my-cmp12 test="hello"
/>
<input type="text" />
<app-my-cmp6 />
<app-my-cmp7 test="hello" />
<hello-world />
<pagination count="1" [test]="hello" (test)="test"></pagination>
<pagination count="1" />
<hello-world />
<hello-world12>
<hello-world13>
<hello-world14 count="1" [test]="hello" (test)="test" />
<hello-world15>
<hello-world16 count="1" [test]="hello" (test)="test" />
<hello-world17 count="1" [test]="hello" (test)="test" />
<hello-world18 count="1" [test]="hello"
(test)="test"
/>
</hello-world15>
</hello-world13>
</hello-world12>
<app-management *ngIf="
categoryList &&
((test1 && test1.length > 0) ||
(test && test.length > 0))
"
[test]="test > 2"
[test]="test"
(testEvent)="test.length > 0 ? test($event) : null"
(testEvent2)="test1($event)" />
"
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { convertNxGenerator } from '@nx/devkit';
import convertGenerator from './generator';

export default convertNxGenerator(convertGenerator);
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';

import convertToSelfClosingTagGenerator from './generator';
import { ConvertToSelfClosingTagGeneratorSchema } from './schema';

const template = `
<div>Hello</div>
<app-my-cmp1>123</app-my-cmp1>
<app-my-cmp1>123</app-my-cmp1>
<app-my-cmp2 test="hello">123</app-my-cmp2>
<app-my-cmp3
test="hello">
123
</app-my-cmp3>
<app-my-cmp4
test="hello"
>
123
</app-my-cmp4>
<app-my-cmp5
test="hello"
>
123
</app-my-cmp5>
<app-my-cmp10 test="hello"
[test]="hello"
(test)="hello()"
>
</app-my-cmp10>
<app-my-cmp11
test="hello"
[test]="hello"
(test)="hello()"
>
</app-my-cmp11>
<app-my-cmp12 test="hello"
>
</app-my-cmp12>
<input type="text" />
<app-my-cmp6 />
<app-my-cmp7 test="hello" />
<hello-world></hello-world>
<pagination count="1" [test]="hello" (test)="test"></pagination>
<pagination count="1" />
<hello-world></hello-world>
<hello-world12>
<hello-world13>
<hello-world14 count="1" [test]="hello" (test)="test" ></hello-world14>
<hello-world15>
<hello-world16 count="1" [test]="hello" (test)="test" />
<hello-world17 count="1" [test]="hello" (test)="test" ></hello-world17>
<hello-world18
count="1" [test]="hello"
(test)="test"
>
</hello-world18>
</hello-world15>
</hello-world13>
</hello-world12>
<app-management
*ngIf="
categoryList &&
((test1 && test1.length > 0) ||
(test && test.length > 0))
"
[test]="test > 2"
[test]="test"
(testEvent)="test.length > 0 ? test($event) : null"
(testEvent2)="test1($event)"></app-management>
`;

const filesMap = {
notComponent: `
import { Injectable } from '@angular/core';
@Injectable()
export class MyService {}
`,
componentNoTemplate: `
import { Component } from '@angular/core';
@Component({})
export class MyCmp {}
`,

componentWithTemplateUrl: `
import { Component, Input } from '@angular/core';
@Component({
templateUrl: './my-file.html'
})
export class MyCmp {
}
`,

componentWithInlineTemplate: `
import { Component, Input } from '@angular/core';
@Component({
template: \`
<router-outlet></router-outlet>
\`
})
export class MyCmp {
}
`,
} as const;

describe('convertToSelfClosingTagGenerator', () => {
let tree: Tree;
const options: ConvertToSelfClosingTagGeneratorSchema = {
path: 'libs/my-file.ts',
};

function setup(file: keyof typeof filesMap) {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.write('package.json', `{"dependencies": {"@angular/core": "17.1.0"}}`);
tree.write(`libs/my-file.ts`, filesMap[file]);

if (file === 'componentWithTemplateUrl') {
tree.write(`libs/my-file.html`, template);
return () => {
return [
tree.read('libs/my-file.ts', 'utf8'),
filesMap[file],
tree.read('libs/my-file.html', 'utf8'),
template,
];
};
}

return () => {
return [tree.read('libs/my-file.ts', 'utf8'), filesMap[file]];
};
}

it('should not do anything if not component/directive', async () => {
const readContent = setup('notComponent');
await convertToSelfClosingTagGenerator(tree, options);
const [updated, original] = readContent();
expect(updated).toEqual(original);
});

it('should not do anything if no template', async () => {
const readContent = setup('componentNoTemplate');
await convertToSelfClosingTagGenerator(tree, options);
const [updated, original] = readContent();
expect(updated).toEqual(original);
});

it('should convert properly for templateUrl', async () => {
const readContent = setup('componentWithTemplateUrl');
await convertToSelfClosingTagGenerator(tree, options);
const [updated, , updatedHtml] = readContent();
expect(updated).toMatchSnapshot();
expect(updatedHtml).toMatchSnapshot();
});

it('should convert properly for inline template', async () => {
const readContent = setup('componentWithInlineTemplate');
await convertToSelfClosingTagGenerator(tree, options);
const [updated, , updatedHtml] = readContent();
expect(updated).toMatchSnapshot();
expect(updatedHtml).toMatchSnapshot();
});
});
Loading

0 comments on commit 8687a39

Please sign in to comment.