Я пытался перейти на формат потока управления в своих проектах Angular 18 в рабочей области nx
. Большинство шаблонов работали отлично, но для одного HTML я получаю следующую ошибку:
[ngSwitch]: Error: Text node: "
◬" would result in invalid migrated @switch block structure. @switch can only have @case or @default as children.
Что я сделал:
npx nx generate @angular/core:control-flow-migration
Вот мой полный HTML:
<ng-container *ngIf = "!!type; else DeprecatedStructure">
<ng-container [ngSwitch] = "type">
<ng-container *ngFor = "let type of buttonTypes">
<button
*ngSwitchCase = "type"
[style.display] = "'flex'"
[style.flexDirection] = "iconPosition === 'right' ? 'row' : 'row-reverse'"
[ngClass] = "buttonClasses[type]"
[disabled] = "disabled"
[type] = "htmlType"
[id] = "id"
[attr.data-test-id] = "dataTestId"
(click) = "handleClick($event)">
<ng-container [ngSwitch] = "type">
<ng-container *ngSwitchDefault>
<span><ng-template [ngTemplateOutlet] = "ButtonContentRef"></ng-template></span>
<mat-icon
*ngIf = "!!icon"
class = "fl-icon"
[ngClass] = "{
right: iconPosition === 'right',
left: iconPosition === 'left'
}">
{{ icon }}
</mat-icon>
<mat-icon
*ngIf = "!!svgIcon && !icon"
class = "fl-icon"
[svgIcon] = "svgIcon"
[ngClass] = "{
right: iconPosition === 'right',
left: iconPosition === 'left'
}">
</mat-icon>
<span *ngIf = "loading" class = "loading loading-sm"></span>
</ng-container>
<ng-container *ngSwitchCase = "icon">
<mat-icon class = "fl-icon">{{ icon }}</mat-icon>
<span *ngIf = "loading" class = "loading loading-sm"></span>
</ng-container>
</ng-container>
</button>
</ng-container>
</ng-container>
</ng-container>
<ng-template #DeprecatedStructure>
<button
[class.btn] = "!onlyIcon"
[class.btn-primary] = "!onlyIcon && !textButton && !linkButton && !cardButton"
[class.btn-secondary] = "secondary && !textButton && !linkButton && !cardButton"
[class.btn-destructive] = "destructive && !textButton && !linkButton && !cardButton"
[class.btn-sm] = "size === 'small'"
[class.btn-lg] = "size === 'large'"
[class.btn-floating-primary] = "onlyIcon"
[class.btn-floating-secondary] = "onlyIcon && !!secondary"
[class.btn-floating-destructive] = "onlyIcon && !!destructive"
[class.btn-flat-primary] = "textButton"
[class.btn-flat-secondary] = "textButton && secondary"
[class.btn-flat-destructive] = "textButton && destructive"
[class.btn-plain-primary] = "linkButton"
[class.btn-plain-secondary] = "linkButton && secondary"
[class.btn-plain-destructive] = "linkButton && destructive"
[class.btn-card] = "cardButton"
[class.btn-loading] = "loading"
[ngClass] = "className"
[disabled] = "!!disabled"
[type] = "htmlType"
[id] = "id"
[attr.data-test-id] = "dataTestId"
(click) = "handleClick($event)">
<mat-icon *ngIf = "icon && iconPosition !== 'right'" class = "fl-icon" [ngClass] = "iconClassName">{{ icon }}</mat-icon>
<span *ngIf = "!onlyIcon"><ng-template [ngTemplateOutlet] = "ButtonContentRef"></ng-template></span>
<mat-icon *ngIf = "icon && iconPosition === 'right'" class = "fl-icon right" [ngClass] = "iconClassName">{{
icon
}}</mat-icon>
<span *ngIf = "loading" class = "loading loading-sm"></span>
</button>
</ng-template>
<ng-template #ButtonContentRef>
<ng-content></ng-content>
</ng-template>
Как вы можете прочитать в сообщении об ошибке, @switch
может иметь только @case
или @default
в качестве дочерних элементов. Однако в вашем случае во время миграции будет создано что-то вроде следующего:
@switch(type) {
@for (...) {
@case () {}
}
}
Что неверно согласно приведенному выше утверждению. Angular не может автоматически решить эту проблему, поскольку это был допустимый синтаксис с [ngSwitch]
и *ngSwitchCase
, поэтому миграция не удалась. Чтобы это исправить, вы можете переместить *ngFor
перед [ngSwitch]
:
<ng-container *ngFor = "let type of buttonTypes">
<ng-container [ngSwitch] = "type">
<button *ngSwitchCase = "">
<!-- ... your other content -->
</ng-container>
</ng-container>
А затем снова запустите миграцию.
Попробуйте вручную перенести код, это скрипт, он не может обрабатывать все крайние сценарии. Мы можем перенести его, это будет выглядеть примерно так, как показано ниже.
default
всегда должен быть внизу оператора переключателя, я думаю, это может быть ошибка. Обратите внимание на внесенное мною изменение, которое отличается от вашего кода: я переместил оператор случая наверх во внутреннем операторе переключения.
import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
@Component({
selector: 'app-root',
standalone: true,
template: `
@if (!!type; else DeprecatedStructure) {
@switch(type) {
@for(type of buttonTypes; track $index) {
@case (type) {
<button
[style.display] = "'flex'"
[style.flexDirection] = "iconPosition === 'right' ? 'row' : 'row-reverse'"
[ngClass] = "buttonClasses[type]"
[disabled] = "disabled"
[type] = "htmlType"
[id] = "id"
[attr.data-test-id] = "dataTestId"
(click) = "handleClick($event)">
@switch(type) {
@case (icon) {
<mat-icon class = "fl-icon">{{ icon }}</mat-icon>
@if (loading) {
<span class = "loading loading-sm"></span>
}
}
@default {
<span><ng-template [ngTemplateOutlet] = "ButtonContentRef"></ng-template></span>
@if (icon) {
<mat-icon
class = "fl-icon"
[ngClass] = "{
right: iconPosition === 'right',
left: iconPosition === 'left'
}">
{{ icon }}
</mat-icon>
}
@if (svgIcon && !icon) {
<mat-icon
class = "fl-icon"
[svgIcon] = "svgIcon"
[ngClass] = "{
right: iconPosition === 'right',
left: iconPosition === 'left'
}">
</mat-icon>
}
@if (loading) {
<span class = "loading loading-sm"></span>
}
}
}
</button>
}
}
}
}
<ng-template #DeprecatedStructure>
<button
[class.btn] = "!onlyIcon"
[class.btn-primary] = "!onlyIcon && !textButton && !linkButton && !cardButton"
[class.btn-secondary] = "secondary && !textButton && !linkButton && !cardButton"
[class.btn-destructive] = "destructive && !textButton && !linkButton && !cardButton"
[class.btn-sm] = "size === 'small'"
[class.btn-lg] = "size === 'large'"
[class.btn-floating-primary] = "onlyIcon"
[class.btn-floating-secondary] = "onlyIcon && !!secondary"
[class.btn-floating-destructive] = "onlyIcon && !!destructive"
[class.btn-flat-primary] = "textButton"
[class.btn-flat-secondary] = "textButton && secondary"
[class.btn-flat-destructive] = "textButton && destructive"
[class.btn-plain-primary] = "linkButton"
[class.btn-plain-secondary] = "linkButton && secondary"
[class.btn-plain-destructive] = "linkButton && destructive"
[class.btn-card] = "cardButton"
[class.btn-loading] = "loading"
[ngClass] = "className"
[disabled] = "!!disabled"
[type] = "htmlType"
[id] = "id"
[attr.data-test-id] = "dataTestId"
(click) = "handleClick($event)">
@if (icon && iconPosition !== 'right') {
<mat-icon class = "fl-icon" [ngClass] = "iconClassName">{{ icon }}</mat-icon>
}
@if (!onlyIcon) {
<span><ng-template [ngTemplateOutlet] = "ButtonContentRef"></ng-template></span>
}
@if (icon && iconPosition === 'right') {
<mat-icon class = "fl-icon right" [ngClass] = "iconClassName">{{
icon
}}</mat-icon>
}
@if (loading) {
<span class = "loading loading-sm"></span>
}
</button>
</ng-template>
<ng-template #ButtonContentRef>
<ng-content></ng-content>
</ng-template>
`,
})
export class App {
name = 'Angular';
}
bootstrapApplication(App);