Angular — архитектура компонентов приложения

В настоящее время я экспериментирую с дизайном компонента приложения в Angular 7.*. Не много поработав с ним, мне трудно разработать чистую архитектуру компонентов.

Этот компонент, назовем его MainComponent, получает JSON ввод, такой как

{
   "name": "name",
   "description": "description",
   "attributes": [ // Rectangle in the image
      {
         "attr1": "value", // Editable
         "attr2": "value", // Editable
         "attr3": "value"  // Editable
      },
      {
         "attr1": "value", // Editable
         "attr2": "value", // Editable
         "attr3": "value"  // Editable
      },
      ...
   ]
}

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

Каждый элемент attributes становится прямоугольником внутри основного.
И каждое поле attribute редактируется, поэтому FormGroup необходимо создать.

Angular — архитектура компонентов приложения

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

Я думал о двух решениях.

  1. Имейте FormGroup на основном уровне с несколькими под-FormGroup-ами, по одному на каждый подкомпонент. Каждый суб-FormGroup вводится внутри суб-Component. При сохранении я просто смотрю на основную группу форм, она уже содержит все измененные значения.

  2. Каждому подкомпоненту принадлежит отдельный FormGroup. При сохранении основной компонент спрашивает (то есть вызов метода) каждого подкомпонента для извлечения измененных значений.

Какое решение больше всего подходит для Angular? Что бы ты сделал?

Редактировать: я спрашиваю, как лучше распределить ввод JSON по нескольким подкомпонентам, позаботившись о получении отредактированных значений при сохранении, которое находится на верхнем/основном уровне.

Вы можете вызвать общий компонент и создать группу форм со значением входного атрибута? Обновлено: проблема в том, что кнопка сохранения находится на родительском компоненте.

TintinSansYeux 11.03.2019 11:35

@MsuArven, что вы имеете в виду под компонентом общий? Да "проблема" именно в этом!

LppEdd 11.03.2019 11:36

Я думаю, что лучший подход — использовать formArray Проверьте эту ссылку: angular.io/guide/…

Bunyamin Coskuner 11.03.2019 11:38

@BunyaminCoskuner в основном внутри основного компонента, мне пришлось бы создать FormGroup, который содержит FormArray из FormGroups? Это то, что вы говорите? Таким образом, ответ будет Опция 1 (управлять состоянием и FormGroup только на основном уровне)

LppEdd 11.03.2019 11:40

Кроме того, вам нужно сделать так, чтобы ваш компонент Input реализовывал ControlValueAccessor, чтобы вы могли использовать formControlName в этом компоненте.

Bunyamin Coskuner 11.03.2019 11:40

Да, таким образом вашему компоненту main не нужно знать, сколько у него подкомпонентов. Каждый подкомпонент будет обновлять formArray значения formGroup, если вы правильно настроили его.

Bunyamin Coskuner 11.03.2019 11:41

@BunyaminCoskuner каждый подкомпонент будет принимать один FormGroup (который происходит от FormArray), верно?

LppEdd 11.03.2019 11:42

Нет, они не принимают никаких входных данных, им просто нужно реализовать ControlValueAccessor, чтобы вы могли использовать formControlName на них. Им не нужно знать о formGroup

Bunyamin Coskuner 11.03.2019 11:43

@BunyaminCoskuner, поэтому я могу просто использовать (например) formControlName для элемента ввода внутри шаблона подкомпонента? Действительно? мне нужно понять как это работает

LppEdd 11.03.2019 11:49

Я создал стекблиц, скоро выложу

Bunyamin Coskuner 11.03.2019 12:02

@BunyaminCoskuner спасибо!

LppEdd 11.03.2019 12:04

@BunyaminCoskuner, быстрое предупреждение, помните, что каждый квадрат «Входные данные» выше содержит несколько редактируемых значений (attr1, attr2, ecc). Можно ли использовать ControlValueAccessor?

LppEdd 11.03.2019 12:08

Вы можете поместить их в один объект, не так ли?

Bunyamin Coskuner 11.03.2019 12:09

@BunyaminCoskuner на данный момент они содержатся в одном объекте { "attr1": "value", "attr2": "value", ... }

LppEdd 11.03.2019 12:10
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
0
14
59
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

this.lotTwoFormGroup = this.formBuilder.group({
  title: ['', Validators.compose([Validators.required])],
  description: ['', Validators.compose([Validators.required])],
  dutchTitle: [''],
  dutchDescription: [''],
  frenchTitle: [''],
  frenchDescription: ['']
});
Ответ принят как подходящий

Вот рабочий раствор.

Я использовал FormArray. Для получения дополнительной информации прочитайте здесь

