В NgRx как я могу сохранить тип во всем состоянии моего приложения, следуя рекомендациям?

#angular #typescript #state #ngrx

#angular #typescript #состояние #ngrx

Вопрос:

Использование Angular 11.1.2 и rxjs 6.6.2

Я хотел бы создать приложение, которое динамически отображает список компонентов. Я добился этого самостоятельно. Проблемы, с которыми я сейчас сталкиваюсь, связаны с переходом к управлению состоянием через NgRx.

Я использую следующую модель:

 import { Type } from '@angular/core';

export enum Position { Default, Top, Bottom }

export class NodeModel {
  constructor(
    public type: Type<any>,
    public position: Position
  ) {}
}
 

Который передается через серию *ngFor и @Input() , которые будут использоваться в службе для создания динамических компонентов:

 @Component({
  selector: 'app-node-body',
  template: '<ng-container #container></ng-container>',
  styleUrls: ['./node-body.component.scss']
})
export class NodeBodyComponent implements AfterViewInit {
  @ViewChild('container', { read: ViewContainerRef, static: false }) viewRef;
  @Input() type: Type<any>;
  
  constructor(private nodeBodyService: NodeBodyService) { }
  
  ngAfterViewInit(): void {
    this.nodeBodyService.createDynamicComponent(this.type, this.viewRef);
  }
}
 
 @Injectable()
export class NodeBodyService {
  constructor(private cfr: ComponentFactoryResolver) {}
  
  createDynamicComponent<T>(component: Type<T>, viewRef: ViewContainerRef): ComponentRef<any> {
    const factory = this.cfr.resolveComponentFactory<T>(component);
    return viewRef.createComponent(factory);
  }
}
 

На этом этапе приложение правильно создает динамические компоненты.

Я сталкиваюсь с проблемами при переключении моего подхода на использование NgRx, чтобы начать мониторинг состояния моего приложения. Следующий подход не позволяет мне видеть Type<any> значение ни в состоянии, ни в соответствующем body.component.ts , и, следовательно, динамические компоненты не отображаются.

 // node-list.actions.ts
export const SET_NODES = '[Node List] Set Nodes';

export class SetNodes implements Action {
  readonly type = SET_NODES;
  constructor(public payload: NodeModel[]) {}
}

export type NodeListActions = SetNodes;
 
 // node-list.reducer.ts
export interface State {
  nodes: NodeModel[];
}

export function nodeListReducer(
  state: State,
  action: NodeListActions.NodeListActions
) {
  switch (action.type) {
    case NodeListActions.SET_NODES:
      return {
        ...state,
        nodes: action.payload
      };
    default:
      return state;
  }
}
 
 import * as fromApp from './store/app.reducer';
import * as NodeListActions from './node-list/store/node-list.actions';

@Component({
  selector: 'app-root',
  template: '<app-node-list [nodes]="nodes"></app-node-list>',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  nodes: NodeModel[] = [
    new NodeModel(SuperHeaderComponent, Position.Default),
    new NodeModel(IntroductionComponent, Position.Bottom)
  ];
  
  constructor(private store: Store<fromApp.AppState>) {}
  
  ngOnInit(): void {
    this.store.dispatch(new NodeListActions.SetNodes(this.nodes));
  }
}
 

Это то, что появляется после SET_NODES отправки действий:

 {
  nodeList: {
    nodes: [
      {
        position: 0
      },
      {
        position: 2
      }
    ]
  }
}
 

Я распознаю использование Type<any> во время выполнения результатов { position: 0, type: Function } , которые не являются «подобными состоянию». Итак, я пошел по пути, попробовав сопоставление типов, например, Map<string, Type<any>> наряду с изменением моего NodeModel

 import { Type } from '@angular/core';

export enum Position { Default, Top, Bottom }

export class NodeModel {
  constructor(
    public type: string, // <-- Switched this so that it is visible in state
    public position: Position
  ) {}
}
 

Это решило мою проблему в отношении отображения того, что я хочу видеть в своем состоянии приложения, но я все еще не смог отобразить свои динамические компоненты, поскольку появляется следующая ошибка:

 ERROR TypeError: Cannot add property __NG_ELEMENT_ID__, object is not extensible
 

Из того, что я прочитал до сих пор, кажется, что я мог бы отключить эту ошибку, если захочу, но это не лучшая практика.

Правильно ли я подхожу к управлению состоянием, пытаясь сохранить Type<any> или даже идею ссылки на тип для начала?
Если это не подходит для управления состоянием, как я могу отслеживать динамические компоненты и их соответствующее состояние; когда я решу эту проблему, я буду стремиться к тому, чтобы состояние передавалось из динамических компонентов обратно в корень?

Ответ №1:

Мне удалось решить мою проблему, не жертвуя неизменностью состояния. В итоге я ошибся, предполагая, что я не сохранил Type<any> в своем объекте состояния.

Type<any> был сохранен в моем состоянии приложения, но из-за того, что он был a Function , я не мог видеть его в redux devtools. Я прибегнул к корректировке моего объекта состояния следующим образом:

 export interface Node {
  type: string;
  position: Position;
}
 

Затем я обновил свой сервис, чтобы использовать карту типов:

 @Injectable()
export class NodeBodyService {
  
  constructor(private cfr: ComponentFactoryResolver) {}
  
  nodeTypes: Map<string, Type<any>> = new Map([
    [SuperHeaderComponent.name, SuperHeaderComponent],
    [IntroductionComponent.name, IntroductionComponent]
  ]);
  
  createDynamicComponent(component: string, viewRef: ViewContainerRef): void {
    const nodeType: Type<any> = this.nodeTypes.get(component);
    viewRef.createComponent(this.cfr.resolveComponentFactory(nodeType));
  }
}
 

Теперь я могу видеть визуализацию моих динамических компонентов и соответствующее состояние