如何将可观察值传递给@Input() Angular 4

Posted

技术标签:

【中文标题】如何将可观察值传递给@Input() Angular 4【英文标题】:How to pass observable value to @Input() Angular 4 【发布时间】:2018-09-14 16:25:47 【问题描述】:

我是 Angular 新手,我有以下情况,即我有一个服务 getAnswers():Observable<AnswerBase<any>[]> 和两个相互关联的组件。

在线报价 动态形式

online-quote 组件在其ngOnInit() 方法中调用服务getAnswers():Observable<AnswerBase<any>[]>,并将其结果传递给组件dynamic-form

为了说明情况,这是我的两个组件的代码:

在线报价.component.html

 <div>
    <app-dynamic-form [answers]="(answers$ | async)"></app-dynamic-form>
</div>

在线报价.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");
  


动态表单.component.html:

<div *ngFor="let answer of answers">
 <app-question *ngIf="actualPage===1" [answer]="answer"></app-question>
</div>

动态表单.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&lt;AnswerBase&lt;any&gt;[]&gt; 的结果信息是可观察的,那么将信息从 online-quote 传递到 dynamic-form 的正确方法是什么.

我已经尝试了很多方法,但它不起作用。我希望有人能帮我解决这个问题。非常感谢!

【问题讨论】:

【参考方案1】:

假设DynamicFormService.getAnswers('CAR00PR')异步(可能是),使用async Pipe传递异步结果是正确的方法,但是当@987654323时你不能指望现在得到异步结果@ 被创建(在 ngOnInit)因为 异步。运行以下代码行时,结果尚未准备好。

this.form = this.qcs.toFormGroup(this.answers);

有几种方法可以解决您的问题。

1。在ngOnChanges lifehook 处收听@Input() answers 的valueChange。

ngOnChanges(changes) 
  if (changes.answers) 
    // deal with asynchronous Observable result
    this.form = this.qcs.toFormGroup(changes.answers.currentValue);
  

2。将 Observable 直接传递给 DynamicFormComponent 并订阅它以收听它的结果。

在线报价.component.html:

<app-dynamic-form [answers]="answers$"></app-dynamic-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);
    )

【讨论】:

我真的很喜欢你介绍了两种处理这种情况的可行方法。值得一提的是,虽然不是基于示例的问题,但“订阅”是在组件代码中处理的,没有取消订阅机制。同样,这里不是问题(除了不是问题的重点),我们知道原始可观察源来自 http 调用,该调用在调用后完成。对于其他 observable,这很快就会成为有问题的内存泄漏。【参考方案2】:

我有一个与 OP 几乎相同的用例,建议的解决方案也对我有用。

为了简单起见,我想出了一个适用于我的案例的不同解决方案,并且看起来更简单一些。我之前在模板中应用了异步管道作为 *ngIf 结构指令的一部分,并使用变量能够将已经评估的值传递给子组件。

<div *ngIf="answers$ | async as answers">
    <app-dynamic-form [answers]="answers"></app-dynamic-form>
</div>

【讨论】:

看起来不错,但是如何在 app-dynamic-form 中处理它? 在 app-dynamic-form 内部,它不再是 Observable,而是一个 AnswerBase[]。我相信 OP 不必更改其中的任何代码。 如果他只是处理原始数据对象,他可能不需要做任何进一步的事情,这是一个可行的解决方案。但是,他正在获取答案数据并将其转换为表单组,因此接受的答案中提到的 ngOnChanges 将很好地补充此解决方案。 作为一个魅力,你可以在这里看到一个实现:angular-ivy-z9shje.stackblitz.io【参考方案3】:

我遇到了同样的问题,我创建了一个小型库,提供 ObservableInput 装饰器来帮助解决这个问题:https://www.npmjs.com/package/ngx-observable-input。该案例的示例代码为:

在线报价.component.html:

<app-dynamic-form [answers]="answers$ | async"></app-dynamic-form>

动态表单.component.ts:

@Component(
  ...
)
export class DynamicFormComponent implements OnInit 
  @ObservableInput() Input("answers") answers$: Observable<string[]>;

  ...

【讨论】:

【参考方案4】:

我的方法和建议是使用 BehaviorSubject 进行以下操作 原因 这是来自文档:

Subjects 的变体之一是 BehaviorSubject,它有一个 “当前值”的概念。它存储发出的最新值 它的消费者,并且每当有新的观察者订阅时,它将 立即从 BehaviorSubject 接收“当前值”。

我认为 OP 声明的子组件总是需要发出的最后一个值,因此我觉得 BehaviorSubject 会更适合这个。

在线报价.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>

// 这会发出可观察的主题

现在您可以订阅子组件中的值。订阅答案的方式没有变化 动态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);
    )

【讨论】:

【参考方案5】:

将可观察对象传递给输入是要避免的,原因是:当检测到新输入时,您如何处理对前一个可观察对象的订阅?让我们澄清一下,可以订阅 observable,一旦您订阅,您有责任完成 observable 或取消订阅。一般来说,这甚至被认为是一种反模式,将 observables 传递给不是运算符的函数,因为您正在执行命令式编码,其中 observables 应该以声明方式使用,将一个传递给组件也不例外。

如果你真的想这样做,你需要非常小心,不要忘记取消订阅 observable。为此,您要么必须确保输入永远不会更改,要么专门完成对覆盖输入的任何先前订阅(嗯......就在它被覆盖之前)

如果您不这样做,您最终可能会出现泄漏,并且很难找到错误。因此,我会推荐以下 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>

【讨论】:

以上是关于如何将可观察值传递给@Input() Angular 4的主要内容,如果未能解决你的问题,请参考以下文章

将可写 StringBuilder 数组从 C# 传递给 C++

如何将可选视图传递给 SwiftUI 中的另一个视图?

如何将可分配数组传递给 Fortran 中的子例程

reactjs中如何将可选元素作为prop传递给组件

如何将可选 ID 从 Route 传递给 Laravel 控制器?

Python:当列表仅在运行时确定时,如何将可迭代列表传递给 zip?