Anote uma vez: AngularDart
Apresentando a herança de componentes
O AngularDart permite que desenvolvedores criem componentes e diretivas anotando definições de classe com metadados. Esses metadados instruem o compilador a gerar o código necessário para injeção de dependência, construção de templates, data binding e muito mais.
import 'package:angular/angular.dart';
@Component(
selector: 'greet',
template: '<p>Hello !</p>',
)
class GreetComponent {
String name;
}
Até pouco tempo atrás, o compilador do AngularDart não processava metadados em supertipos. Isso significava que compartilhar funcionalidades comuns entre componentes e diretivas diferentes por meio de herança exigia declarar novamente todos os metadados em cada classe.
Vamos ver como era esse padrão.
Ao longo deste artigo, voltaremos ao exemplo a seguir para demonstrar os benefícios da herança de metadados. É um exemplo simplificado, tirado dos componentes Material Design para AngularDart oficiais usados no Google.
import 'dart:async';
import 'dart:html';
import 'package:angular/angular.dart';
@Directive(selector: '[my-button]')
class MyButtonDirective {
final StreamController<UIEvent> _trigger = new StreamController<UIEvent>();
@Input()
bool disabled = false;
@Output()
Stream<UIEvent> get trigger => _trigger.stream;
@HostListener('click', const [r'$event'])
void onClick(MouseEvent event) {
if (disabled) return;
_trigger.add(event);
}
@HostListener('keypress', const [r'$event'])
void onKeyPress(KeyboardEvent event) {
if (disabled) return;
if (event.keyCode == KeyCode.ENTER) {
_trigger.add(event);
}
}
}
Aqui temos uma diretiva simples que adiciona a funcionalidade de botão ao seu elemento host. Se o elemento host for clicado, ou estiver com foco quando a tecla enter for pressionada, ele emite um evento pelo output
trigger. Ele também pode ser desabilitado para não emitir eventos. Essa lógica pode ser reaproveitada para criar um componente de botão usando herança.
import 'package:angular/angular.dart';
import 'my_button_directive.dart';
@Component(
selector: 'my-button',
template: '<ng-content></ng-content>',
inputs: const ['disabled'],
outputs: const ['trigger'],
host: const {
'(click)': r'onClick($event)',
'(keypress)': r'onKeyPress($event)',
},
)
class MyButtonComponent extends MyButtonDirective {}
Agora temos um componente que herda o comportamento de botão da diretiva, mas percebeu a necessidade de declarar de novo o input, o output e os host listeners na anotação do componente derivado?
A partir da versão 4.0, declarar metadados novamente em tipos derivados não é mais necessário!
import 'package:angular/angular.dart';
import 'my_button_directive.dart';
@Component(
selector: 'my-button',
template: '<ng-content></ng-content>',
)
class MyButtonComponent extends MyButtonDirective {}
Agora MyButtonComponent tem implicitamente os mesmos input, output e host listeners que
MyButtonDirective, de forma parecida com o jeito como uma classe normalmente herda propriedades e métodos. Agora que você viu um exemplo simples e, esperamos, convincente, vamos ver exatamente o que pode ser herdado e como isso funciona.
O que pode ser herdado?
#As anotações a seguir agora são herdadas de todos os supertipos, incluindo superclasses, interfaces e mixins.
@ContentChild/@ContentChildren@HostBinding@HostListener@Input@Output@ViewChild/@ViewChildren
Isso significa que não é mais necessário declarar novamente nenhuma das anotações acima em uma classe derivada.
O que não pode ser herdado?
#
Templates, estilos e outros parâmetros de @Directive não são herdados. Só os metadados vinculados a uma propriedade ou método são herdados. Essa decisão foi tomada porque tipos derivados podem sobrescrever acessores e métodos, referenciando a implementação original via
super, enquanto não existe um mecanismo assim para os valores vinculados a outros metadados.
Metadados herdados são imutáveis
#Ainda é válido declarar anotações novamente em tipos derivados; porém, não é permitido modificar os metadados herdados nem vinculá-los a outra propriedade.
import 'package:angular/angular.dart';
import 'my_button_directive.dart';
@Directive(selector: '[illegal-button]')
class IllegalButtonDirective extends MyButtonDirective {
@Input('notEnabled') // Changing the binding name causes a compile error.
set disabled(bool value) {
super.disabled = value;
}
}
Essa restrição pode ser removida no futuro, mas por enquanto é necessária para simplificar o ferramental e garantir que componentes e diretivas possam ser substituídos com facilidade.
Tipos base de metadados
#A herança de metadados traz uma consequência interessante: classes que não são, elas mesmas, diretivas ou componentes agora podem ser anotadas para servir de tipos base ou mixins para outras. Vamos desenvolver mais o nosso exemplo para demonstrar.
Há um problema na implementação atual do componente de botão: o botão não pode receber foco. Isso significa que ele não é acessível e não pode ser acionado pelo listener de tecla pressionada. Esse problema também existe na diretiva de botão se o elemento host dela não puder receber foco. Podemos resolver isso fazendo a diretiva de botão definir a propriedade
tabindex no elemento host.
MyButtonDirective poderia implementar isso diretamente, mas vamos imaginar que temos outros componentes que também vão querer o comportamento de receber foco. Em vez disso, vamos criar uma classe abstrata que pode ser estendida ou usada como mixin por diretivas ou componentes.
import 'package:angular/angular.dart';
abstract class HasTabIndex {
bool get disabled;
@HostBinding('tabindex')
String get tabIndex => disabled ? null : '0';
}
Agora basta estender HasTabIndex ou usá-la como mixin para que a diretiva de botão (e, por consequência, o componente derivado) possa receber foco.
@Directive(selector: '[my-button]')
class MyButtonDirective extends HasTabIndex {
...
}
Conclusão
#Vimos como os metadados herdáveis reduzem a duplicação de código entre componentes e diretivas com funcionalidades em comum. Além disso, eles devem melhorar a legibilidade, já que permitem que os metadados fiquem ao lado do campo ou método associado. Esperamos que esse recurso seja útil no desenvolvimento e na manutenção dos seus próprios projetos.
Mais do Dart
Trazendo construtores primários para o Dart
Um olhar por dentro das decisões de design, dos trade-offs e do açúcar sintático por trás da adição de construtores primários no Dart 3.13.
A história da interop com JS no Dart
O suporte ao Wasm acaba de chegar ao Flutter beta atual, graças a um marco empolgante de interop com JavaScript alcançado no Dart 3.3. Para celebrar…