【问题标题】:Angular circular dependency warning角度循环依赖警告
【发布时间】:2020-03-04 01:26:25
【问题描述】:

tl;dr:向下滚动到解决方案

我有一个循环依赖,我收到了一个警告,但是,我正在管理它。问题是我有一个聊天组件。在角落,您可以选择查看他们的个人资料页面,而在他们的个人资料页面中,您可以选择向他们发送消息,因此是循环依赖。我正在管理这个

聊天组件

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();
} 

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

更新: 根据下面的建议,我尝试创建服务。但是现在我有一个三向依赖圈:

聊天组件

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();
  }
}

更新 Stackblitz 对 Ionic 4 来说太不稳定了,所以我无法在上面复制,所以这里有一个 gist 包含信息和相关代码。

更新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);
  }
}

就是这样,尽管您仍然需要确保关闭模态框,否则它们将继续堆叠。

【问题讨论】:

    标签: angular ionic-framework


    【解决方案1】:

    遇到过几次这种情况。我每次都得到相同的解决方案,而且它对我来说非常适合,所以就这样吧。

    您将需要一项服务(正如其他人所建议的那样),但也需要一个,让我们称之为公正的玩家。这个想法是使用服务作为两个相互依赖的组件之间的通信/消息缓冲区,以帮助打破交叉引用。为了举例,我们假设“App.Component”。

    组件和职责:

    Modal.Service:接收消息以执行操作。它可以是通过单个方法接收指示动作的字符串或对象,也可以是每个动作的多个方法。实施细节由您决定。

    App.Component:获取注入的ModalService并订阅消息事件。根据action消息,然后激活相应的modal。

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

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

    这可以很好地扩展,并且该服务可以用作几个其他模块和/或组件之间的通信缓冲区。

    【讨论】:

    • 嗯,有道理,我试一试,告诉你是怎么回事
    • 工作就像一个魅力!非常感谢!!我将用我的重构方式更新帖子
    【解决方案2】:

    这有点烦人,但您需要包装器或多种服务。如您所见,单一服务将无法工作,因为显然您无法将组件导入服务,然后将服务导入组件。这只是一个稍大的圆圈。

    方法 1 是多种服务,不能很好地扩展。创建一个 ChatModalService 和一个 ProfileModalService 并注入它们的对立面。很简单,如果你不做太多,就会工作。

    方法 2 是更好的 IMO。将页面包装器放在处理模态调用的组件周围,您可以保留单一服务方法。

    像这样创建页面包装器组件:

    @Component({
      template: `<profile (openChat)="openChat()></profile>`
    })
    export class ProfilePageComponent {
       openChat() {
         // call your service or what have you here
       }
    }
    

    为聊天组件创建一个类似的设置,并将您的个人资料/聊天组件更改为只发出而不是调用服务(或者只是将用于调用模式的按钮放在包装器中)。希望您不要太频繁地使用这种双向模态关系。但这有效,因为包装器没有导入到组件中,您路由到页面包装器,但页面包装器在模态中实例化组件。缩放好一点,但仍然不理想。这里最大的好处是,在开发此应用程序时,如果给定组件可以显示为页面或模式,您可能会发现在组件周围使用页面包装器会带来更多好处,因为有时您希望组件以不同的方式位于其上下文中.如果您预见到这样做的好处,请采用这种方法。相反,您也可以将组件包装在 Modal 包装器中,并直接实例化它们而不是组件。导入逻辑是相同的,它出于相同的原因工作,并提供相同的上下文优势,但另一方面。

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

    @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())
      }
    }
    

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

    【讨论】:

    • 太棒了!感谢您的输入,我将不得不阅读第二次、两次、五次以了解所有包装逻辑和正确的方法来完成这一切。我会回来告诉你进展如何以及我选择了哪一个
    • 工作就像一个魅力!非常感谢!!我将用我的重构方式更新帖子
    • 很高兴听到这个消息!如果有任何其他问题或问题,请告诉我
    【解决方案3】:

    创建一个知道这两个组件的模式服务。

     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();
        } 
      }
    

    在两个组件中注入服务。

    您可能需要检查multiple instance services,以便每次注入新服务时都可以使用它。

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

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

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

    【讨论】:

    • 我的猜测是这只会给依赖增加另一层间接性
    • 好的,午饭后我会试一试,我会告诉你我做了什么,无论它是否按预期工作,或者我是否找到了不同的解决方案!非常感谢您的意见
    • 为什么会这样?任何组件都不会与其他组件耦合。它只是创建模态的一个组件
    • hm,尝试这样做会给我一个错误:Cannot access 'ModalService' before initialization 我看到了堆栈跟踪,但看起来我现在有很多侦探工作要做
    • 原因是因为我有一个 SharedModule,忘记在里面放一个组件,所以使用模态服务的组件在共享模块被声明之前就被声明了。
    猜你喜欢
    • 1970-01-01
    • 2019-11-30
    • 1970-01-01
    • 2017-08-17
    • 1970-01-01
    • 2021-01-08
    • 2019-04-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多