4

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

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

人.模型.ts:

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

人.service.ts:

export class PersonService {

  // inject http: HttpClient

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

人.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 'calculated properties angular',您很可能会在组件本身中找到具有计算属性的示例。

@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

4

3 回答 3

0

基础溶液

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

人.模型.ts

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

  constructor(obj) {
    Object.assign(this, obj);
  }

  get fullName(): string {
    return this.firstName + this.lastName;
  }
}

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

个人服务.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 是否有一些额外的字段,那么您可能需要添加更多逻辑,例如:

过滤对象.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

人.模型.ts

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

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

于 2019-07-02T20:53:44.613 回答
0

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

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

如果您对模型的类感到满意,那么它可以挽救生命,并且可以处理类的深度嵌套以及许多其他重要的情况。

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

于 2019-11-13T13:01:31.320 回答
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);
  }
}


于 2019-07-01T19:20:44.827 回答