角度和异步管道打字稿抱怨 null

问题描述 投票:0回答:5

我有以下容器组件

export class EventsComponent {
  data$: Observable<Data[]> = this.store.select(data);
  loading$: Observable<boolean> = this.store.select(loading);
}

并通过

| async
将可观察量绑定到表示组件:

 <app-presentational 
   [rowData]="data$ | async" 
   [loading]="loading$ | async" 
   ...
export class PresentComponent {
  @Input()
  rowData: Data[];

  @Input()
  loading: boolean;
}

然而,TS编译器总是抱怨异步管道可能返回null。

更新,这就是我得到的确切错误

Type 'boolean | null' is not assignable to type 'boolean'.
  Type 'null' is not assignable to type 'boolean'.ngtsc(2322)

那么我真的必须把我所有的

@Input()
都改成这个吗?

export class PresentComponent {
  @Input()
  rowData: Data[] | null;

  @Input()
  loading: boolean | null;
}
angular ngrx
5个回答
26
投票

正如 Angular 文档所指出的那样这里

上述问题有两种潜在的解决方法:

在模板中,包含非空断言运算符!在最后 可以为 null 的表达式,例如 .

在此示例中,编译器忽略了类型不兼容的情况 可空性,就像在 TypeScript 代码中一样。在异步的情况下 管道,请注意表达式需要用括号括起来,如 在 .

完全禁用 Angular 模板中的严格 null 检查。

启用 strictTemplates 后,仍然可以禁用 类型检查的某些方面。设置选项 strictNullInputTypes 设置为 false 会禁用严格的 null 检查 有角度的模板。该标志适用于所有组成部分的组件 应用程序。

但是,您可以:

,而不是使用非空断言运算符,甚至禁用 Angular 严格检查
  1. 使用 nullish 合并运算符 (
    ??
    ) - 在 Angular 12+ 中可用:

TS

@Component({
  template: ``,
})
export class PresentationalComponent {
  @Input() loading: boolean;
  @Input() rowData: Data[];
}

HTML

<app-presentational
  [loading]="(loading$ | async) ?? false"
  [rowData]="(rowData$ | async) ?? []"
></app-presentational>
  1. 使用
    ngAcceptInputType_*

TS

@Component({
  template: ``,
})
export class PresentationalComponent {
  static ngAcceptInputType_loading?: boolean | null;
  // Note that if you have `@angular/cdk` installed you can use this instead:
  // static ngAcceptInputType_loading: BooleanInput;
  static ngAcceptInputType_rowData?: Data[] | null;

  @Input() loading: boolean;
  @Input() rowData: Data[];
}

HTML

<app-presentational
  [rowData]="rowData$ | async"
  [loading]="loading$ | async"
></app-presentational>
  1. TS 4.3+
    getter
    setter
    的不同类型:

TS

@Component({
  template: ``,
})
export class PresentationalComponent {
  @Input()
  // Note that if you have `@angular/cdk` installed you can use `BooleanInput` instead.
  set loading(loading: boolean | null | undefined) {
    this._loading = loading ?? false;
  }
  get loading(): boolean {
    return this._loading;
  }
  private _loading: Data[];

  @Input()
  set rowData(rowData: Data[] | null | undefined) {
    this.rowData = rowData ?? [];
  }
  get rowData(): Data[] {
    return this._rowData;
  }
  private _rowData: Data[] = [];
}

请注意,现在您应该更喜欢使用选项 3 而不是选项 2,因为

input setter coercion fields
已被弃用


2
投票

修改您的代码片段如下。应该修复空分配错误。

 <app-presentational 
   [rowData]="(data$ | async)!" 
   [loading]="(loading$ | async)!" 
   ...

0
投票

仅从您提供的信息中我无法判断,但当 Observable 未提供值时,组件看起来只需要 null 处理

<app-presentational 
  [rowData]="data$ | async" 
  [loading]="(loading$ | async) || false"
/>

0
投票

Angular 文档建议使用非空断言运算符

!
。一个例子是:

<user-detail [user]="(user$ | async)!"></user-detail>

使用非空断言运算符表示您确信某个值将始终存在,这会覆盖 TypeScript 的空检查行为。

但是,这种方法并不总是合适的。当您可以从一开始就保证一个值时,例如使用

of()
定义可观察量时,最好使用它。

另一种方法可以将组件输入转换为可观察值,或者接受

null
值,但现在我们正在使“哑”组件变得更聪明。

以下是我坚持的一些一般准则和示例:

1.非空断言

如果您确定从一开始就有一个值,请使用非空断言运算符:

<user-detail [user]="(user$ | async)!"></user-detail>

当您知道 user$ 总是会发出一个值时,这很合适,也许因为它是立即用一个值初始化的。

2.如果/否则

如果您正在等待一个值,请实现加载状态并在该值可用时渲染组件:

<ng-container *ngIf="user$ | async as user; else loading">
  <user-detail [user]="user"></user-detail>
</ng-container>
<ng-template #loading>
  <p>Loading...</p>
</ng-template>

在这种情况下,ngIf 检查 user$ 是否已发出值。如果没有,则会显示加载模板。

3.回退值

如果即使在等待值时也需要渲染组件,请提供后备:

<user-detail [user]="(user$ | async) || defaultUser"></user-detail>

这里,defaultUser 用作后备值,直到 user$ 发出值。当您想要确保组件始终呈现时,即使实际数据尚不可用,此方法也很有用。

通过遵循这些规则并使用适当的示例,您可以确保您的应用程序更有效地处理值和加载状态。


-1
投票

您的 Store State 初始值未定义,这就是为什么您得到 null 或者您没有声明 Store State 对象

  1. 尝试设置初始值:
export class PresentComponent {
  @Input()
  rowData: Data[] = [];

  @Input()
  loading = false;

对于 .select,您还可以选择它与字符串一起使用的状态的一部分: 尝试将它们转换为字符串

export class EventsComponent {
  data$: Observable<Data[]> = this.store.select('data');
  loading$: Observable<boolean> = this.store.select('loading');

当进入处于状态的对象内部时,您也可以使用倍数。

this.store.select('name1', 'name2', ...)
  1. 但它仍然可能为空,因为你的初始状态。 您可以尝试不使用 |异步,所以这永远不会为空
export class EventsComponent implements OnInit {
  data: Data[] = [];
  loading = false;

  constructor(private store: Store<{ data: Data[], loading: boolean}>) {
  }

  ngOnInit(): void {
    // you can do one by one
    this.store.select('data')
      .subscribe(next => this.data = next);

    this.store.select('loading')
      .subscribe(next => this.loading = next);

    // or do

    // all together
    this.store.subscribe(next => {
      this.data = next.data;
      this.loading = next.loading;
    });
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.