Angular2 наблюдает за изменениями атрибута в службе

Я новичок в Angular2 и пытаюсь выяснить шаблон проектирования для родительских и дочерних компонентов для выполнения действий при изменении атрибута в службе, эта служба доступна глобально (объявлено в app.component.ts в атрибуте providers). Я использую:

  • Угловой 2.2.1
  • rxjs 5.0.0-бета.12
  • машинопись 2.2.1

По сути, служба подключается к удаленному API, и этот API требует аутентификации с помощью токена. Сервис имеет общедоступные функции login() и logout(). Первый выполняет вызов API, а затем устанавливает закрытый атрибут token в строку токена (если действителен логин) или '' (если неверный логин). Все другие общедоступные функции, которые вызывают API, используют этот закрытый токен.

Я предположил, что компонентам, использующим эту службу, не нужно видеть или использовать атрибут токена, потому что они должны быть «тупыми» для бизнес-процессов API и потреблять данные только через эту службу.

Сервис (упрощенный)

import { Injectable } from '@angular/core';
import { Http, Response, Headers, URLSearchParams } from '@angular/http';
import {Observable, Subject} from 'rxjs/Rx';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';

@Injectable()
export class ApiService {
  private url:string = 'my.service.com/';
  private username: string;
  private password: string;
  private token: string;

  constructor (private http: Http) {}

  public getUsername(): string {
    return this.username;
  }

  public setUsername(username: string) {
    this.username = username;
  }

  public getPassword(): string {
    return this.password;
  }

  public setPassword(password: string) {
    this.password = password;
  }

  private setToken(token: string) {
    this.token = token;
  }

  public login() {
    let headers = new Headers({ 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' });
    let urlSearchParams = new URLSearchParams();
    urlSearchParams.append('username', this.username);
    urlSearchParams.append('password', this.password);
    let body = urlSearchParams.toString();
    let url = this.url + 'user/login';
    this.http.post(url, body, {headers: headers})
      .map(res => {this.setToken(typeof res['token'] !== 'undefined' ?  res['token'] : '');});
  }

  public logout() {
    this.setToken('');
  }

  public getData() {
    let headers = new Headers({ 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' });
    let urlSearchParams = new URLSearchParams();
    urlSearchParams.append('token', this.token);
    let body = urlSearchParams.toString();
    let url = this.url + 'data';
    return this.http.get(url, body, {headers: headers})
      .map(res => res.json());
  }

app.component.ts

import { Component, ViewEncapsulation } from '@angular/core';
import { ApiService } from './services/api';

@Component({
  selector: 'app',
  encapsulation: ViewEncapsulation.None,
  styleUrls: ['./app.component.global.scss'],
  templateUrl: './app.component.html',
  providers: [ApiService]
})

export class AppComponent {
  constructor(private apiService: ApiService) {}
}

дочерний компонент

import { Component } from '@angular/core';
import { ApiService } from '../services/api';

@Component({
  selector: 'child-component',
  styleUrls: ['./child-component.component.scss'],
  templateUrl: './child-component.component.html'
})

export class ChildComponent {
  data: any[];
  constructor(private apiService: ApiService) {}
}

Как мне получить компоненты (например, ChildComponent для наблюдения за состоянием закрытого атрибута token в ApiService, а когда это не '', для вызова функций в службе, которым требуется токен (например, getData())? Возможно, я подхожу к этому в неправильно, но мои причины для такого подхода заключаются в том, что есть несколько дочерних компонентов, зависящих от действительного токена для выборки/отправки и отображения данных, и должны получать/отправлять данные только при успешном входе в систему. Я экспериментировал с различные ngOnChanges, Observerable, Subject.emit и т. д., но не смогли заставить эту работу работать.


person vim    schedule 18.12.2016    source источник


Ответы (2)


Дочерние компоненты не должны знать о состоянии службы.

когда дочерний компонент использует getData(), служба может проверить, есть ли у него действительный токен, если нет, он может получить его через вход в систему, а затем выполнить GET.

если вы хотите, чтобы пользователь не загружал компонент, если токен отсутствует, вы можете использовать защиту маршрута.

Возможно, это не то, что вам нужно, но полезно прочитать о охранниках здесь: http://blog.thoughtram.io/angular/2016/07/18/guards-in-angular-2.html

person Johan Blomgren    schedule 18.12.2016
comment
Спасибо за ответ, Йохан, это отличное решение, но не совсем соответствует моим требованиям. Я хотел, чтобы страница всегда была доступна, но некоторые дочерние компоненты на странице должны заполняться только тогда, когда пользователь вошел в систему. Я подробно описываю решение, с которым столкнулся ниже. Но я обновляю ваш ответ как полезный. - person vim; 19.12.2016
comment
Извините, я не могу проголосовать за это, у меня есть представитель ‹ 15. Но это было записано. - person vim; 20.12.2016

Однако благодаря Йохану я хочу, чтобы страница была доступна и чтобы некоторые дочерние компоненты заполнялись данными только тогда, когда пользователь вошел в систему.

Я нашел решение в Делегирование: EventEmitter или Observable в Angular2.

Таким образом, я могу запретить дочерним компонентам делать запрос до тех пор, пока токен не будет изменен (т.е. login() не был успешно вызван):

import { Injectable } from '@angular/core';
import { Http, Response, Headers, URLSearchParams } from '@angular/http';
import {Observable, Subject} from 'rxjs/Rx';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';

@Injectable()
export class ApiService {
  private url:string = 'my.service.com/';
  private username: string;
  private password: string;
  private token: string;

  constructor (private http: Http) {}

  private setToken(token: string) {
    this.tokenValue = token;
    this.token.next(token);
  }

  public login() {
    let headers = new Headers({ 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' });
    let urlSearchParams = new URLSearchParams();
    urlSearchParams.append('username', this.username);
    urlSearchParams.append('password', this.password);
    let body = urlSearchParams.toString();
    let url = this.url + 'user/login';
    this.http.post(url, body, {headers: headers})
      .map(res => {this.setToken(typeof res['token'] !== 'undefined' ?  res['token'] : '');});
  }

  public logout() {
    this.setToken('');
  }

  public getData() {
    let headers = new Headers({ 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' });
    let urlSearchParams = new URLSearchParams();
    urlSearchParams.append('token', this.token);
    let body = urlSearchParams.toString();
    let url = this.url + 'data';
    return this.http.get(url, body, {headers: headers})
  .map(res => res.json());
  }

дочерний компонент

import { Component } from '@angular/core';
import { ApiService } from '../services/api';
import { Subscription } from 'rxjs/Rx';

@Component({
  selector: 'child-component',
  styleUrls: ['./child-component.component.scss'],
  templateUrl: './child-component.component.html'
})

export class ChildComponent {
  tokenSubscription: Subscription;
  data: any[];
  constructor(private apiService: ApiService) {}

 ngOnInit() {
  this.tokenSubscription = this.apiService.changeToken$
  .subscribe(token => { this.apiService.getData().subscribe(res => this.data = res.json()));
 }

 ngOnDestroy() {
  this.tokenSubscription.unsubscribe();
  }
}
person vim    schedule 19.12.2016