Angular - 动态添加/删除单个组件的多个实例,而无需刷新页面以更新视图



这是我第三次尝试问这个问题。

这个问题会很长,因为我认为没有办法让它简短。

目的

我试图做的是有一个简单的Web应用程序,它有一个带有按钮的标题(我创建为HeaderComponent),一个页面(我创建为LiveSummaryComponent),将显示一个组件的多个实例(我创建为StatusBoxComponent)将显示不同的数据。

标题将有一个按钮,该按钮将打开一个模态对话框(我已将其创建为AddStatusBoxComponent),该对话框具有表单和提交按钮。提交表单后,表单数据将作为地图数据结构保存到浏览器的本地存储中,但此时我还想创建一个StatusBoxComponent实例,该实例将显示在LiveSummaryComponent页面上。

然后,该特定状态框组件实例应检索本地存储中的值以显示唯一值。我还希望能够通过单击StatusBoxComponent的每个实例上的按钮以及编辑按钮来删除StatusBoxComponent实例。

理想情况下,我还希望每次添加、编辑或删除新的StatusBoxComponent实例时都不必刷新页面来更新LiveSummaryComponent页面。

目前如何工作

由于我不久前问的这个问题的答案,我目前确实可以部分工作,但它有局限性,因为我找不到一种方法来删除和编辑StatusBoxComponent的每个实例中的信息。我只能在提交表单时添加。此外,每次添加新的StatusBoxComponent实例时,您都必须刷新页面以更新LiveSummaryComponent页面,这很烦人。

其工作方式是,当提交AddStatusBoxComponent表单时,它仅将表单数据放在地图数据结构中的本地存储中,并赋予唯一的名称/键。创建 StatusBoxComponent实例的方式是,在LiveSummaryComponentngInit()函数中,所有本地存储映射数据结构都经过迭代并传递给名为myConfigs的数组。然后,myConfigs数组中的每个 Map 都可以在名为DynamicStatusBoxDirective的指令中访问,该指令实际上使用ComponentFactoryResolver创建StatusBoxComponent的实例。然后,该特定的StatusBoxComponent填充来自传入config变量的数据,该变量来自LiveSummaryComponent中的myConfigs数组。

这种方式意味着DynamicStatusBoxDirective只需加载与本地存储映射一样多的StatusBoxComponent实例。它在ngInit()函数中执行此操作,因此它可以一次加载所有内容,而不是在单击按钮时动态添加,这也不理想。

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 代码:

<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')
}
}

我尝试过,在每个状态框组件实例上获得删除/编辑功能

我发现这段代码使您能够添加和删除组件的实例,但它有一个限制,即如果您无序删除组件,它会弄乱它使用的索引 ID 系统。这意味着您最终可能会得到两个具有相同 ID 的组件实例。 为了解决这个问题,我宁愿使用在每个本地存储映射中设置的deviceID值(这是一个字符串)来标识我的StatusBoxComponent的每个实例,而不是索引。(虽然我还不确定这将如何工作)。

此代码的另一个问题是调用createComponnent()的按钮全部位于一个组件中。就我而言,我希望让我的LiveSummaryComponent定义与我当前DynamicStatusBoxDirective相同的createComponent()函数。但是能够从AddStatusBoxComponent中的表单提交按钮调用/访问createComponent()。显然,它们是两个完全不同的组成部分。正如你从我问的这个问题中看到的,在 LiveSummaryComponent 之外访问该createComponent()函数会给我带来错误。不幸的是,后一个问题的解决方案我无法开始工作。

结论

我不确定我是否应该坚持使用DynamicStatusBoxDirective的原始方法,或者我应该尝试让我链接的示例代码能够实现我的目标。还是我应该做一些不同的事情?

单击组件上的删除按钮时,似乎也很难知道要删除StatusBoxComponent的哪个实例。本地存储中的每个地图数据结构都有一个我想使用的字符串deviceID值。但是我不知道如何将该deviceID字符串与特定的StatusBoxComponent实例相关联。

我完全被困在这一点上。

我希望我想要实现的目标至少是清楚的。请单击我的其他问题的链接,因为这将提供更多上下文。由于敏感信息,创建我的代码堆栈将非常困难。

谢谢。

您可以使用服务从本地存储和具有当前值数组的流中存储/读取数据。

@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>

把它想象成你有一个简单的状态数组,有一个唯一的id(映射键)并使用它。您在方法中使用本地存储的所有工作都是将数组转换为 Map 并保存到本地存储,反之亦然 - 将 Map 从本地存储转换为数组以显示您的状态。*ngFor将完成显示组件的所有工作

最新更新