【问题标题】:How do you use computed/calculated properties in Angular?你如何在 Angular 中使用计算/计算的属性?
【发布时间】:2019-11-12 09:46:43
【问题描述】:

我被这个问题折磨着:我应该在 Angular 项目中的哪里找到我的计算属性?

例如: 我有模型、获取模型的服务和展示模型的组件。

person.model.ts:

export class Person {
  firstName: string;
  lastName: string;
}

person.service.ts:

export class PersonService {

  // inject http: HttpClient

  get(id) {
   return this.http.get<Person>(`api-endpoint/person/${id}`);
  }
}

person.component.ts

@Component({
  selector: 'app',
  template: `
   <div>
    <input [value]='person.firstName'>
    <input [value]='person.lastName'>
   </div>
`,
  providers:  [ PersonService ]
})
export class AppComponent {
  person: Person;

  // inject personService: PersonService

  ngOnInit() {
   personService.get(1).subscribe(p => this.person = p);
  }
}

现在我需要fullName 将其显示到我的输入字段下的模板中。

选项 1。如果您在 google 上搜索“角度计算属性”,您很可能会在组件本身中找到具有计算属性的示例。

@Component({
  selector: 'app',
  template: `
   <div>
    <input [value]='person.firstName'>
    <input [value]='person.lastName'>
    <span>{{ fullName }}</span>
   </div>
`,
  providers:  [ PersonService ]
})
export class AppComponent {
  person: Person;
  get fullName() {
    return `${this.person.firstName} ${this.person.lastName}`
  }
  // inject personService: PersonService

  ngOnInit() {
   personService.get(1).subscribe(p => this.person = p);
  }
}

但是,这是否是此类代码的正确位置? 如果我们想在其他组件、服务等中重用这个属性怎么办?

选项2。我个人想扩展person.model.ts

export class Person {
  firstName: string;
  lastName: string;
  get fullName(): string {
    return `${this.firstName} ${this.lastName}`
  }
}
@Component({
  selector: 'app',
  template: `
   <div>
    <input [value]='person.firstName'>
    <input [value]='person.lastName'>
    <span>{{ person.fullName }}</span>
   </div>
`,
  providers:  [ PersonService ]
})
export class AppComponent {
  person: Person;
  // inject personService: PersonService

  ngOnInit() {
   personService.get(1).subscribe(p => this.person = p);
  }
}

但是我们面临另一个问题。我们的 personService 返回的对象根本没有这个 getter。

那我该怎么办?我是否需要创建person.model.ts 的新实例,然后将我们的响应分配给它,或者我可能需要另一个模型,比如person.view-model.ts

感谢您的宝贵时间:D

【问题讨论】:

  • 我会选择选项 2,我会将 personService 理解为实体存储库,不确定您所说的“我们的 personService 完全返回没有这个 getter 的对象”是什么意思。能详细点吗?
  • @Xesenix 看看我的小例子stackblitz.com/edit/angular-cwgqhu。问题也在这里解释:stackoverflow.com/questions/49516876/…
  • 是的,您没有创建该模型的实际对象,您只是告诉 typescript 假设它应该是您需要使用服务器返回的数据初始化 Person 模型的那种返回值,因此它需要拥有可以将服务器响应转换为其字段的构造函数
  • @Xesenix 好的。那么你怎么能选择选项2呢? (你在第一条评论中写道)
  • 我已经添加了草稿,您如何处理它,但它可能需要一些边缘案例修改,并且实际上并没有运行代码,因此可能需要一些修复

标签: angular binding calculated-field computed-properties computed-field


【解决方案1】:

我将添加一个我觉得非常有用的模式。我个人喜欢保持关注点分离 - 即将所有与 Person 相关的计算属性保留在 PersonService 中,并直接从服务中使用这些属性。特别是如果您在其他地方使用它们。 在您的 PersonService 中,我会添加一个 BehaviourSubject,您可以在其他地方的模板中绑定它:

// person.serice.ts

private readonly _fullName: BehaviorSubject<string> = new BehaviorSubject('');
public readonly fullName$ = this._fullName.asObservable();

public get fullName(): string {
    return this._fullName.getValue();
}

public set fullName(p: Person): string {
    this._fullName.next(p.firstName + p.lastName);
}

// inject http: HttpClient

get(id) {
   return this.http.get<Person>(`api-endpoint/person/${id}`)
       .pipe(tap((p: Person) => this.fullName = p);
}

在你的组件中:

// person.component.ts

@Component({
  selector: 'app',
  template: `
   <div>
    <input [value]='person.firstName'>
    <input [value]='person.lastName'>
    <span>{{ fullName$ | async }}</span>
   </div>
`,
  providers:  [ PersonService ]
})
export class AppComponent {
  person: Person;
  fullName$: Observable<string>;

  // inject personService: PersonService

  ngOnInit() {
   this.fullName$ = this.personService.fullName$;
   this.personService.get(1).subscribe(p => this.person = p);
  }
}


【讨论】:

    【解决方案2】:

    基础溶液

    因此,对于选项 2,您需要将来自服务器的数据封装到模型中。这是执行此操作的一种可能方法:

    person.model.ts

    export class Person {
      firstName: string;
      lastName: string;
    
      constructor(obj) {
        Object.assign(this, obj);
      }
    
      get fullName(): string {
        return this.firstName + this.lastName;
      }
    }
    

    当您实现这一点或使用一些额外的保护逻辑(检查扩展解决方案)时,您可以在您的服务中执行此操作:

    person.service.ts

    export class PersonService {
      ...
      get(id): Observable<Person> {
        return this.http.get<Partial<Person>>(`api-endpoint/person/${id}`).pipe(
          map((response: Partial<Person>) => new Person(response)),
        );
      }
    }
    

    Partial&lt;Person&gt; 类型,因为它不会有一些动态属性

    扩展解决方案

    虽然这个基本解决方案容易受到运行时错误的影响。例如,如果初始化对象有一些与类定义冲突的字段,并且它也不关心 obj 是否有一些额外的字段,那么您可能需要添加更多逻辑,例如:

    filtered-object.helper.ts

    import { keys } from 'ts-transformer-keys'; // enables to take out keys from interface
    
    export function filteredObjec<T>(obj) {
      const allowed = keys<T>();
      return Object.keys(obj)
        .filter(key => allowed.includes(key))
        .reduce((obj, key) => {
          obj[key] = raw[key];
          return obj;
        }, {});
    }
    

    请记住,这需要安装ts-transformer-keys

    person.model.ts

    import { filteredObjec } from 'path/to/filtered-object.helper';
    ...
      constructor(obj) {
        Object.assign(this, filteredObjec<Person>(obj));
      }
    

    或者,您可以创建自己的装饰器来描述字段,我不会在这里提供示例,因为这将是非常过分的。

    【讨论】:

      【解决方案3】:

      我已经成功使用这个 npm 模块很长时间了:

      https://www.npmjs.com/package/class-transformer

      如果您对模型的类感到满意,那么它就是救命稻草,它可以处理类的深度嵌套以及许多其他不平凡的情况。

      不幸的是,我们的团队决定为模型使用接口,所以我现在要删除所有使用该模块的代码。

      【讨论】:

        猜你喜欢
        • 2020-12-01
        • 1970-01-01
        • 2019-11-27
        • 2021-03-04
        • 2022-01-14
        • 2017-09-17
        • 2014-01-23
        • 1970-01-01
        • 2018-09-19
        相关资源
        最近更新 更多