Persisting and accessing values globally in multiple components in Angular 2

允我心安 提交于 2019-12-17 23:37:31

问题


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


回答1:


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.




回答2:


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().




回答3:


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 {}



回答4:


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.




回答5:


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



来源:https://stackoverflow.com/questions/34572005/persisting-and-accessing-values-globally-in-multiple-components-in-angular-2

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!