Как передать наблюдаемое значение в @Input() Angular 4
Я новичок в угловой, и у меня есть следующая ситуация, у меня есть сервис getAnswers():Observable<AnswerBase<any>[]>
и два компонента, которые связаны друг с другом.
- онлайн-цитаты
- динамическая форма
компонентонлайн-котировки вызывает сервис getAnswers():Observable<AnswerBase<any>[]>
в его ngOnInit()
Метод и результат этого передаются компоненту dynamic-form.
Чтобы проиллюстрировать ситуацию, это код моих двух компонентов:
онлайн-quote.component.html:
<div>
<app-dynamic-form [answers]="(answers$ | async)"></app-dynamic-form>
</div>
онлайн-quote.component.ts:
@Component({
selector: 'app-online-quote',
templateUrl: './online-quote.component.html',
styleUrls: ['./online-quote.component.css'],
providers: [DynamicFormService]
})
export class OnlineQuoteComponent implements OnInit {
public answers$: Observable<any[]>;
constructor(private service: DynamicFormService) {
}
ngOnInit() {
this.answers$=this.service.getAnswers("CAR00PR");
}
}
динамико-form.component.html:
<div *ngFor="let answer of answers">
<app-question *ngIf="actualPage===1" [answer]="answer"></app-question>
</div>
динамические-form.component.ts:
@Component({
selector: 'app-dynamic-form',
templateUrl: './dynamic-form.component.html',
styleUrls: ['./dynamic-form.component.css'],
providers: [ AnswerControlService ]
})
export class DynamicFormComponent implements OnInit {
@Input() answers: AnswerBase<any>[];
constructor(private qcs: AnswerControlService, private service: DynamicFormService) { }
ngOnInit() {
this.form = this.qcs.toFormGroup(this.answers);
}
Мой вопрос заключается в том, как правильно передать информацию из онлайн-цитаты в динамическую форму, если информация о результате службы getAnswers():Observable<AnswerBase<any>[]>
это наблюдаемое.
Я пробовал это разными способами, но это не работает. Я хотел бы, чтобы кто-то помог мне с этим. Большое спасибо!
6 ответов
Предполагать DynamicFormService.getAnswers('CAR00PR')
является асинхронным(вероятно, это так), используя async Pipe
передать асинхронный результат - правильный путь, но вы не можете ожидать получить асинхронный результат прямо сейчас, когда DynamicFormComponent
создается (в ngOnInit) из-за асинхронного. Результат еще не готов при запуске строки кода ниже.
this.form = this.qcs.toFormGroup(this.answers);
Есть несколько способов, которые могут решить вашу проблему.
1. слушать значение изменения @Input() answers
в ngOnChanges
lifehook.
ngOnChanges(changes) {
if (changes.answers) {
// deal with asynchronous Observable result
this.form = this.qcs.toFormGroup(changes.answers.currentValue);
}
}
2. передать Observable непосредственно в DynamicFormComponent
и подписаться на него, чтобы послушать его результат.
онлайн-quote.component.html:
<app-dynamic-form [answers]="answers$)"></app-dynamic-form>
динамические-form.component.ts:
@Component({
...
})
export class DynamicFormComponent implements OnInit {
@Input() answers: Observable<AnswerBase[]>;
ngOnInit() {
this.answers.subscribe(val => {
// deal with asynchronous Observable result
this.form = this.qcs.toFormGroup(this.answers);
})
}
I had an almost identical use-case as OP and the proposed solution worked for me too.
For simplicity sake, I figured out a different solution that worked in my case and seems a little simpler. I applied the async pipe earlier on in the template as part of the *ngIf structural directive and used variables to be able to pass through the already evaluated value through to the child component.
<div *ngIf="answers$ | async as answers">
<app-dynamic-form [answers]="answers"></app-dynamic-form>
</div>
Мое мнение и предложение - использовать BehaviorSubject по следующей причине. Это из документа:
Одним из вариантов Subjects является BehaviorSubject, который имеет понятие «текущее значение». Он хранит последнее значение, отправленное своим потребителям, и всякий раз, когда новый наблюдатель подписывается, он немедленно получает «текущее значение» от BehaviorSubject.
Я предполагаю, что дочерний компонент, объявленный OP, всегда будет нуждаться в последнем испускаемом значении, и поэтому я чувствую, что BehaviorSubject больше подходит для этого.
Online-quote.component
@Component({
selector: 'app-online-quote',
templateUrl: './online-quote.component.html',
styleUrls: ['./online-quote.component.css'],
providers: [DynamicFormService]
})
export class OnlineQuoteComponent implements OnInit {
public answers$: BehaviorSubject<any>;
constructor(private service: DynamicFormService) {
this.answers$ = new BehaviorSubject<any>(null);
}
ngOnInit() {
this.service.getAnswers("CAR00PR").subscribe(data => {
this.answer$.next(data); // this makes sure that last stored value is always emitted;
});
}
}
В html-представлении
<app-dynamic-form [answers]="answer$.asObservable()"></app-dynamic-form>
// это излучает объект как наблюдаемый
Теперь вы можете подписаться на значения в дочернем компоненте. Нет никаких изменений в том, как должен быть подписан ответ dynamic-form.component.ts
@Component({
...
})
export class DynamicFormComponent implements OnInit {
@Input() answers: Observable<any>;
ngOnInit() {
this.answers.subscribe(val => {
// deal with asynchronous Observable result
this.form = this.qcs.toFormGroup(this.answers);
})
}
Следует избегать передачи наблюдаемого на вход, причина в том, что вы делаете с подписками на предыдущий наблюдаемый при обнаружении нового входа? давайте уточним, на наблюдаемое можно подписаться, если у вас есть подписка, вы обязаны либо завершить наблюдаемое, либо отказаться от подписки. Вообще говоря, это даже считается анти-шаблоном для передачи наблюдаемых в функции, которые не являются операторами, потому что вы выполняете императивное кодирование, где предполагается, что наблюдаемые используются декларативным способом, передача одного в компонент не является исключением.
Если вы действительно хотите это сделать, вам нужно быть очень осторожным, чтобы не забыть отказаться от подписки на наблюдаемый объект после того, как вы это сделали. Для этого вам нужно либо убедиться, что ввод никогда не изменяется, либо специально выполнить любую предыдущую подписку на перезаписанный ввод (ну… прямо перед тем, как он будет перезаписан).
Если вы этого не сделаете, вы можете столкнуться с утечками, а найти ошибки довольно сложно. Поэтому я бы рекомендовал 2 следующих варианта:
- Либо используйте службу общего хранилища, либо «предоставьте» ее для конкретного компонента, посмотрите https://datorama.github.io/akita/ , чтобы узнать, как это сделать. В этом случае вы вообще не используете входы, вы будете подписываться только на запросы внедренного сервиса хранилища. Это чистое решение, когда нескольким компонентам необходимо асинхронно записывать и читать общий источник данных.
Или же
- Создайте наблюдаемое (BehaviourSubject, ReplaySubject или Subject) для компонента, который будет испускать при изменении ввода.
@Component({
selector: 'myComponent',
...
})
export class DynamicFormComponent implements OnInit {
// The Subject which will emit the input
public myInput$ = new ReplaySubject();
// The accessor which will "next" the value to the Subject each time the myInput value changes.
@Input()
set myInput(value){
this.myInput$.next(value);
}
}
И, конечно же, чтобы изменить ввод, вы будете использовать асинхронный канал.
<myComponent [myInput]="anObservable | async"></myComponent>
У меня была такая же проблема, и я создал небольшую библиотеку, которая предоставляет ObservableInput
декоратор, который поможет в этом: https://www.npmjs.com/package/ngx-observable-input. Пример кода для этого случая:
online-quote.component.html:
<app-dynamic-form [answers]="answers$ | async"></app-dynamic-form>
dynamic-form.component.ts:
@Component({
...
})
export class DynamicFormComponent implements OnInit {
@ObservableInput() Input("answers") answers$: Observable<string[]>;
...
}
Изменить online-quote.component.ts: к следующему
@Component({
selector: 'app-online-quote',
templateUrl: './online-quote.component.html',
styleUrls: ['./online-quote.component.css'],
providers: [DynamicFormService]
})
export class OnlineQuoteComponent implements OnInit {
public answers$: AnswerBase<any>[];
constructor(private service: DynamicFormService) {
}
ngOnInit() {
this.service.getAnswers("CAR00PR").subscribe(success=>{this.answers$=success;});
}
}