2

I am writing a sample app, and I have a user list component:

@Component({
  selector: 'user-list',
  templateUrl: './list.component.html',
  styleUrls: ['./list.component.css'],
})
export class ListComponent implements OnInit {
  users: Array = [];
  private usersService: UsersService;

  constructor(private service: UsersService) {
    this.usersService = service;
  }

  loadUsers() {
    this.usersService.getUsers().subscribe(users => this.users = users);
  }

  ngOnInit() {
    this.loadUsers();
    this.usersService.userEvent.subscribe(user => this.loadUsers());
  }
}

And the service is:

@Injectable()
export class UsersService {
  userEvent: EventEmitter = new EventEmitter();


  constructor(private http: Http) {
  }
  getUsers(): Observable {
    return this.http.get('/rest/users')
      .map(res => res.json());
  }

  create(user: User) {
    this.http.post("/rest/users", user).subscribe(resp => this.userEvent.emit(user));
  }
}

export class User {
  constructor(public username: string,
    public email: string, public password: string
  ) { }
}

And there is a sibling component for user creation:

@Component({
  selector: 'app-form',
  templateUrl: './form.component.html',
  styleUrls: ['./form.component.css']
})
export class FormComponent implements OnInit {
  private usersService: UsersService;
  constructor(private service: UsersService, private router: Router) {
    this.usersService = service;
  }

  ngOnInit() {
  }

  onSubmit(formValue: any) {
    let user = new User(formValue.username, formValue.email, formValue.password);
    this.usersService.create(user);
    this.router.navigate(['users']);
  }

}

This currently does what I want to do, but I wonder if there is a more elegant way to update the user list in the event of a new user being created on the server by a sibling component. It seems weird to have to signal using an event emitter if I am subscribing to the user list, though I also don't know how the http.get could be notified about a new user being created on the server.


  • Just a quick tip: 'private' in the constructor makes the service parameter private variable for the class, no need to assign it to a new one. - Toshkata Tonev

2 답변


1

You can use BehaviorSubject to notify any component that subscribes it. It's a special type of observables. For example in your user service, define a user (you can easily change this into a user list):

import {Observable, BehaviorSubject} from 'rxjs/Rx';  // 
import {User} from "../../models/user";               // Your model

... inside your class:

private _currentUser = new BehaviorSubject<User>(new User);
getUser = this._currentUser.asObservable();
setUser(user: User) { this._currentUser.next(user) }

In a component, you can subscribe getUser subject like this:

this.userService.getUser.subscribe(
user => {
    this.user = user;
});

This way, multiple components can subscribe to this BehaviorSubject, and any trigger in any component/service that uses setUser method can change these subscribed components immediately.

... you successfully added a user in your
... component, now use the trigger:

this.userService.setUser(this.user);


1

This is the exact sort of thing my library RxCache is designed to manage. It takes care of all the observable subscriptions for you, this way you don't have to manage any subscriptions or worry about subscription leaks.

https://github.com/adriandavidbrand/ngx-rxcache

In your component you can expose the observables for the users and the loading flag.

@Component({
  selector: 'user-list',
  templateUrl: './list.component.html',
  styleUrls: ['./list.component.css'],
})
export class ListComponent implements OnInit {
  users$ = this.service.users$;
  loading$ = this.service.loading$;

  constructor(private service: UsersService) {
      service.load();
  }
}

In your HTML use the async pipe to manage all the subscriptions

<div *ngIf="loading$ | async else elseBlock">loading ...</div>
<ng-template #elseBlock>
    <div *ngIf="users$ | async as users">
        <div *ngFor="let user of users">
            Email: {{user.email}}
        </div>
    </div>
</ng-template>

In your service use RxCache by installing "npm install ngx-rxcache --save" and import { RxCacheService } from 'ngx-rxcache';

@Injectable()
export class UsersService {
  constructor(private http: Http, private cache: RxCacheService) { }

  private userCache = this.cache.get<User[]>({
      id: '[User] users',
      construct: () => this.http.get<User[]>('/rest/users'),
      save: user => this.http.post<User[]>("/rest/users", user)
  });

  users$ = this.userCache.value$;
  loading$ = this.userCache.loading$;
  saving$ = this.userCache.saving$;

  create(user: User, saved?: (response: any, value: any) => void) {
      this.userCache.save(user, saved);
  }

  load() {
      this.userCache.load();
  }
}

// Use interfaces over classes
export interface User {
  username: string;
  email: string;
  password: string;
}

And in the sibling component

@Component({
  selector: 'app-form',
  templateUrl: './form.component.html',
  styleUrls: ['./form.component.css']
})
export class FormComponent implements OnInit {
  constructor(private service: UsersService, private router: Router) { }

  saving$ = this.service.saving$;

  ngOnInit() {
  }

  onSubmit(formValue: any) {
    let user = { username: formValue.username, email: formValue.email, password: formValue.password };
    this.usersService.create(user, (response, value) => {
        // Don't navigate until the save has finished.
        this.router.navigate(['users']);
    });
  }

}

And in your html you can show a saving message like we did with loading in the other component.

<div *ngIf="saving$ | async">saving ...</div>

Related

Latest