Services
Services are a fundamental building block in AngularDart applications. They handle tasks like data fetching, business logic, and shared state management.
What is a Service?
A service is a class with a specific, well-defined purpose. Services are typically:
- Singletons that provide shared data or functionality
- Data access layers that communicate with APIs
- Business logic containers
- State management solutions
Creating a Service
import 'package:angulardart/angular.dart';
@Injectable()
class UserService {
List<User> _users = [
User(id: 1, name: 'Alice'),
User(id: 2, name: 'Bob'),
];
List<User> getUsers() => List.unmodifiable(_users);
User? getUserById(int id) {
return _users.where((u) => u.id == id).firstOrNull;
}
void addUser(User user) {
_users.add(user);
}
void updateUser(int id, User updatedUser) {
final index = _users.indexWhere((u) => u.id == id);
if (index != -1) {
_users[index] = updatedUser;
}
}
void deleteUser(int id) {
_users.removeWhere((u) => u.id == id);
}
}
class User {
final int id;
final String name;
User({required this.id, required this.name});
}
Providing a Service
Root-level (Singleton)
@Component(
selector: 'app-component',
template: '<router-outlet></router-outlet>',
providers: [ClassProvider(UserService)],
)
class AppComponent {}
Component-level
@Component(
selector: 'user-list',
template: '<ul>...</ul>',
providers: [ClassProvider(UserService)],
)
class UserListComponent {
final UserService _userService;
UserListComponent(this._userService);
}
Using a Service
@Component(
selector: 'user-list',
template: '<h2>Users</h2>'
'<ul>'
'<li *ngFor="let user of users">'
'{{ user.name }}'
'<button (click)="deleteUser(user.id)">Delete</button>'
'</li>'
'</ul>'
'<button (click)="addUser()">Add User</button>',
directives: [NgFor],
)
class UserListComponent implements OnInit {
final UserService _userService;
List<User> users = [];
UserListComponent(this._userService);
@override
void ngOnInit() {
users = _userService.getUsers();
}
void addUser() {
final newUser = User(id: users.length + 1, name: 'New User');
_userService.addUser(newUser);
users = _userService.getUsers();
}
void deleteUser(int id) {
_userService.deleteUser(id);
users = _userService.getUsers();
}
}
Async Services
Services often handle asynchronous operations:
@Injectable()
class ApiService {
final String _baseUrl = 'https://api.example.com';
Future<List<User>> getUsers() async {
final response = await HttpRequest.request('$_baseUrl/users');
final data = jsonDecode(response.responseText!) as List;
return data.map((item) => User.fromJson(item)).toList();
}
Future<User> getUser(int id) async {
final response = await HttpRequest.request('$_baseUrl/users/$id');
final data = jsonDecode(response.responseText!) as Map;
return User.fromJson(data);
}
Future<void> createUser(User user) async {
await HttpRequest.request(
'$_baseUrl/users',
method: 'POST',
sendData: jsonEncode(user.toJson()),
);
}
}
Using async services in components
@Component(
selector: 'user-list',
template: '<div *ngIf="isLoading">Loading...</div>'
'<div *ngIf="error" class="error">{{ error }}</div>'
'<ul *ngIf="!isLoading">'
'<li *ngFor="let user of users">{{ user.name }}</li>'
'</ul>',
directives: [NgIf, NgFor],
)
class UserListComponent implements OnInit {
final ApiService _apiService;
List<User> users = [];
bool isLoading = true;
String? error;
UserListComponent(this._apiService);
@override
void ngOnInit() {
_loadUsers();
}
Future<void> _loadUsers() async {
isLoading = true;
error = null;
try {
users = await _apiService.getUsers();
} catch (e) {
error = 'Failed to load users: $e';
} finally {
isLoading = false;
}
}
}
Services with Streams
Services can expose reactive streams:
@Injectable()
class CartService {
final _items = StreamController<List<CartItem>>.broadcast();
final List<CartItem> _cart = [];
Stream<List<CartItem>> get items => _items.stream;
List<CartItem> get currentItems => List.unmodifiable(_cart);
int get itemCount => _cart.length;
void addItem(CartItem item) {
_cart.add(item);
_items.add(List.unmodifiable(_cart));
}
void removeItem(int index) {
_cart.removeAt(index);
_items.add(List.unmodifiable(_cart));
}
void clear() {
_cart.clear();
_items.add([]);
}
void dispose() {
_items.close();
}
}
Consuming streams in components
@Component(
selector: 'cart',
template: '<h2>Cart ({{ items.length }} items)</h2>'
'<ul>'
'<li *ngFor="let item of items; let i = index">'
'{{ item.name }} - {{ item.price }}'
'<button (click)="removeItem(i)">Remove</button>'
'</li>'
'</ul>',
directives: [NgIf, NgFor],
)
class CartComponent implements OnInit, OnDestroy {
final CartService _cartService;
List<CartItem> items = [];
StreamSubscription? _sub;
CartComponent(this._cartService);
@override
void ngOnInit() {
items = _cartService.currentItems;
_sub = _cartService.items.listen((newItems) {
items = newItems;
});
}
@override
void ngOnDestroy() {
_sub?.cancel();
}
void removeItem(int index) {
_cartService.removeItem(index);
}
}
Service with Dependencies
Services can depend on other services:
@Injectable()
class AuthService {
final StorageService _storage;
final ApiService _api;
User? _currentUser;
AuthService(this._storage, this._api);
User? get currentUser => _currentUser;
bool get isLoggedIn => _currentUser != null;
Future<bool> login(String email, String password) async {
try {
final user = await _api.authenticate(email, password);
_currentUser = user;
_storage.save('auth_token', user.token);
return true;
} catch (e) {
return false;
}
}
void logout() {
_currentUser = null;
_storage.remove('auth_token');
}
}
@Component(
selector: 'app',
providers: [
ClassProvider(StorageService),
ClassProvider(ApiService),
ClassProvider(AuthService),
],
)
class AppComponent {}
Best Practices
- Keep services focused on a single responsibility
- Use
@Injectable()annotation for all services - Provide services at the appropriate level (root vs component)
- Use streams for reactive data
- Handle errors gracefully
- Clean up resources in a
dispose()method - Use abstract classes for service interfaces