Как написать повторно используемый компонент, когда происходит сложная зависимость - PullRequest
0 голосов
/ 02 апреля 2019

Я работаю над angular уже несколько недель, и мне было интересно, как я могу написать чистый компонент без слишком большой зависимости и большого повторного использования.

Я должен сказать, что моя проблема связана с большой архитектуройгде вещи уже существуют.

Допустим, мне нужно написать компонент, который отображает выбор с помощью 2 переключателей: либо вы можете выбрать «Плейлист 1» или «Плейлист 2».Этот компонент имеет службу с именем «PlayerService», которая имеет метод «Play», который принимает массив композиций.Идея состоит в том, чтобы выбрать «Playlist 1» или «Playlist 2» и, нажав кнопку, вызвать PlayerService с правильным Playlist.

Вот базовая реализация: этот код только для демонстрации.

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

Я придумал 2 способа сделать это:

  • @ Input / @Output:
    • Получение списка воспроизведения из родительского компонента с помощью параметра @Input.
    • Затем при щелчке пользователяPlayButton, BasicComponent "испускает свой @Ouput" с именем "OnPlay" с именем выбранного списка воспроизведения в качестве аргумента
    • Родитель реагирует на этот @Output и получает хороший список воспроизведения и устанавливает его в качестве входных данных для BasicComponent
    • BasicComponent реализует логику, которая обнаруживает изменение на входе, и если оно изменяется, потому что мы генерировали выход, то он может вызвать метод воспроизведения в службе с этим списком воспроизведения.

Я думаю, что есть много уродливого кода, вызывающего это, в основном из-за этой задержки между @ output.emit и фактическим chАнж @input.Эта идея не очень хорошая, но я подумал об этом.

  • @ Ввод службы:
    • Создание интерфейса IPlayListProvider, который содержитМетод GetPlayList (name: string).
    • Создание службы PlayListProvider и Inherit из IPlayListProvider.
    • Создание @Input в моем BasicComponent типа IPlayListProvider.
    • Таким образом, родитель может иметь (из DI в конструкторе) и хранить экземпляр PlayListProvider.
    • Таким образом, родительский объект может передать этот экземпляр в службу в качестве входных данных для BasicComponent.
    • BasicComponent теперь может вызывать метод GetPlayList с SelectedPlayListName в выборе переключателя, когда пользователь нажимает PlayButton.

Мне нравится эта реализация, потому что этот сервис не очень связан с моим BasicComponent, я могу повторно использовать свой компонент и предоставить другой сервис, который также реализует этот интерфейс, и он будет работать.Я могу хранить все, где они должны быть, даже если мне потребуется выполнить некоторую логику, чтобы служба PlayListProvider могла получать список воспроизведения, где бы они ни находились.Единственное, я не знаю, может ли это делать Dangerous, может ли это сломать вещи в моих проектах Angular, если это противоречит лучшей практике Angular.

Или, может быть, есть другой способ? (что держит вещи отдельно, и мой компонент можно использовать повторно)

Что вы думаете? Как бы вы это сделали?

Редактировать 1:

В моей исходной почтовой версии я не говорил о своем требовании: На самом деле у меня есть такая реализация в существующем коде. «Родительский компонент» уже существует и не хранит какой-либо список воспроизведения, но у него есть службы, которые можно использовать для получения списка воспроизведения по имени. По бизнес-требованиям я не могу напрямую использовать эту услугу внутри своих компонентов. Я должен пройти через этот родительский компонент, я могу изменить родительский компонент, но не слишком сильно, это не структура или поведение. Поэтому моему компоненту нужно для работы в качестве «промежуточного программного обеспечения», предоставляющего выбор, и запрашивающего данные для другой службы или компонента и передающего их в проигрыватель. Более того, эта система ввода должна быть многократно используемой, я должен иметь возможность взять этот компонент и, скажем, превратить его в новый проект, и он должен работать здесь без каких-либо изменений внутри этого компонента. Это большое ограничение, но это ограничение, которое я не могу сломать.

Ответы [ 2 ]

2 голосов
/ 02 апреля 2019

Хорошая практика заключается в том, что управление данными переходит в службу, а управление шаблонами - в компонент / директиву / канал ...

Второй хорошей практикой является то, что 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 голос
/ 02 апреля 2019

Лично я предпочитаю следовать идее умного / немого компонента.

Основным компонентом, компонентом музыкального проигрывателя, может быть интеллектуальный компонент, он обрабатывает всю логику и использует службы по мере необходимости. Интеллектуальный компонент также использует немые компоненты для отображения и событий.

music-player может иметь

  1. Playlist-сервис, который получает список плейлистов. Он также может иметь метод для предоставления списка песен при условии идентификатора списка воспроизведения.
  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. Службы хорошо взаимодействуют между интеллектуальным компонентом и внешней средой.

По моему опыту, тестирование также легко с этим подходом.

Не принимайте это за окончательный дизайн, я просто пытаюсь сделать введение в концепцию умных / немых компонентов. Есть много статей, которые вы можете искать.

...