Home > Mobile >  Angular managing state of nested components
Angular managing state of nested components

Time:10-18

Using Angular 11, I am trying to build something similar to what Google Admin console uses for editing different parts of a form. I call it inline-editing. I have the basic functionality working, but I am trying to figure out how to handle only opening up one "editor" at a time. If I am editing in one panel and I click on the other panel, I want the current editor to close and the other one to open. I'm NOT wanting anything quick and dirty... I would prefer a solution that would allow me to be able to manage the "states" of each editor. Here is my working code:

Example Stackblitz

Any examples of what I am trying to achieve out there?

CodePudding user response:

simple version: Add an id to your inline-editor. Then create a service and have it manage a BehaviourBubject in which you save the id of the currently active id.

In inline-editor.component.ts instead of calling this.editing = true call serviceObject.editing.next(editorId);. Also in ngOnInit subscribe to the subject and change editing acordingly: serviceObject.editing.subscribe(id => editing = (id === this.id));

Instead of the BehaviourBubject<number> you could also do BehaviourBubject<boolean[]> and save editing for all editors in there.

complex version: use ngrx store

CodePudding user response:

Often times the simplest solutions work best in such scenarios. You say 'manage state in nested components' this means there is a parent and child. Due to lack of code provided in the question using my own example. In other words use Master => Detail pattern with Inputs Outputs.

Master component GeneralContainerComponent:

@Component({
    selector: 'odm-general-container',
    templateUrl: './general-container.component.html',
    styleUrls: ['./general-container.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class GeneralContainerComponent implements OnInit {
    availableEditors$: Observable<{ //...some properties; expanded: boolean; }[]>
    constructor(private _sb: AccountSandboxService) {
        // the account sandbox service handles getting a reference to available editors
        this.availableEditors$ = _sb.availableEditors$;
    }

    onEditorScreenClicked(event: number): void {
        // now just dispatch an action to your store or state service 
        // that will handle the update of the available editors. 
        // So this means change all other editors expanded property to false 
        // and the one that matches this id to true
        this._sb.expandEditorById(event);
    }
}

Master component GeneralContainerComponent template:

<ng-container *ngIf="availableEditors$ | async as editors">
   <ng-contianer *ngFor="let editor of editors">
      <odm-editor 
         [editor]="editor" 
         (editorClicked)="onEditorScreenClicked($event)">
      </odm-editor>
   </ng-container>
</ng-container>

Detail component EditorComponent :

@Component({
    selector: 'odm-editor',
    templateUrl: './editor.component.html',
    styleUrls: ['./editor.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class EditorComponent {
   @Input() editor: { //...some properties; expanded: boolean; };
   @Output() editorClicked = new EventEmitter<number>();

   // the id of the editor clicked could come from the template or from some property
   onEditorClicked(event: number): void 
   {
      this.editorClicked.emit(event);
   }
}

Detail component EditorComponent template:

<ng-template *ngIf="editor?.expanded; else collapsedEditor">
   // display expanded editor
</ng-container>

<ng-template #collapsedEditor>
   ... html for collapsed editor
</ng-template>

At this point your app is wired up to respond to changes to availableEditors$ observable coming from a service. So if you are NOT using NGXS or NGRX you have to update the array yourself and then emit the entire array again so Angular can respond to the change and run changeDetection for all of your editors to see which one is now open.

@Injectable()
export class AccountSandboxService {    
    availableEditorsSub: BehaviorSubject<{//...some properties; expanded: boolean;}[]>
    availableEditors$ = this.availableEditorsSub.asObservable();
    
    expandEditorById(editorId: number): void {
        // I think data on BehaviorSubject gives you access to raw data
        const updatedEditors = availableEditorSub.data.map(editor => {
        if(editor.id === editorId){
            editor.expanded = true;
        }
        else{
            editor.exapnded = false;
        }
            return editor;
        })
        this.availableEditorsSub.next(updatedEditors);    
    }
       
}

Does this make sense? I haven't tested the code but I'm hoping this will give you an idea of how you can approach this problem.

  • Related