如何在出现复杂依赖关系时编写可重用组件

问题描述 投票:0回答:2

我几个星期以来一直在研究角度,我想知道如何在没有太多依赖性和重复性的情况下编写干净的组件。

我不得不说我的问题与已经存在的大型架构有关。

假设我需要编写一个显示2个单选按钮选项的组件:您可以选择“播放列表1”或“播放列表2”。该组件有一个名为“PlayerService”的服务,它有一个名为“Play”的方法,它接受一组歌曲。我们的想法是选择“播放列表1”或“播放列表2”,然后在按钮单击时,使用正确的播放列表调用PlayerService。

这是一个基本实现:此代码仅用于演示。

BasicComponent.ts

import { PlayerService } from '../services/player.service';
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-basic',
  templateUrl: './basic.component.html',
  styleUrls: ['./basic.component.css']
})
export class BasicComponent implements OnInit {

  private playlistArray = [
    { name: "playlist1", sounds: ["sound1.mp3", "sound2.mp3"]},
    { name: "playlist2", sounds: ["sound2.mp3", "sound3.mp3", "sound4.mp3"]}
  ]

  public SelectedPLayListName: string;

  constructor(private playerService: PlayerService) { }

  ngOnInit() {
  }

  onPlayButtonClicked(): void {
    var playList = this.playlistArray.find(x => x.name == this.SelectedPLayListName);

    this.playerService.Play(playList.sounds);
  }
}

PlayerSercice.ts

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class PlayerService {

  constructor() {}

  public Play(sounds: any[]): void  {
    console.log("Start Play on a Playlist containing " + sounds.length + " elements");
  }
}

因此,正如我上面提到的,我们有BasicComponent,它在一个数组中有2个播放列表,2个单选按钮和一个用于验证选择的按钮。在验证时,组件为PlayerService提供正确的数组。

这不是我希望/我可以基于当前项目架构实现的行为。

BasicComponent不能在自己的类中拥有PlayList数组。相反,我希望能够从父组件中为他提供播放列表数组作为例子的输入。这可以很容易地完成,声明@Input等等。

这仍然不是我希望/我可以基于当前项目架构实现的行为。

我不希望我的BasicComponent将PlaylistArray作为@Input,我只想要一个播放列表对象。我的用户通过单击我的BasicComponent中的单选按钮选择的那个。所以我希望我的BasicComponent能够显示Playlist1和Playlist2之间的选择,然后以某种方式从他的父母那里请求正确的播放列表并将其提供给PlayerService

我想出了两种做法:

  • @输入输出 : 通过@Input参数从父组件获取playList。 然后在用户点击PlayButton时,BasicComponent“发出他的@Ouput”​​,名为“OnPlay”,并将所选播放列表的名称作为参数 父对此@Output作出反应并检索好的播放列表并将其设置为BasicComponent的输入 BasicComponent实现了一个逻辑,用于检测Input上的更改,如果由于我们发出输出而发生更改,那么他可以使用此播放列表调用服务上的play方法。

我认为这有很多丑陋的代码,主要是因为@ output.emit和@input的实际更改之间的延迟。这个想法根本不是一个好主意,但我想到了。

  • @Input a Service: 创建包含方法GetPlayList(name:string)的接口IPlayListProvider。 从IPlayListProvider创建服务PlayListProvider和Inherit。 在我的BasicComponent中创建一个类型为IPlayListProvider的@Input。 所以父级可以(从构造函数中的DI)并存储PlayListProvider的实例。 因此,Parent可以将此实例作为BasicComponent的输入传递给服务。 当用户单击PlayButton时,BasicComponent现在可以使用单选按钮选项中的SelectedPlayListName调用方法GetPlayList。

我喜欢这个实现,因为这个服务并没有很好地耦合到我的BasicComponent,我可以重用我的组件并提供另一个也实现这个接口的服务,它会工作。即使我需要做一些逻辑,我也可以保留所有应该存在的东西,PlayListProvider服务可以检索播放列表。唯一的事情是,我不知道它是否像Dangerous这样做,如果它可以破坏我的Angular项目,如果它反对角度最佳实践。

