-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
76d6138
commit 24f0916
Showing
8 changed files
with
533 additions
and
0 deletions.
There are no files selected for viewing
95 changes: 95 additions & 0 deletions
95
docs/src/content/docs/utilities/Migrations/host-binding-migration.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
--- | ||
title: Queries Migration | ||
description: Schematics for migrating from decorator-based Queries to Signal-based Queries | ||
entryPoint: plugin/src/generators/convert-queries | ||
badge: stable | ||
contributors: ['enea-jahollari'] | ||
--- | ||
|
||
Recent releases of Angular have deprecated the `@HostBinding` and `@HostListener` decorators, replacing them with `host` defined properties. This migration schematic will help you convert your existing `@HostBinding` and `@HostListener` decorators to the new `host` properties. | ||
|
||
### How it works? | ||
|
||
The moment you run the schematics, it will look for all the decorators that have binding and replace them with `host` properties. | ||
|
||
- It will keep the same name for the attributes and properties bindings. | ||
- It will update the component's decorators by adding the `host` property if it does not exist or by adding additional properties within it. | ||
- It won't convert properties to signals. | ||
- It will remove the `@HostListener`, `@HostBinding` decorators. | ||
|
||
### Example | ||
|
||
Before running the schematics: | ||
|
||
```typescript | ||
import { Component, HostBinding, HostListener } from '@angular/core'; | ||
|
||
@Component({ | ||
/* ... */ | ||
}) | ||
export class CustomSlider { | ||
@HostBinding('attr.aria-valuenow') | ||
value: number = 0; | ||
|
||
@HostBinding('tabIndex') | ||
getTabIndex() { | ||
return this.disabled ? -1 : 0; | ||
} | ||
|
||
@HostListener('keydown', ['$event']) | ||
updateValue(event: KeyboardEvent) { | ||
/* ... */ | ||
} | ||
} | ||
``` | ||
|
||
After running the schematics: | ||
|
||
```typescript | ||
import { Component } from '@angular/core'; | ||
|
||
@Component({ | ||
host: { | ||
'[attr.aria-valuenow]': 'value', | ||
'[tabIndex]': 'disabled ? -1 : 0', | ||
'(keydown)': 'updateValue($event)', | ||
}, | ||
}) | ||
export class CustomSlider { | ||
value: number = 0; | ||
disabled: boolean = false; | ||
updateValue(event: KeyboardEvent) { | ||
/* ... */ | ||
} | ||
} | ||
``` | ||
|
||
### 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-host-binding | ||
``` | ||
|
||
If you want to specify the project name you can pass the `--project` param. | ||
|
||
```bash | ||
ng g ngxtension:convert-host-binding --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-host-binding --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-host-binding --project=<project-name> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
libs/plugin/src/generators/convert-host-binding/__snapshots__/generator.spec.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`convert-host-binding generator should convert properly for component 1`] = ` | ||
" | ||
import { Component } from '@angular/core'; | ||
@Component({ | ||
selector: 'my-component', | ||
template: 'My component', | ||
host: { '[class.active]': 'isActive', '[attr.aria-disabled]': 'isDisabled', '[tabIndex]': 'getTabIndex()' } | ||
}) | ||
export class MyComponent { | ||
isActive = true; | ||
get isDisabled() { | ||
return true; | ||
} | ||
getTabIndex() { | ||
return this.isDisabled ? -1 : 0; | ||
} | ||
} | ||
" | ||
`; | ||
|
||
exports[`convert-host-binding generator should convert properly for component with host property 1`] = ` | ||
" | ||
import { Component } from '@angular/core'; | ||
@Component({ | ||
selector: 'my-component', | ||
template: 'My component', | ||
host: { | ||
'[class.active]': 'isActive', | ||
'[attr.aria-disabled]': 'isDisabled' | ||
} | ||
}) | ||
export class MyComponent { | ||
isActive = true; | ||
get isDisabled() { | ||
return true; | ||
} | ||
} | ||
" | ||
`; | ||
|
||
exports[`convert-host-binding generator should convert properly for directive 1`] = ` | ||
" | ||
import { Directive } from '@angular/core'; | ||
@Directive({ | ||
selector: '[myDirective]', | ||
host: { '[class.active]': 'isActive', | ||
'(keydown)': 'updateValue($event)' | ||
} | ||
}) | ||
export class MyDirective { | ||
isActive = true; | ||
updateValue(event: KeyboardEvent) { | ||
} | ||
} | ||
" | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { convertNxGenerator } from '@nx/devkit'; | ||
import { convertHostBindingGenerator } from './generator'; | ||
|
||
export default convertNxGenerator(convertHostBindingGenerator); |
108 changes: 108 additions & 0 deletions
108
libs/plugin/src/generators/convert-host-binding/generator.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import { Tree } from '@nx/devkit'; | ||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; | ||
|
||
import { convertHostBindingGenerator } from './generator'; | ||
import { ConvertHostBindingGeneratorSchema } from './schema'; | ||
|
||
const filesMap = { | ||
componentWithHostBinding: ` | ||
import { Component, HostBinding } from '@angular/core'; | ||
@Component({ | ||
selector: 'my-component', | ||
template: 'My component', | ||
}) | ||
export class MyComponent { | ||
@HostBinding('class.active') isActive = true; | ||
@HostBinding('attr.aria-disabled') get isDisabled() { | ||
return true; | ||
} | ||
@HostBinding('tabIndex') getTabIndex() { | ||
return this.isDisabled ? -1 : 0; | ||
} | ||
} | ||
`, | ||
directiveWithHostBinding: ` | ||
import { Directive, HostBinding } from '@angular/core'; | ||
@Directive({ | ||
selector: '[myDirective]' | ||
}) | ||
export class MyDirective { | ||
@HostBinding('class.active') isActive = true; | ||
@HostListener('keydown', ['$event']) | ||
updateValue(event: KeyboardEvent) { | ||
} | ||
} | ||
`, | ||
|
||
componentWithHostProperty: ` | ||
import { Component } from '@angular/core'; | ||
@Component({ | ||
selector: 'my-component', | ||
template: 'My component', | ||
host: { | ||
'[class.active]': 'isActive', | ||
} | ||
}) | ||
export class MyComponent { | ||
isActive = true; | ||
@HostBinding('attr.aria-disabled') get isDisabled() { | ||
return true; | ||
} | ||
} | ||
`, | ||
}; | ||
|
||
describe('convert-host-binding generator', () => { | ||
let tree: Tree; | ||
const options: ConvertHostBindingGeneratorSchema = { | ||
path: 'libs/my-file.ts', | ||
}; | ||
|
||
function setup(file: keyof typeof filesMap) { | ||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); | ||
tree.write('package.json', `{"dependencies": {"@angular/core": "17.3.0"}}`); | ||
tree.write(`libs/my-file.ts`, filesMap[file]); | ||
|
||
return () => { | ||
return [tree.read('libs/my-file.ts', 'utf8'), filesMap[file]]; | ||
}; | ||
} | ||
|
||
beforeEach(() => { | ||
tree = createTreeWithEmptyWorkspace(); | ||
}); | ||
|
||
it('should convert properly for component', async () => { | ||
const readContent = setup('componentWithHostBinding'); | ||
await convertHostBindingGenerator(tree, options); | ||
|
||
const [updated] = readContent(); | ||
|
||
expect(updated).toMatchSnapshot(); | ||
}); | ||
|
||
it('should convert properly for directive', async () => { | ||
const readContent = setup('directiveWithHostBinding'); | ||
await convertHostBindingGenerator(tree, options); | ||
|
||
const [updated] = readContent(); | ||
|
||
expect(updated).toMatchSnapshot(); | ||
}); | ||
|
||
it('should convert properly for component with host property', async () => { | ||
const readContent = setup('componentWithHostProperty'); | ||
await convertHostBindingGenerator(tree, options); | ||
|
||
const [updated] = readContent(); | ||
|
||
expect(updated).toMatchSnapshot(); | ||
}); | ||
}); |
Oops, something went wrong.