Как я могу инициализировать реактивную форму Angular2 с помощью Observable?

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

вот как я это делаю сейчас:

constructor(private store: Store<AppState>, private fb: FormBuilder) {
    this.images = images;
    this.recipe$ = store.select(recipeBuilderSelector);
    this.recipe$.subscribe(recipe => this.recipe = recipe); // console.log() => undefined
    this.recipeForm = fb.group({
      foodName: [this.recipe.name], // also tried with an OR: ( this.recipe.name || '')
      description: [this.recipe.description]
    })
  }

Хранилищу дается начальное значение, которое, как я видел, правильно проходит через мою функцию выбора, но к тому времени, когда моя форма будет создана, я не думаю, что это значение вернулось. Следовательно, this.recipe все еще не определен.

Это неправильный подход, или я могу каким-то образом гарантировать, что наблюдаемое будет возвращено перед созданием формы?


person Nate May    schedule 19.02.2017    source источник


Ответы (2)


Я могу придумать два варианта ...

Вариант 1:

Используйте * ngIf в HTML, который отображает форму что-то вроде

<form *ngIf="this.recipe">...</form>

Вариант 2: используйте async в шаблоне и создайте свою модель, например:

компонент

model: Observable<FormGroup>;    
...
this.model = store.select(recipeBuilderSelector)
    .startWith(someDefaultValue)
    .map((recipe: Recipe) => {
        return fb.group({
            foodName: [recipe.name],
            description: [recipe.description]
        })
    })

шаблон

<app-my-form [model]="(model | async)"></app-my-form>

Вам нужно будет подумать, как обрабатывать обновления магазина и текущей модели.

person shusson    schedule 20.02.2017
comment
Мне не удалось заставить его работать таким образом. startsWith() does not exist on type Observable. эта функция существует только для строк. - person Nate May; 20.02.2017
comment
Извините, метод должен быть startWith. github.com/Reactive-Extensions/ RxJS / blob / master / doc / api / core / - person shusson; 20.02.2017
comment
.startWith() работал на первой итерации, но когда запустился второй (режим разработки), он снова не был определен. Я заставил его работать, удалив startWith() и изменив свой селектор на: return _.cloneDeep(state.recipebuilder) || someDefaultValue; - person Nate May; 20.02.2017

Хотя добавление еще одного уровня может показаться более сложным, гораздо проще иметь дело с наблюдаемыми, разделив один компонент на два: компонент контейнер и компонент презентационный.

Компонент контейнера имеет дело только с наблюдаемыми, а не с презентацией. Данные из любых наблюдаемых передаются компоненту представления через свойства @Input, и используется канал async:

@Component({
  selector: "recipe-container",
  template: `<recipe-component [recipe]="recipe$ | async"></recipe-component>`
})
export class RecipeContainer {

  public recipe$: Observable<any>;

  constructor(private store: Store<AppState>) {
    this.recipe$ = store.select(recipeBuilderSelector);
  }
}

Компонент представления получает простые свойства и не должен иметь дело с наблюдаемыми:

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: "recipe-component",
  template: `...`
})
export class RecipeComponent {

  public recipeForm: FormGroup;

  constructor(private formBuilder: FormBuilder) {
    this.recipeForm = this.formBuilder.group({
      foodName: [""],
      description: [""]
    });
  }

  @Input() set recipe(value: any) {
    this.recipeForm.patchValue({
      foodName: value.name,
      description: value.description
    });
  }
}

Идея использования контейнерных и презентационных компонентов является общей концепцией Redux и объясняется в < em> Компоненты презентации и контейнеры.

person cartant    schedule 20.02.2017
comment
Я не мог заставить этот способ работать на меня. Каким-то образом форма строилась до того, как был вызван Селектор - person Nate May; 20.02.2017
comment
Ага, я понимаю вашу точку зрения. Я должен был создать форму в конструкторе и применить изменения только при изменении @Input. Я обновил ответ. Какой бы способ вы ни выбрали, я бы посоветовал вам рассмотреть возможность разделения вещей на контейнерные и презентационные компоненты, поскольку это действительно облегчает жизнь. - person cartant; 20.02.2017
comment
какая-то критика или ответ Шуссона на 2 вариант? Я смог обойтись без контейнера, используя [formGroup]="recipe$ | async" - person Nate May; 20.02.2017
comment
Вы определенно можете заставить его работать без разделения контейнера / презентации, но я считаю, что разделение выгодно для более крупных приложений. Вам решать, стоит ли это того в вашей ситуации. Это просто то, о чем нужно знать; почти всегда есть несколько способов сделать что-то. Я использую компоненты-контейнеры для всех взаимодействий с хранилищами и службами, оставляя компоненты представления работать с простыми входными данными. Кстати, я упростил ответ, чтобы использовать сеттер @Input вместо OnChanges. - person cartant; 20.02.2017
comment
@cartant Можете ли вы использовать ChangeStrategy.onPush с @Input () в макете презентации? - person rotemx; 18.09.2017
comment
@rotemx Использование OnPush и @Input в презентационном компоненте - это суть ответа, поэтому я не уверен, о чем вы спрашиваете. Если вы спрашиваете, можно ли использовать OnPush и @Input в компоненте container, тогда да, конечно, можете. - person cartant; 18.09.2017
comment
@cartant ~ подход в этом ответе действительно чистый - person P.M; 04.01.2021