Angular, RxJs, 初始化组件的子变量

Angular, RxJs, Initializing a Component's Child Variable

在 UserEditComponent 中初始化 JavaScript 变量 user.person 的最佳方法是什么?我读到不推荐嵌套订阅。我还能如何获得人员字段 firstName 和 lastName?以下是与我的问题相关的所有 Angular 代码。

我已经创建了一个组件、一个服务和 JPA 实体。

{{user.person.firstName}} 抛出错误,因为 user.person 为空。

调用http://localhost:8080/users/ returns:

[{"id":"951bcd7e-a69c-4143-abc7-c2475ee249d2","version":0,"password":null,"username":null,"isActive":false,"createTime":1598588747997,"deactivatedTime":0,"person":{"id":"4340c6fc-8288-434c-a5ea-2497b72034a0","version":0,"firstName":"Amar","middleName":null,"lastName":"Patel","ssn":null}}]

调用 userService.get(id) 或 http://localhost:8080/users/951bcd7e-a69c-4143-abc7-c2475ee249d2 returns:

{
  "password" : null,
  "username" : null,
  "isActive" : false,
  "createTime" : 1598588747997,
  "deactivatedTime" : 0,
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/users/951bcd7e-a69c-4143-abc7-c2475ee249d2"
    },
    "user" : {
      "href" : "http://localhost:8080/users/951bcd7e-a69c-4143-abc7-c2475ee249d2"
    },
    "person" : {
      "href" : "http://localhost:8080/users/951bcd7e-a69c-4143-abc7-c2475ee249d2/person"
    }
  }
}

这是对http://localhost:8080/users/951bcd7e-a69c-4143-abc7-c2475ee249d2/person的调用returns:

{
  "firstName" : "Amar",
  "middleName" : null,
  "lastName" : "Patel",
  "ssn" : null,
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/persons/4340c6fc-8288-434c-a5ea-2497b72034a0"
    },
    "person" : {
      "href" : "http://localhost:8080/persons/4340c6fc-8288-434c-a5ea-2497b72034a0"
    },
    "user" : {
      "href" : "http://localhost:8080/persons/4340c6fc-8288-434c-a5ea-2497b72034a0/user"
    }
  }
}

Angular 组件:

@Component({
  selector: 'app-user-edit',
  templateUrl: './user-edit.component.html',
  styleUrls: ['./user-edit.component.css']
})
export class UserEditComponent implements OnInit, OnDestroy {

  user: any = {};

  sub: Subscription;

  constructor(private route: ActivatedRoute,
              private router: Router,
              private userService: UserService) { 

  }

  ngOnInit(): void {
    this.sub = this.route.params.subscribe(params => {
      const id = params.id;
      if (id) {
        this.userService.get(id).subscribe((user: any) => {
          if(user) {
            this.user = user;
            this.user.href = user._links.self.href;
          } else {
            console.log(`User with id '${id}' not found, returning to list`);
            this.gotoList();
          }
        });
      }
    });
  }

Angular 服务:

@Injectable({
  providedIn: 'root'
})
export class UserService {
  public API = '//localhost:8080';
  public USER_API = this.API + '/users';

  constructor(private http: HttpClient) {
  }

  getAll(): Observable<any> {
    return this.http.get(this.API + '/users');
  }

  get(id: string) {
    return this.http.get(this.USER_API + '/' + id);
  }

  save(user: any): Observable<any> {
    let result: Observable<any>;
    if(user.href) {
      result = this.http.put(user.href, user);
    } else {
      result = this.http.post(this.USER_API, user);
    }

    return result;
  }

  remove(href: string) {
    return this.http.delete(href);
  }
}

JPA 用户对象:

@Entity
@NoArgsConstructor
public class User extends BaseEntity {

    @Basic
    private String password;

    @Basic
    @Column(unique = true, length = 100, columnDefinition = "VARCHAR(100)")
    private String username;

    @OneToOne
    @JoinColumn(name = "person_id")
    private Person person;

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

}

JPA Person 对象:

@Entity
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class Person extends BaseEntity {

    @Basic(optional = false)
    @Column(nullable = false, columnDefinition = "VARCHAR(255)")
    private String firstName;

    @Basic(optional = false)
    @Column(nullable = false, columnDefinition = "VARCHAR(255)")
    private String lastName;

    @OneToOne(mappedBy = "person", cascade = CascadeType.ALL)
    @JsonIgnore
    private User user;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}

对象初始化:

  1. 您基本上可以像这样用虚拟数据初始化您的对象:
user: any = {
    person: {
        firstName: 'john',
        ...
    },
    ...
};
  1. 更好的方法是让您对象未定义 user: any; 并检查模板中值的存在性 {{ user?.person.firstName }} 不会抛出未定义的错误

嵌套订阅: 为避免嵌套订阅,您可以像这样使用 rxjs operator switchMap:

ngOnInit(): void {
    this.sub = this.route.params.pipe(
        switchMap(
            params => {
              const id = params.id;
              if (id) {
                  return this.userService.get(id);
              } else {
                  return of(null);
              }
            }
        ).subscribe((user: any) => {
              if(user) {
                  this.user = user;
                  this.user.href = user._links.self.href;
              } else {
                  console.log(`User with id '${id}' not found, returning to list`);
                  this.gotoList();
              }
        });
}

有关 switchMap 工作的更多信息:https://www.learnrxjs.io/learn-rxjs/operators/transformation/switchmap

必须做出一些关于架构的基本决定。

首先,您应该介绍User和Person的类型定义。为 User 命名 3 个可能的定义:

  1. 一个用户对象总是包含一个人对象:
interface User {
  ...
  person: Person;
  ...
}
  1. 一个用户对象可能包含一个人对象:
interface User {
  ...
  person?: Person;
  ...
}
  1. 用户对象包含人员对象的 ID:
interface User {
  ...
  _links: {
    person: string
  };
  ...
}

User 类型的设计影响了谁负责获取人员数据的问题,即调用 http://localhost:8080/users/{id}/person。如果您选择选项 1,那么它应该是 UserService 的任务,并且 UserEditComponent 永远不会看到没有初始化 Person 的 User 对象。使用选项 3,UserEditComponent 将为用户和人员提供两个单独的属性,组件的任务是显式获取人员。选项2位于中间,两种初始化方式都是合理的。

此外,this.user.href = user._links.self.href;似乎也不是一个组件应该做的事情。服务负责转换从后端接收到的数据。

我希望这个答案朝着正确的方向发展。欢迎在评论中进一步讨论。