This is my 3rd attempt at asking this question.
This question will be long since I see no way of making it short.
What I’m trying to do is to have a simple web-app that has a header (which I’ve created as HeaderComponent) with buttons, a single page (which I’ve created as LiveSummaryComponent) that will display multiple instances of a component (which I’ve created as StatusBoxComponent) that will display different data.
The header will have a button that will open up a modal dialogue (which I’ve created as AddStatusBoxComponent) which has a form and a submit button on. When the form is submitted the form data is saved to the browsers local storage as a map data structure, but also at that point I would like to create a single instance of a StatusBoxComponent that will be displayed on the LiveSummaryComponent page.
That particular StatusBoxComponent instance should then retrieve the values in local storage to display unique values. I would also like to be able to remove StatusBoxComponent instances by clicking a button on each instance of StatusBoxComponent as well as an edit button.
Ideally I’d also like to be able to not have to refresh the page to update LiveSummaryComponent page each time a new StatusBoxComponent instance is added, edited or removed.
I do have this partially working at the moment thanks to an answer on this question I asked a while back, but it has limitations in that I couldn’t find a way to remove and edit information in each instance of a StatusBoxComponent. I’m only able to add when submitting the form. Also you have to refresh the page to update the LiveSummaryComponent page each time a new StatusBoxComponent instance is added which is annoying.
The way this works is that when the AddStatusBoxComponent form is submitted it only puts the form data in the local storage in a map data structure and given a unique name/key. The way in which StatusBoxComponent instances are created is that in the ngInit()
function in the LiveSummaryComponent all of the local storage map data structures are iterated through and passed to an Array called myConfigs
. Each Map within the myConfigs
array is then accessed in a directive called DynamicStatusBoxDirective
which actually creates the instances of the StatusBoxComponent using the ComponentFactoryResolver
. That particular StatusBoxComponent is then populated with it’s data from the passed in config
variable which comes from the myConfigs
array in LiveSummaryComponent.
This way of doing it means that the DynamicStatusBoxDirective
simply loads as many StatusBoxComponent instances as there are are local storage maps. And it does this within the ngInit()
function so it loads everything in at once rather than dynamically adding at the click of a button, which also isn't ideal.
LiveSummaryComponent ts code:
import { Component, OnInit,} from '@angular/core';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-live-summary',
templateUrl: './live-summary.component.html',
styleUrls: ['./live-summary.component.css']
})
export class LiveSummaryComponent implements OnInit
{
subscription!: Subscription;
myConfigs: any[] = [];
localStorageKeys: string[] = ['Old', 'New', 'Current', 'Test']
constructor() { }
ngOnInit(): void
{
for (var key of this.localStorageKeys) // iterates though each type of device
for(let i = 1; i < 5; i++) // for each of those device types
if (localStorage.getItem(key+i+'Topics') != null) // if the device + topics string is not null in local storage
this.myConfigs.push(new Map(JSON.parse(localStorage.getItem(key+i+'Topics')!)))
else
console.log(key+i + ' currently not deployed');
}
}
LiveSummaryComponent html code:
<div class='status-box-container'>
<ng-container *ngFor="let config of myConfigs; let i=index" appDynamicStatusBox [config]="config"></ng-container>
</div>
DynamicStatusBoxDirective ts code:
import { ComponentFactoryResolver, ComponentRef, Directive, Input, OnDestroy, OnInit, ViewContainerRef } from '@angular/core';
import { StatusBoxComponent } from '../components/status-box/status-box.component';
@Directive({selector: '[appDynamicStatusBox]'})
export class DynamicStatusBoxDirective implements OnInit, OnDestroy
{
@Input() config: any;
componentRef!: ComponentRef<StatusBoxComponent>;
constructor(private resolver: ComponentFactoryResolver, public viewContainerRef: ViewContainerRef) {}
ngOnInit(): void
{
const factory = this.resolver.resolveComponentFactory(StatusBoxComponent);
this.viewContainerRef.clear();
this.componentRef = this.viewContainerRef.createComponent(factory);
// set each StatusBoxComponent vars with values from myConfig
this.componentRef.instance.deviceId = this.config.get('deviceId')
this.componentRef.instance.infoExample1 = this.config.get('infoExample1')
this.componentRef.instance. infoExample2 = this.config.get('infoExample2')
this.componentRef.instance. infoExample3 = this.config.get('infoExample3')
}
}
I’ve found this code that enables you to add and remove instances of a Component, but it has a limitation in that if you remove a component out of order, it messes up the indexing ID system it uses. Meaning that you can end up with two component instances with the same ID.
To get around this I’d rather use the deviceID
value (which is a string) which gets set in each local storage Map to identify each instance of my StatusBoxComponent rather than an index. (Although I’m not sure how this would work yet).
The other problem with this code is that the button that calls the createComponnent()
is all within one component. In my case I would want to have my LiveSummaryComponent define the createComponent()
function that does the same as my current DynamicStatusBoxDirective
. But be able to call / access the createComponent()
from my form submission button in the AddStatusBoxComponent. Obviously they are two completely different components. And as you can see from this and this question I asked, accessing that createComponent()
function outside of the LiveSummaryComponent gives me errors. And the solution in the latter question I wasn’t able to get working unfortunately.
I’m not sure if I should stick to my original approach of using the DynamicStatusBoxDirective
or should I try to get the example code I linked working to be able to meet my objective. Or should I do something different?
It also seems difficult to be able to know which instance of the StatusBoxComponent to remove when clicking on a remove button on the component. Each map data structure in local storage does have a string deviceID
value which I'd like to use. But I have no idea how to associate that deviceID
string with a particular StatusBoxComponent instance.
I’m completely stuck on this.
I hope what I’m trying to achieve is at least clear. And please click the links to my other questions as this will provide a lot more context. Creating a stackblitz of my code will be very difficult due to sensitive information.
Thanks.
you can use service to store/read data from localStorage and a stream with array of current values.
@Injectable()
export class SomeService {
constructor() {
this._initStream();
}
//your stream with statuses
public statuses$ = new BehaviorSubject([]);
private _initStream(){
// get data from localStorage
// transform Map to Array
this.statuses$.next(transformedDataToArray);
}
// add status, use it in your modal
addStatus(props) {
...your logic to add
this._updateDataInLocalStorage();
this._updateStream();
}
// use it in your StatusBoxComponent
removeStatus() {
...your logic to remove
this._updateDataInLocalStorage();
this._updateStream();
}
// use it in your StatusBoxComponent
updateStatus() {
...your logic to remove
this._updateDataInLocalStorage();
this._updateStream();
}
// set current value to stream
_updateStream() {
const statuses = localStorage.getItem();
this.statuses$.next(statuses)
}
// update data in localStroage
_updateDataInLocalStorage() {
...some logic here
}
LiveSummaryComponent html file
<app-status-box *ngFor="let status of SomeService.statuses$ | async; let i=index" [props]="status" [index]="index"></app-status-box>
Think of it like you have a simple array of statuses with a unique id (map key) and work with it. All your work with local storage inside your methods is to convert your array into a Map and save to local storage, and vice versa - convert Map from local storage to an array to display your statuses. And *ngFor
will do all the work of displaying the components