Angular 循环依赖警告

6

简而言之:向下滚动查看解决方案。

我有一个循环依赖,我收到了警告,但这是正确的,因为我正在管理它。问题是我有一个聊天组件,在角落里你可以选择查看他们的个人资料页面,而在他们的个人资料页面中你可以选择给他们发送消息,因此出现了循环依赖。我通过以下方式进行管理:

聊天组件

public async openProfile(): Promise<void> {
  this.modalCtrl.dismiss(); //closing the chat component before opening the profile modal
  const profile = await this.modalCtrl.create({
    component: ProfileComponent,
  });
  await profile.present();
} 

profile.component

public async openChat(): Promise<void> {
  this.modalCtrl.dismiss(); //closing the profile component before opening the chat modal
  const chat = await this.modalCtrl.create({
    component: ProfileComponent,
  });
  await chat.present();
} 

有没有更简单的方法处理这个循环依赖?

更新: 根据下面的建议,我尝试创建了一个服务。然而现在我有了一个三方依赖循环:

chat.component

private modalService: ModalService;

constructor(modalService: ModalService){
  this.modalService = modalService
}

public async openProfile(): Promise<void> {
  this.modalService.openProfile(this.userData);
} 

profile.component

private modalService: ModalService;

constructor(modalService: ModalService){
  this.modalService = modalService
}

public async openChat(): Promise<void> {
  this.modalService.openChat(this.userData);
}

modal.service

import { ModalController } from '@ionic/angular';
import { Injectable } from '@angular/core';
import { ProfileComponent } from '../../components/profile/profile.component';
import { ChatComponent } from '../../components/chat/chat.component';
import { UserData } from '../../interfaces/UserData/userData.interface';

@Injectable({
  providedIn: 'root',
})
export class ModalService {
  private modal: ModalController;
  public constructor(modal: ModalController) {
    this.modal = modal;
  }

  public async openProfileComponent(user: UserData): Promise<void> {
    this.modal.dismiss();
    const profile = await this.modal.create({
      component: ProfileComponent,
      componentProps: {
        contact: user,
      },
    });

    await profile.present();
  }

  public async openChatComponent(user: UserData): Promise<void> {
    this.modal.dismiss();
    const chat = await this.modal.create({
      component: ChatComponent,
      componentProps: {
        contact: user,
      },
    });

    await chat.present();
  }

  public close(): void {
    this.modal.dismiss();
  }
}

< p > 更新 Stackblitz 在使用Ionic 4时过于不稳定,因此我无法在其中复制它,因此这里有一个要点以及相关代码。

更新2 我采纳了答案中提到的建议,但仍然收到错误信息。为此,我创建了一个名为shared.module.ts的文件,其内容如下:

import { UserService } from './componentServices/user/user.service';
import { ModalService } from './componentServices/modal/modal.service';
import { AuthenticationSecurityService } from './componentServices/auth_security/authentication-security.service';
import { AuthGuardService } from '../_guards/auth-guard.service';
import { ApiService } from './componentServices/api/api.service';
import { ChatService } from './components/chat/socketIO/chat.service';

@NgModule({
  imports: [CommonModule, ReactiveFormsModule, IonicModule.forRoot(), FormsModule, IonicModule],
  declarations: [
    // various components
  ],
  exports: [
    // various components and common modules
  ],
})
export class SharedModule {
  static forRoot(): ModuleWithProviders {
    return {
      ngModule: SharedModule,
      providers: [
        UserService,
        ModalService,
        DashboardService,
        AuthenticationSecurityService,
        AuthGuardService,
        ApiService,
        ChatService,
      ],
    };
  }
}

app.module.ts

imports: [
    SharedModule.forRoot(),
]

client:135 Circular dependency detected:
src/sharedModules/componentServices/modal/modal.service.ts -> src/sharedModules/components/profile/profile.component.ts -> src/sharedModules/componentServices/modal/modal.service.ts

client:135 Circular dependency detected:
src/sharedModules/components/chat/chat.component.ts -> src/sharedModules/components/search/search.component.ts -> src/sharedModules/components/profile/profile.component.ts -> src/sharedModules/componentServices/modal/modal.service.ts -> src/sharedModules/components/chat/chat.component.ts