Во-первых, давайте создадим компонент ввода и назовем его MyInputComponent

@Component({
  selector: 'my-input',
  template: `
    <div>
      Attr1: <input type = "text" [ngModel] = "value.attr1" 
               (ngModelChange) = "updateModel($event, 'attr1')" />
    </div>
    <div>
      Attr2: <input type = "text" [ngModel] = "value.attr2" 
               (ngModelChange) = "updateModel($event, 'attr2')" />
    </div>
  `
})
export class MyInputComponent {
  value;

  updateModel(value, attrName) {
    this.value[attrName] = value;
  }
}

Это довольно простой компонент. Он содержит два входа (вы можете добавить больше) и привязывает модель к этому входу с помощью ngModel. На данный момент он ничего не раскрывает внешнему миру. При изменении любого из входов он соответственно обновляется value.

Теперь давайте использовать его в нашем main.component

Допустим, у вас есть следующие данные

  attributes = [
    { 'attr1': 'value1', 'attr2': 'value12'},
    { 'attr1': 'value2', 'attr2': 'value22'},
    { 'attr1': 'value3', 'attr2': 'value32'},
  ];

И вы можете использовать этот компонент в своем шаблоне следующим образом.

<div *ngFor = "let attr of attributes; let i = index">
  <my-input></my-input>
  <hr />
</div>

Теперь давайте привяжем formArrayName и formControlName к этому входу.

Для этого мы импортируем ReactiveFormsModule в наш модуль и заставляем MyInput внедрять ControlValueAccessor

my-input.component

@Component({
  selector: 'my-input',
  template: `
    <div>
      Attr1: <input type = "text" [ngModel] = "value.attr1" (ngModelChange) = "updateModel($event, 'attr1')" />
    </div>
    <div>
      Attr2: <input type = "text" [ngModel] = "value.attr2" (ngModelChange) = "updateModel($event, 'attr2')" />
    </div>
  `,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => MyInputComponent),
      multi: true
    }
  ]
})
export class MyInputComponent implements ControlValueAccessor {

  value;

  onChange;
  onTouched;
  disabled = false;

  updateModel(value, attrName) {
    this.value[attrName] = value;
    this.onChange(this.value); // now I call onChange method to update the value within form
  }

  // comes from ControlValueAccessor
  writeValue(newValue): void {
    this.value = newValue;
  }

  // comes from ControlValueAccessor
  registerOnChange(fn: (rating: number) => void): void {
    this.onChange = fn;
  }

  // comes from ControlValueAccessor
  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }

  // comes from ControlValueAccessor
  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

}

И измените шаблон основного компонента на

<div [formGroup] = "myForm">
  <div formArrayName = "array">
    <div *ngFor = "let attr of attributes; let i = index">
      <my-input [formControlName] = "i"></my-input>
      <hr />
    </div>
    <button (click) = "save()">Save</button>
  </div>
</div>

И вам нужно создать formGroup внутри основного компонента следующим образом:

export class AppComponent  {

  myForm = this.fb.group({
    array:  this.fb.array([])
  })

  attributes = [
    { 'attr1': 'value1', 'attr2': 'value12'},
    { 'attr1': 'value2', 'attr2': 'value22'},
    { 'attr1': 'value3', 'attr2': 'value32'},
  ];

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    const arrayFormControl = this.myForm.get('array') as FormArray;
    this.attributes.forEach(attr => 
      arrayFormControl.push(this.fb.control(attr)));
  }

  save() {
    console.info(this.myForm.value);
  }
}

Большое спасибо! Вопрос, внутри MyInputComponent есть поле value, которое не типизировано, потому что в интерфейсе используется any. Будет ли это FormGroup в этом случае? (как вы связались с ним [formControlName] = "i")

LppEdd 11.03.2019 12:34

Хорошо, было бы FormControl. Просто прочитайте arrayFormControl.push(this.fb.control(attr)));

LppEdd 11.03.2019 12:37

Это не FormControl. Это любое значение, которое вы связываете. Самое приятное в этом то, что MyInputComponent не знает, как вы его используете. Вы также можете использовать ngModel вместо [formControlName] = "i".

Bunyamin Coskuner 11.03.2019 12:39

Хорошо! Как насчет проверки одного атрибута внутри MyInputComponent? Не будет ли это проблемой?

LppEdd 11.03.2019 12:42

С этой настройкой вам нужно создать пользовательскую проверку.

Bunyamin Coskuner 11.03.2019 12:43

Или я мог бы использовать внутренний FormGroup (не через Input), похожий на stackoverflow.com/questions/52622135/…

LppEdd 11.03.2019 12:44

Да, я так думаю

Bunyamin Coskuner 11.03.2019 12:46

Другие вопросы по теме