Как вы используете вычисленные / рассчитанные свойства в Angular? - PullRequest
0 голосов
/ 01 июля 2019

Меня мучает вопрос: где мне найти свои рассчитанные свойства в угловом проекте?

Например: У меня есть модель, сервис для получения модели и компонент для показа модели.

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. Если вы гуглите «рассчитанные свойства углов», вы, скорее всего, найдете примеры с вычисленными свойствами в самом компоненте.

@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

Ответы [ 2 ]

0 голосов
/ 02 июля 2019

Базовый раствор

Так что для варианта 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 дополнительные поля, поэтому вы можете захотеть добавить еще немного логики, например:

фильтрованной 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));
  }

В качестве альтернативы, вы можете создать свои собственные декораторы для описания полей. Я не буду приводить здесь пример, так как это было бы весьма излишним.

0 голосов
/ 01 июля 2019

Я добавлю шаблон, который я считаю очень полезным. Лично мне нравится разделять вопросы - т.е. сохранять все 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);
  }
}


...