如何在Angular中使用计算/计算属性?

时间:2019-07-01 18:29:46

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

我被一个问题折磨了:我应该在角工程中将计算出的属性放在哪里?

例如: 我有模型,提供服务的模型和提供模型的组件。

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返回的对象根本没有这个吸气剂。

那我该怎么办?我是否需要创建新的person.model.ts实例,然后为其分配响应,还是根本需要另一个模型,例如person.view-model.ts

感谢您的时间:D

3 个答案:

答案 0 :(得分:0)

我将添加一个我认为非常有用的模式。我个人喜欢保持关注的分离-即将所有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);
  }
}


答案 1 :(得分:0)

基本解决方案

因此,对于选项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<Person>类型,因为它没有动态属性

扩展解决方案

尽管此基本解决方案容易受到运行时错误的影响。例如,如果初始化对象的某些字段与类定义冲突,并且它也不在意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));
  }

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

答案 2 :(得分:0)

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

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

如果可以接受模型的类,可以节省生命,可以处理类的深层嵌套,以及许多其他不重要的情况。

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