Как получить FormGroup из ng-content

Не могу реализовать следующую задачу: есть кастомная раскрывающаяся панель, в которую вкладывается различный контент (по типу mat-expansion-panel), контент проецируется через ng-content, необходимо реализовать визуализацию наличия не валидных контролов вложенных форм на закрытой панели. При этом нужно понимать, что степень вложенности форм может быть различной (компонент в компоненте), возможно наличие n-количества форм, динамическая генерация форм. @ContentChildren, как оказалось, здесь не поможет, так как различная глубина вложенности. Соответственно, nativeElement нельзя использовать.

Пример Есть компонент, в котором используется panel с вложенным panel-content, в котором может создаваться formGroup.

        <panel>
            <panel-header>
                <div class="title">{{subgroup.name}}</div>
            </panel-header>
            <panel-content>
                <simple-group-card [group]="subgroup"></simple-group-card>
            </panel-content>
        </panel>

В свою очередь в panel компонент проецируются через ng-content дочерние компоненты panel-header и panel-content.

<ng-content select="panel-header"></ng-content>
<div class="panel-content" *ngIf="promoted">
  <ng-content select="panel-content"></ng-content>
</div>

В panel компоненте через декоратор @ContentChild получаю PanelContentComponent

@ContentChild(PanelContentComponent, {static: true})
private _content;

Получаю объект компонента (в консоли), в котором содержится FormGroup

PanelContentComponent {__ngContext__: LEmbeddedView_SimpleGroupCardComponent_ng_container_1_ng_container_1(75)}
__ngContext__: LEmbeddedView_SimpleGroupCardComponent_ng_container_1_ng_container_1(75)
  0: simple-group-card
  1: TView {type: 2, blueprint: LViewBlueprint(75), queries: TQueries_, viewQuery: null, template: 
  ...
  28: FormGroup {_hasOwnPendingAsyncValidator: false, _parent: null, pristine: true, touched: false, _onCollectionChange: ƒ, …} 

Как получить ссылку на эту FormGroup? Данный подход не работает:

@ContentChild(PanelContentComponent, {static: true, read: FormGroup})
private _content;

Необходимо узнать, присутствует ли FormGroup в компоненте


Ответы (1 шт):

Автор решения: Aleks-JS

Опишу, как я решил данный вопрос. Добавил класс, реализующий хранение состояния. Также добавил токен для передачи инстанса класса дочерним компонентам:

export const CONTROLS_STATE_TOKEN = new InjectionToken<IControlsState>('CONTROLS_STATE_TOKEN');

export interface IControlsState {   
    isInvalid: boolean; 
    addControl(control: AbstractControl): void;
    removeControl(control: AbstractControl): void;
}

export class ControlsState implements IControlsState {
    private _controls: AbstractControl[] = [];
    
    get isInvalid(): boolean {
        return this._controls.some(control => control.invalid);
    }
    
    remove(control: AbstractControl): void {
        const index = this._controls.indexOf(control);
        if (index > -1) {
            this._controls.splice(index, 1);
        }
    }
    
    add(control: AbstractControl): void {
        if (!this._controls.includes(control)) {
            this._controls.push(control);
        }
         
    }   
}

В родительском компоненте, в котором необходимо получить информацию о вложенных контролах, использую провайдер фабрику, создаю экземпляр класса:

providers: [{
    provide: CONTROLS_STATE_TOKEN,
    useFactory: (): ControlsState=> new ControlsState()
}]

и инжектирую токен в конструкторе

constructor(
    @Inject(CONTROLS_STATE_TOKEN)
    private _сontrolsState: ControlsState
) {}

получаю информацию об инвалидных контролах

public get isInvalid(): boolean {
    return  this._сontrolsState.isInvalid;
}

Во всех компонентах с контролами (у нас это библиотека кастомных ControlValueAccessor компонентов) также инжектирую токен в конструктор и добавляю контрол компонента в ControlsState:

constructor(
    @Optional()
    @Inject(CONTROLS_STATE_TOKEN)
    private _сontrolsState: ControlsState,
    private _control: NgControl
) {}

ngOnInit(): void {
    this._сontrolsState?.add(this._control.control);
}

ngOnDestroy(): void {
    this._сontrolsState?.remove(this._control.control);
}

Теперь родительский компонент получает состояние всех контролов из дочерних компонентов, не зависимо от глубины вложенности и наличия/отсутствия форм.

Данный подход очень удобен, подойдёт для реализации различной логики.

→ Ссылка