问题
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