client:135 Circular dependency detected:
src/sharedModules/components/profile/profile.component.ts -> src/sharedModules/componentServices/modal/modal.service.ts -> src/sharedModules/components/profile/profile.component.ts

client:135 Circular dependency detected:
src/sharedModules/components/search/search.component.ts -> src/sharedModules/components/profile/profile.component.ts -> src/sharedModules/componentServices/modal/modal.service.ts -> src/sharedModules/components/chat/chat.component.ts -> src/sharedModules/components/search/search.component.ts

解决方案

正如 @bryan60 和 @Luis 所说,必须有一个缓冲区,所以我按照他们两个都建议的发射路线来做。Bryan 给出了更多的代码样式,而 Luis 则提供了很好的职责概述。这是我重构的方式:

app.component.ts

  public initializeApp(): void {
    this.platform.ready().then((): void => {
      this.statusBar.styleDefault();
      this.splashScreen.hide();
      this._subToObservables();
    });
  }

  private _subToObservables(): void {
    this.modalService.openModal$.subscribe(
      async (e: ModalEmitInterface): Promise<void> => {
        const { command, data } = e;
        switch (command) {
          case 'open-profile':
            const profile = await this.modalCtrl.create({
              component: ProfileComponent,
              componentProps: {
                contact: data,
              },
            });
            await profile.present();
            break;

          case 'open-chat':
            // same as above
            break;

          default:
            break;
        }
      },
    );
  }

modalSignal.service.ts

export class ModalService {
  private openModalSubject: Subject<ModalEmitInterface> = new Subject<ModalEmitInterface>();
  public readonly openModal$: Observable<ModalEmitInterface> = this.openModalSubject.asObservable();

  private emitPayload: ModalEmitInterface;
  public openProfileComponent(user: UserData): void {
    this.emitPayload = {
      command: 'open-profile',
      data: user,
    };
    this.openModalSubject.next(this.emitPayload);
  }

  // repeat for others
}

chat.component.html

<button (click)="openProfile(user)">click me</button>

chat.component.ts

export class ChatComponent {
  public constructor(private modalSignal: ModalService){}

  private openProfile(user: UserData): void {
    this.modalSignal.openProfileComponent(user);
  }
}

就是这样,不过您仍需确保关闭模态框,否则它们将继续叠加。

3个回答

3

我曾多次遇到这种情况。每次我都采取了相同的解决方案,而且效果很好。下面是我的做法。

你需要一个服务(如其他人所建议的),还需要一个客观的参与者。使用服务作为两个相互依赖组件之间通信/消息缓冲区,以帮助打破交叉引用。以“应用程序组件”为例。

组件和职责:

Modal.Service:接收消息以执行操作。可以通过单个方法接收指示操作的字符串或对象,也可以通过每个操作的多个方法来实现。具体实现细节取决于您。

App.Component:获取注入的ModalService并订阅消息事件。根据操作消息,然后激活对应的模态框。

Chat.Component:获取注入的Modal.Service并发送指示要执行的操作的消息,例如显示配置文件。

Profile.Component:获取注入的Modal.Service并发送指示要执行的操作的消息,例如发送消息。

这种方法适用范围广泛,服务可用作几个其他模块和/或组件之间的通信缓冲区。


嗯,那很有道理,我会尝试一下并告诉你结果。 - Ctfrancia
太棒了!非常感谢!!我将更新帖子,说明我进行了如何重构。 - Ctfrancia

1
It's kind of annoying but you need wrappers or multiple services. Single service won't work as you've seen because clearly you can't import your component into the service and then the service into your component. That's just a slightly bigger circle.
Approach 1 is multiple services, which doesn't scale great. Create a ChatModalService and a ProfileModalService and inject them into their respective opposites. It's pretty straightforward and will work if you're not doing this too much.
Approach 2 is a little nicer in my opinion. Put page wrappers around your components that handle the modal calls and you can keep your single service approach.
Create page wrapper components like this:
@Component({
  template: `<profile (openChat)="openChat()></profile>`
})
export class ProfilePageComponent {
   openChat() {
     // call your service or what have you here
   }
}