或许还有另一种方式? (保持事物分离,我的组件可重复使用)

你怎么看 ?你会怎么做?

编辑1:

在我原来的帖子版本中,我没有谈到我的要求:实际上我实际上在现有代码中有这种实现。 “父组件”已经存在,并且不存储任何播放列表,但是,他具有可用于按名称检索播放列表的服务。根据业务需求,我不能在我的组件中直接使用此服务。我必须通过这个父组件,我可以修改父级但不是太多,不是结构或行为。因此,我的组件需要作为“中间件”工作,提供选择,并向其他服务或组件请求数据并将其传递给播放器。更重要的是,这个输入系统需要是可重用的,我应该能够接受该组件,并且可以说,将它放到一个新项目中,并且他需要在这里工作,而不需要在这个组件内进行任何修改。这是一个很大的约束,但这是我无法打破的约束。

angular typescript design-patterns components interaction
2个回答
2
投票

好的做法是数据管理进入服务,模板管理进入组件/指令/管道......

第二个好的做法是Angular提供了一个async管道,真正简化了RxJS功能的管理,如可观察和主题。

第三个好的做法是你的角度特征应该只有一个责任。

第四个好的做法是你应该减少变化检测迭代。由于以前的良好实践,可以轻松完成。

现在让我们开始编码吧。

首先是你的HTTP服务

export class HttpService {
  constructor(private http: Httpclient) {}

  getPlaylists() {
    return this.http.get('...');
  }
}

现在,您的商店/缓存/数据服务:

export class StoreService {
  constructor(private httpService: HttpService) {
    this.refreshPlaylists();
  }

  playlists$ = new BehaviorSubject([]);

  refreshPlaylists() {
    this.httpService
      .getPlaylists()
      .subscribe(list => this.playlists$.next(list));
  }

  addPlaylist(playlist) {
    this.playlists$
      .pipe(first())
      .subscribe(list => this.playlists$.next([...list, playlist]));
  }
}

您还有自己的播放器服务,但仍保持不变。

现在,在您的组件中:

export class BasicComponent {
  constructor(
    public data: StoreService,
    public playerService: PlayerService
  ) {}
}

并在其HTML中:

<div class="playlist" 
  *ngFor="let playlist of playlists$ | async" 
  (click)="playerService.play(playlist)">

</div>

正如您所看到的,代码和简化,您可以清楚地看到每个功能的作用,并且一切都可以重复使用而不会让您头疼。


1
投票

我个人更喜欢遵循聪明/愚蠢的组件想法。

主要组件,音乐播放器组件可以是智能组件,它处理所有逻辑并根据需要使用服务。智能组件还使用哑组件进行显示和事件。

music-player可以有

  1. 获取播放列表列表的播放列表服务。它还可以具有提供播放列表id的歌曲列表的方法。
  2. 播放列表组件,显示以@Input格式提供的播放列表列表,并显示其信息并封装样式。每当选择播放列表时,它还实现选择逻辑。
  3. 显示歌曲列表的歌曲组件将歌曲列表提供为@Input,当选择歌曲时,它也会发出事件。
  4. 选择播放列表时启用的播放按钮。

音乐player.html

    <playlist [items]="playlists" (playListSelected)="loadSongs($event)"></playlist>
    <songs [items]="songs" (songSelected)="playSong($event)"></songs>
    <button type="button" (click)="playSongs()">Play</button>

在这里,音乐播放器是使用不同服务来使用服务获取/设置数据的智能组件。它还使用不同的哑组件来显示内容并捕获用户操作。

为了回答您的原始设计问题,@Input / @Output在Smart / Dumb组件之间的通信中表现良好。服务在智能组件和外部环境之间的通信中表现良好。

根据我的经验,使用这种方法测试也很容易。

不要把它作为最终设计,我只是想介绍智能/哑组件的概念。你可以搜索很多文章。

© www.soinside.com 2019 - 2024. All rights reserved.