Component Communication
AngularDart provides several ways for components to communicate with each other.
Parent to Child: @Input()
Use @Input() to pass data from a parent component to a child component:
// child_component.dart
@Component(
selector: 'child-component',
template: '<p>Hello, {{ name }}!</p>',
)
class ChildComponent {
@Input()
String name = '';
}
// parent_component.dart
@Component(
selector: 'parent-component',
template: '<child-component [name]="userName"></child-component>',
directives: [ChildComponent],
)
class ParentComponent {
String userName = 'Alice';
}
Input with alias
@Input('user-name')
String internalName = '';
Usage: <child-component [user-name]="name"></child-component>
Child to Parent: @Output()
Use @Output() with a StreamController to emit events from child to parent:
// child_component.dart
@Component(
selector: 'child-component',
template: '<button (click)="notify()">Notify Parent</button>',
)
class ChildComponent {
@Output()
final messageSent = StreamController<String>.broadcast();
void notify() {
messageSent.add('Hello from child!');
}
}
// parent_component.dart
@Component(
selector: 'parent-component',
template: '<child-component (messageSent)="onMessage($event)"></child-component>'
'<p>Message: {{ receivedMessage }}</p>',
directives: [ChildComponent],
)
class ParentComponent {
String receivedMessage = '';
void onMessage(String message) {
receivedMessage = message;
}
}
Two-Way Binding
Combine @Input() and @Output() for two-way binding:
@Component(
selector: 'my-input',
template: '<input [value]="value" (input)="onInput($event)">',
)
class MyInputComponent {
@Input()
String value = '';
@Output()
final valueChange = StreamController<String>.broadcast();
void onInput(Event event) {
final target = event.target as HtmlInputElement;
valueChange.add(target.value);
}
}
Usage with banana-in-a-box syntax:
<my-input [(value)]="userName"></my-input>
Parent Accesses Child: @ViewChild
Use @ViewChild to access a child component or element from the parent:
@Component(
selector: 'parent-component',
template: '<child-component #child></child-component>'
'<button (click)="callChildMethod()">Call Child</button>',
directives: [ChildComponent],
)
class ParentComponent implements AfterViewInit {
@ViewChild('child')
ChildComponent? child;
@override
void ngAfterViewInit() {
child?.doSomething();
}
void callChildMethod() {
child?.doSomething();
}
}
Multiple children with @ViewChildren
@Component(
selector: 'parent-component',
template: '<child-component *ngFor="let item of items"></child-component>',
directives: [ChildComponent, NgFor],
)
class ParentComponent implements AfterViewInit {
@ViewChildren(ChildComponent)
List<ChildComponent>? children;
@override
void ngAfterViewInit() {
children?.forEach((child) => child.initialize());
}
}
Content Projection: @ContentChild
Use @ContentChild to access projected content:
// panel-header directive
@Directive(selector: '[panelHeader]')
class PanelHeaderDirective {}
@Component(
selector: 'panel',
template: '<div class="panel">'
'<ng-content select="[panelHeader]"></ng-content>'
'<ng-content></ng-content>'
'</div>',
)
class PanelComponent implements AfterContentInit {
@ContentChild(PanelHeaderDirective)
PanelHeaderDirective? header;
@override
void ngAfterContentInit() {
if (header != null) {
print('Panel has a header');
}
}
}
Usage:
<panel>
<div panelHeader>My Header</div>
<p>Panel content</p>
</panel>
@ContentChildren
@Component(
selector: 'tab-container',
template: '<ng-content select="tab"></ng-content>',
)
class TabContainerComponent implements AfterContentInit {
@ContentChildren(TabComponent)
List<TabComponent>? tabs;
@override
void ngAfterContentInit() {
tabs?.first.isActive = true;
}
}
Component Interaction via Services
For complex communication, use a shared service:
@Injectable()
class MessageService {
final _messages = StreamController<String>.broadcast();
Stream<String> get messages => _messages.stream;
void send(String message) {
_messages.add(message);
}
}
@Component(
selector: 'sender',
template: '<button (click)="send()">Send</button>',
providers: [],
)
class SenderComponent {
final MessageService _service;
SenderComponent(this._service);
void send() {
_service.send('Hello!');
}
}
@Component(
selector: 'receiver',
template: '<p>{{ message }}</p>',
)
class ReceiverComponent implements OnInit, OnDestroy {
final MessageService _service;
String message = '';
StreamSubscription? _sub;
ReceiverComponent(this._service);
@override
void ngOnInit() {
_sub = _service.messages.listen((msg) => message = msg);
}
@override
void ngOnDestroy() {
_sub?.cancel();
}
}
Best Practices
- Use
@Input()for parent-to-child communication - Use
@Output()for child-to-parent events - Use services for sibling or distant component communication
- Use
@ViewChildsparingly; prefer data binding - Always unsubscribe from streams in
ngOnDestroy