为聊天组件创建一个类似的设置,并更改您的个人资料/聊天组件,使其仅发出信号而不是调用服务(或者只需将调用模态框的按钮放在包装器中)。希望你没有这种双向模态关系发生得太频繁。但是由于包装器没有导入到组件中,因此在路由到页面包装器时,页面包装器会在模态框中实例化组件。可扩展性略有提高,但仍不理想。这里的重大好处是,在开发此应用程序时,如果一个给定组件可以显示为页面或模态框,则您可能会发现在组件周围加上页面包装器有更多好处,因为有时候您需要以不同的方式将组件放在其上下文中。如果您预见到此类利益,则采用此方法。相反,您也可以在模态框包装器中包装您的组件,并实例化那些包装器,而不是直接实例化组件。导入逻辑相同,并且出于相同的原因而起作用并提供相同的上下文优势,但在另一方面有相同的缺点。

第三个选项类似,设置一个通用页面包装器,并稍微更改您的模态服务,使其只是共享通用页面包装器的事件总线。出于相同的原因,它与上述方法相同并且可扩展性更好,但缺点是您无法以同样的方式为组件添加上下文。

@Injectable()
export class ModalSignalService{
  private openChatSubject = new Subject()
  openChat$ = this.opopenChatSubject.asObservable()
  openChat() {
    this.openChatSubject.next()
  }
  private openProfileSubject = new Subject()
  openProfile$ = this.openProfileSubject.asObservable()
  openProfile() {
    this.openProfileSubject.next()
  }
}

然后,创建一个共享页面包装组件来订阅流并处理模态实例化。
@Component({
  template: `<router-outlet></router-outlet>` // something like this and set up routing with components as child routes
})
export class PageWrapperComponet {

  constructor(private modalSignalService: ModalSignalService) {
    this.modalSignalService.openChat$.subscribe(e => this.openChatModal()) // open modal logic here
    this.modalSignalService.openProfile$.subscribe(e => this.openProfileModal())
  }
}

如果你预见到这个问题会一次又一次地出现,就像这样一劳永逸地解决它。你可能已经有一个(你肯定有一个应用程序组件可以做到这一点,尽管可能不是最好的)。

太好了!感谢您的输入,我需要再读两遍,五遍才能理解所有的包装器逻辑和正确的操作方式。我会回来告诉您进展如何以及我选择了哪一个。 - Ctfrancia
非常顺利!非常感谢!!我将更新帖子,介绍我是如何重构的。 - Ctfrancia
很高兴听到这个消息!如果有任何进一步的问题或问题,请告诉我。 - bryan60

0
创建一个模态服务,它了解两个组件。
 ModalService {
     public async openChat(): Promise<void> {
         this.modalCtrl.dismiss(); //closing the profile component before 
         opening the chat modal
         const chat = await this.modalCtrl.create({
         component: ProfileComponent,
     });

     public async openProfile(): Promise<void> {
             this.modalCtrl.dismiss(); //closing the chat component before opening the 
             profile modal
             const profile = await this.modalCtrl.create({
             component: ProfileComponent,
         });
        await profile.present();
    } 
  }

在两个组件中注入服务。

您可能想要检查多实例服务,以便每次注入时都可以获得新的服务。

现在这两个组件互不相识,因此没有循环依赖。

为了消除警告,您应该通过组件中的注入器进行注入。

private modalService: ModalService;
public constructor(injector:Injector) {
    this.modalService = injector.get(modalService);
}

我的猜测是,这只会给依赖关系增加另一层间接性。 - Normunds Kalnberzins
好的,午饭后我会尝试一下,并告诉你我做了什么,无论它是否按预期工作,或者我是否找到了其他解决方案!非常感谢你的建议。 - Ctfrancia
为什么会呢?没有一个组件会与另一个组件耦合。只是一个组件创建了一个模式窗口。 - Athanasios Kataras
你能否发布一个带有相关示例的 StackBlitz 吗?这将有助于找到解决方案。 - Athanasios Kataras
抱歉,我无法提供StackBlitz,我一直在尝试,但目前(StackBlitz)与Ionic 4存在问题。然而,这里有一个gist。 - Ctfrancia
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接