Persisting and accessing values globally in multip

2019-02-03 08:46发布

I have a settings page where users can save some config variables, such as a Username. The user can also change the username on that page. But when I go to another Component (page) and back, the username isn't saved.

I also want to show the username in the other components. How do I do that? With a global variable?

Structure: - App - app.ts (main) - setting.ts - someOtherComponent.ts

5条回答
beautiful°
2楼-- · 2019-02-03 08:52

What you need is a service to maintain the state of your variables. You would then be able to inject that service into any component to set or get that variable.

Here's an example (/services/my.service.ts):

import {Injectable} from "angular2/core";

@Injectable()
export class MyService {
    private myValue;

    constructor() {}

    setValue(val) {
        this.myValue = val;
    }

    getValue() {
        return this.myValue ;
    }
}

You would probably want to put that service in the providers array of your app's bootstrap function (which might be in your main app component file, or in a separate file depending on how you like to do things).

In your main app file (/app.ts):

import {MyService} from './services/my.service';
bootstrap(App, [MyService, COMMON_DIRECTIVES, ROUTER_DIRECTIVES, ROUTER_PROVIDERS, HTTP_PROVIDERS]); // directives added here are available to all children

You needn't have COMMON_DIRECTIVES and the other all caps items in the array, but those are commonly included just to make it so that you don't have to configure them in each component you write.

Then you would access the service from within a component like this (/components/some-component.ts):

import {MyService} from '../services/my.service';

@Component({
    selector: 'some-component',
    template: `
<div>MyValue: {{val}}</div> 
`
})
export class SomeComponent {
    constructor(private myService:MyService) {
    }

    get val() {
        return this.myService.getValue();
    }
}

You might also want to add to the service so that it saved the value to somewhere (semi) permanent so that it could be accessed the next time the user entered the application.

查看更多
唯我独甜
3楼-- · 2019-02-03 09:04

I have the same problem, most of the component needs loggedInUser information. For eg : username, preferred language, preferred timezone of the user etc.

So for this, once user logs in, we can do the following :

If all the information is in JWT Token or /me RESTful Api, then you can decode the token in one of the service.

eg: user.service.ts

@Injectable()
export class UserService {

  public username = new BehaviorSubject<string>('');
  public preferredLanguage = new BehaviorSubject<string>('');
  public preferredTimezone = new BehaviorSubject<string>('');

  constructor(
    private router: Router,
    private jwtTokenService: JwtTokenService
  ) {
    let token: string = localStorage.getItem('token'); // handled for page hard refresh event
    if (token != null) {
      this.decode(token);
    }
  }

  private decode(token: string) {
    let jwt: any = this.jwtTokenService.decodeToken(token);
    this.username.next(jwt['sub']);
    this.preferredLanguage.next(jwt['preferredLanguage']);
    this.preferredTimezone.next(jwt['preferredTimezone']);
  }

  public setToken(token: any) {
    localStorage.setItem('auth_token', token);
    this.decode(token);
  }

}

which holds three public Behaviour subject, whoever is listening to this variable, will get the value if it is change. Please check the rxjs programming style.

and now wherever this variable are required, just subscribe from that component.

For eg: NavComponent will definitely needs username. hence the code will like this below:

export class NavComponent implements OnInit {

    public username: string;

    constructor(
        private userService: UserService,
        private router: Router) {
    }        

    ngOnInit() {
        this.userService.username.subscribe(data => {
          this.username = data;
        });        
    }
}

Hence, reactive programming gives better solution to handle the dependency variable.

The above also solves the problem if the page is hard refresh, as I am storing the value in localstorage and fetching it in constructor.

Please note : It is assumed that logged in user data is coming from JWT token, we can also the same service(UserHttpService) if the data is coming from Restful Api. Eg: api/me

查看更多
不美不萌又怎样
4楼-- · 2019-02-03 09:15

This might be an overkill in your current situation, but if you are going to grow your application into a larger one, this might save you a lot of troubles down the line. That said, I believe Angular with Redux-like store is a really powerful combination. My suggestion is to use ngrx/store module.

RxJS powered state management for Angular applications, inspired by Redux

Source: ngrx/store GitHub repo

The code below should outline all steps you need to follow to integrate the ngrx/store into your application. For the sake of brevity, I have omitted any existing code you might have and all imports which you will need to add.

Install the Module

npm install --save ngrx/core ngrx/store

Create a Reducer

export const SETTINGS_UPDATE = 'SETTINGS_UPDATE';

const initialState = {
    username: ''
};

export function settingsReducer(state: Object = initialState, action: Action) {
    switch (action.type) {
        case SETTINGS_UPDATE:
            return Object.assign({}, state, action.payload);
        default:
            return state;
    }
};

Import the StoreModule

@NgModule({
    imports: [
        // your other imports
        StoreModule.provideStore({
            settings: settingsReducer
        })
    ]
})
export class AppModule {
    //
}

Create Interface for Settings Reducer

export interface SettingsStore {
    username: string;
}

Create a Store Interface

export interface AppStore {
    settings: SettingsStore;
}

Create Settings Service

@Injectable()
export class SettingsService {

    settings$: Observable<SettingsStore>;

    constructor(private store: Store<AppStore>) {
        this.settings$ = this.store.select('settings');
    }

    public update(payload) {
        this.store.dispatch({ type: SETTINGS_UPDATE, payload });
    }

}

Settings Component Controller

@Component({
    ...
})
export class SettingsComponent implements OnInit {

    settings$: Observable<SettingsStore>;

    constructor(private settingsService: SettingsService) {
        //
    }

    ngOnInit() {
        this.settings$ = this.settingsService.settings$;
    }

    public updateUsername() {
        this.settingsService.update({ username: 'newUsername' });
    }

}

Settings Component Template

<p *ngIf="(settings$ | async)?.username">
    <strong>Username:</strong>
    <span>{{ (settings$ | async)?.username }}</span>
</p>

<button (click)="updateUsername()">Update Username</button>

With the setup outlined above you can then inject the SettingsService into any component and display the value in its template.

The same way you can also update the value of the store from any component by using this.settingsService.update({ ... }); and the change will be reflected on all places which are using that value - be it a component via an async pipe or another service via .subscribe().

查看更多
爷的心禁止访问
5楼-- · 2019-02-03 09:17

Vladimir correctly mentioned about ngrx.

I would use a service with Subject/BehaviourSubject and Observable to set and get the state (values) that I need.

We discussed here Communication between multiple components with a service by using Observable and Subject in Angular 2 how you can share values in multiple components that do not have parent-child or child-parent relationship and without importing external library as ngrx store.

查看更多
手持菜刀,她持情操
6楼-- · 2019-02-03 09:18

You would need a service this so you can inject in wherever you need it.:

@Injectable()
export class GlobalService{
  private value;

  constructor(){

  }

  public setValue(value){
    this.value = value;
  }

  public getValue(){
    return this.value;
  }
}

You could provide this service in all your modules but there is a chance you will get multiple instances. Therefore we will make the service a singleton.

@NgModule({
  providers: [ /* DONT ADD THE SERVICE HERE */ ]
})
class GlobalModule {
  static forRoot() {
    return {
      ngModule: GlobalModule,
      providers: [ GlobalService ]
    }
  }
}

You should call the forRoot method only in your AppModule:

@NgModule({
  imports: [GlobalModule.forRoot()]
})
class AppModule {}
查看更多
登录 后发表回答