Angular 17 SSR应用中如何使子输入组件的formControlName生效
解决Angular 17 SSR中formControlName的NG01050错误
问题重现
错误信息
main.ts:6 ERROR Error: NG01050: formControlName must be used with a parent formGroup directive. You'll want to add a formGroup directive and pass it an existing FormGroup instance (you can create one in your class). Example: <div [formGroup]="myGroup"> <input formControlName="firstName"> </div> In your class: this.myGroup = new FormGroup({ firstName: new FormControl() }); at controlParentException (forms.mjs:1555:10) at _FormControlName._checkParentType (forms.mjs:5902:15) at _FormControlName._setUpControl (forms.mjs:5907:10) at _FormControlName.ngOnChanges (forms.mjs:5857:28) at _FormControlName.rememberChangeHistoryAndInvokeOnChangesHook (core.mjs:4101:14) at callHookInternal (core.mjs:5136:14) at callHook (core.mjs:5167:9) at callHooks (core.mjs:5118:17) at executeInitAndCheckHooks (core.mjs:5068:9) at selectIndexInternal (core.mjs:11029:17)
需求
将父组件FormGroup中的destination属性绑定到子输入组件的formControlName上。
相关代码
父组件代码
import { Component, PLATFORM_ID, Inject } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, } from '@angular/forms'; import { DatePickerComponent } from '../date-picker/date-picker.component'; import { InputComponent } from '../input/input.component'; import { SelectComponent } from '../select/select.component'; import { roomsAndGuestsData } from '../../../constants'; import { ButtonComponent } from '../button/button.component'; @Component({ selector: 'hero', standalone: true, imports: [ FormsModule, ReactiveFormsModule, DatePickerComponent, InputComponent, SelectComponent, ButtonComponent, ], templateUrl: './hero.component.html', }) export class HeroComponent { roomAndGuestsData = roomsAndGuestsData; public isBrowser = isPlatformBrowser(this.platformId); constructor(@Inject(PLATFORM_ID) private platformId: any) {} bookingForm = new FormGroup({ destination: new FormControl<string>(''), }); handleSubmit() { console.log(this.bookingForm.value); } }
父表单模板
<form [formGroup]="bookingForm" class="w-full shadow-xl drop-shadow-2xl -translate-y-1/2 z-10 bg-white ring-0 rounded-xl py-8 px-6 flex flex-col gap-6" > <h2 class="text-xl font-semibold text-blackish-green"> Where are you flying? </h2> <div class="flex flex-wrap gap-4"> <app-input class="w-full sm:w-[calc(100%/2-8px)] lg:flex-1" customClass="w-full" label="Destination" placeholder="City, Country" icon="ionBed" controlName="destination" ></app-input> </div> <div class="flex flex-col-reverse md:flex-row gap-6 items-end md:items-stretch md:justify-end" > <p class="flex items-center gap-1 cursor-pointer text-sm"> <span class="text-xl md:text-2xl">+</span> Add Promo Code </p> <app-button class="w-full md:w-auto" customClass="w-full" text="Show Places" icon="heroBuildingOffice2Solid" ></app-button> </div> </form>
子输入组件代码
import { Component, Input } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { NgIconComponent, provideIcons } from '@ng-icons/core'; import { ionBed } from '@ng-icons/ionicons'; @Component({ selector: 'app-input', standalone: true, imports: [NgIconComponent, ReactiveFormsModule], templateUrl: './input.component.html', viewProviders: [provideIcons({ ionBed })], }) export class InputComponent { @Input() label?: string = ''; @Input() placeholder?: string = ''; @Input() type: string = 'text'; @Input() value: string = ''; @Input() disabled: boolean = false; @Input() icon: string = ''; @Input() customClass?: string = ''; @Input() controlName: string = ''; id = 'custom-input-' + Math.random().toString(36).substring(2, 11); }
子组件模板
<div [class]="'relative h-14'"> <input [id]="id" [type]="type" [placeholder]="placeholder" [class]=" 'absolute left-0 right-0 top-0 bottom-0 pl-4 pr-10 rounded-[4px] outline-none font-normal text-blackish-green border border-light-grey placeholder:text-blackish-green/85 ' + customClass " [formControlName]="controlName" /> @if (label) { <label [for]="id" class="absolute left-3 -top-[7.5px] bg-white text-blackish-green px-1 text-base font-normal leading-[17px] z-10" > {{ label }} </label> } @if(icon){ <ng-icon [name]="icon" class="absolute right-3 top-1/2 -translate-y-1/2 text-2xl" ></ng-icon> } </div>
问题原因
Angular 17的独立组件拥有独立的注入上下文,父组件的formGroup指令无法穿透到子独立组件中,导致子组件内的formControlName找不到对应的父级表单组上下文,触发NG01050错误。
解决方案
方案一:继承父表单上下文(推荐)
在子组件的providers数组中配置,让子组件使用父组件的FormGroupDirective作为ControlContainer,从而继承父表单的上下文。
修改子组件代码:
import { Component, Input } from '@angular/core'; import { ReactiveFormsModule, ControlContainer, FormGroupDirective } from '@angular/forms'; import { NgIconComponent, provideIcons } from '@ng-icons/core'; import { ionBed } from '@ng-icons/ionicons'; @Component({ selector: 'app-input', standalone: true, imports: [NgIconComponent, ReactiveFormsModule], templateUrl: './input.component.html', viewProviders: [provideIcons({ ionBed })], providers: [ { provide: ControlContainer, useExisting: FormGroupDirective } ] }) export class InputComponent { @Input() label?: string = ''; @Input() placeholder?: string = ''; @Input() type: string = 'text'; @Input() value: string = ''; @Input() disabled: boolean = false; @Input() icon: string = ''; @Input() customClass?: string = ''; @Input() controlName: string = ''; id = 'custom-input-' + Math.random().toString(36).substring(2, 11); }
此方案无需修改模板或传递额外参数,适合通用表单组件场景。
方案二:通过Input传递父FormGroup
将父组件的FormGroup通过@Input传递给子组件,在子组件模板中显式绑定formGroup。
- 修改子组件代码,添加
parentForm输入属性:
import { Component, Input } from '@angular/core'; import { ReactiveFormsModule, FormGroup } from '@angular/forms'; import { NgIconComponent, provideIcons } from '@ng-icons/core'; import { ionBed } from '@ng-icons/ionicons'; @Component({ selector: 'app-input', standalone: true, imports: [NgIconComponent, ReactiveFormsModule], templateUrl: './input.component.html', viewProviders: [provideIcons({ ionBed })], }) export class InputComponent { @Input() label?: string = ''; @Input() placeholder?: string = ''; @Input() type: string = 'text'; @Input() value: string = ''; @Input() disabled: boolean = false; @Input() icon: string = ''; @Input() customClass?: string = ''; @Input() controlName: string = ''; @Input() parentForm!: FormGroup; id = 'custom-input-' + Math.random().toString(36).substring(2, 11); }
- 修改父组件模板,传递
parentForm:
<app-input class="w-full sm:w-[calc(100%/2-8px)] lg:flex-1" customClass="w-full" label="Destination" placeholder="City, Country" icon="ionBed" controlName="destination" [parentForm]="bookingForm" ></app-input>
- 修改子组件模板,绑定
formGroup:
<div [class]="'relative h-14'" [formGroup]="parentForm"> <input [id]="id" [type]="type" [placeholder]="placeholder" [class]=" 'absolute left-0 right-0 top-0 bottom-0 pl-4 pr-10 rounded-[4px] outline-none font-normal text-blackish-green border border-light-grey placeholder:text-blackish-green/85 ' + customClass " [formControlName]="controlName" /> @if (label) { <label [for]="id" class="absolute left-3 -top-[7.5px] bg-white text-blackish-green px-1 text-base font-normal leading-[17px] z-10" > {{ label }} </label> } @if(icon){ <ng-icon [name]="icon" class="absolute right-3 top-1/2 -translate-y-1/2 text-2xl" ></ng-icon> } </div>
此方案适合需要在子组件内直接操作父FormGroup的场景。
内容的提问来源于stack exchange,提问作者Just V
相关产品推荐
相关产品推荐

