我几个星期以来一直在研究角度,我想知道如何在没有太多依赖性和重复性的情况下编写干净的组件。
我不得不说我的问题与已经存在的大型架构有关。
假设我需要编写一个显示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
我想出了两种做法:
我认为这有很多丑陋的代码,主要是因为@ output.emit和@input的实际更改之间的延迟。这个想法根本不是一个好主意,但我想到了。
我喜欢这个实现,因为这个服务并没有很好地耦合到我的BasicComponent,我可以重用我的组件并提供另一个也实现这个接口的服务,它会工作。即使我需要做一些逻辑,我也可以保留所有应该存在的东西,PlayListProvider服务可以检索播放列表。唯一的事情是,我不知道它是否像Dangerous这样做,如果它可以破坏我的Angular项目,如果它反对角度最佳实践。
或许还有另一种方式? (保持事物分离,我的组件可重复使用)
你怎么看 ?你会怎么做?
编辑1:
在我原来的帖子版本中,我没有谈到我的要求:实际上我实际上在现有代码中有这种实现。 “父组件”已经存在,并且不存储任何播放列表,但是,他具有可用于按名称检索播放列表的服务。根据业务需求,我不能在我的组件中直接使用此服务。我必须通过这个父组件,我可以修改父级但不是太多,不是结构或行为。因此,我的组件需要作为“中间件”工作,提供选择,并向其他服务或组件请求数据并将其传递给播放器。更重要的是,这个输入系统需要是可重用的,我应该能够接受该组件,并且可以说,将它放到一个新项目中,并且他需要在这里工作,而不需要在这个组件内进行任何修改。这是一个很大的约束,但这是我无法打破的约束。
好的做法是数据管理进入服务,模板管理进入组件/指令/管道......
第二个好的做法是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>
正如您所看到的,代码和简化,您可以清楚地看到每个功能的作用,并且一切都可以重复使用而不会让您头疼。
我个人更喜欢遵循聪明/愚蠢的组件想法。
主要组件,音乐播放器组件可以是智能组件,它处理所有逻辑并根据需要使用服务。智能组件还使用哑组件进行显示和事件。
music-player
可以有
音乐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组件之间的通信中表现良好。服务在智能组件和外部环境之间的通信中表现良好。
根据我的经验,使用这种方法测试也很容易。
不要把它作为最终设计,我只是想介绍智能/哑组件的概念。你可以搜索很多文章。