One of the first things that trips up AngularJS newcomers is sharing state between controllers. Each controller gets its own $scope so you can't just read from another controller's scope directly. The solution is a shared service — specifically a factory that acts as a singleton.
The factory (shared state container)
app.factory('Stuff', function() {
return {};
});
That's it. The factory returns an object that AngularJS caches and injects as the same instance into every controller that asks for it. It's a singleton by design.
Controller One (writing)
app.controller('oneController', function($scope, Stuff) {
$scope.stuff = Stuff;
$scope.stuff.thing = "I'm a thing!";
});
Controller Two (reading)
app.controller('twoController', function($scope, Stuff) {
$scope.thing = Stuff.thing;
});
Any controller that injects Stuff is looking at the same object, so changes in one are immediately visible in the other. Name your factory something more meaningful than Stuff — UserSession, CartState, AppConfig, etc.
Reacting to changes
If Controller Two needs to react when Controller One updates the value (rather than just reading it once), use $watch:
app.controller('twoController', function($scope, Stuff) {
$scope.$watch(function() {
return Stuff.thing;
}, function(newValue) {
$scope.thing = newValue;
});
});
Or emit events through $rootScope for one-time notifications:
// In controller one
$rootScope.$emit('thingUpdated', Stuff.thing);
// In controller two
$rootScope.$on('thingUpdated', function(event, value) {
$scope.thing = value;
});
Modern equivalent: Angular 14+ with RxJS BehaviorSubject
If you're starting a new project today, here's the Angular 2+ equivalent of the same pattern. A BehaviorSubject from RxJS gives you a shared observable that any component can subscribe to:
// shared-state.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class SharedStateService {
private thingSubject = new BehaviorSubject<string>('');
thing$ = this.thingSubject.asObservable();
setThing(value: string) {
this.thingSubject.next(value);
}
getThing(): string {
return this.thingSubject.getValue();
}
}
// component-one.component.ts
import { Component } from '@angular/core';
import { SharedStateService } from './shared-state.service';
@Component({ selector: 'app-one', template: `<button (click)="update()">Update</button>` })
export class ComponentOne {
constructor(private state: SharedStateService) {}
update() {
this.state.setThing("I'm a thing!");
}
}
// component-two.component.ts
import { Component, OnInit } from '@angular/core';
import { SharedStateService } from './shared-state.service';
@Component({ selector: 'app-two', template: `<p>{{ thing }}</p>` })
export class ComponentTwo implements OnInit {
thing = '';
constructor(private state: SharedStateService) {}
ngOnInit() {
this.state.thing$.subscribe(value => this.thing = value);
}
}
Same idea — a singleton service — but with an observable stream instead of a plain object, which makes reactive updates much easier to manage.
Tell your friends...