docs(guide/lifecycle-hooks): add Dart version of example

NOTE: The Dart version generates fewer lifecycle events than the TS version.
See angular/angular#6498 for details.

closes #733
This commit is contained in:
Adão Júnior 2016-01-20 19:14:46 -02:00 committed by Kathy Walrath
parent 6ee3756989
commit c4ae1f1b1b
14 changed files with 632 additions and 0 deletions

View File

@ -0,0 +1,94 @@
// #docregion
import 'package:angular2/angular2.dart';
import 'logger_service.dart';
import 'child_component.dart';
@Component(
selector: 'after-content',
template: '''
<div class="after-content">
<div>-- child content begins --</div>
<ng-content></ng-content>
<div>-- child content ends --</div>
</div>
''',
styles: const ['.after-content {background: LightCyan; padding: 8px;}'])
class AfterContentComponent
implements AfterContentChecked, AfterContentInit, AfterViewInit {
LoggerService _logger;
// Query for a CONTENT child of type `ChildComponent`
@ContentChild(ChildComponent) ChildComponent contentChild;
// Query for a VIEW child of type`ChildComponent`
// No such VIEW child exists!
// This component holds content but no view of that type.
@ViewChild(ChildComponent) ChildComponent viewChild;
String _prevHero;
AfterContentComponent(this._logger) {
_logger.log('AfterContent ctor: $message');
}
///// Hooks
ngAfterContentInit() {
// contentChild is set after the content has been initialized
_logger.log('AfterContentInit: $message');
}
get hasViewChild => viewChild != null;
ngAfterViewInit() {
_logger
.log('AfterViewInit: There is ${hasViewChild ? 'a' : 'no'} view child');
}
ngAfterContentChecked() {
// contentChild is updated after the content has been checked
// Called frequently; only report when the hero changes
if (!hasContentChild || _prevHero == contentChild.hero) return;
_prevHero = contentChild.hero;
_logger.log('AfterContentChecked: $message');
}
bool get hasContentChild => contentChild != null;
String get message => hasContentChild
? '"${contentChild.hero}" child content'
: 'no child content';
}
@Component(
selector: 'after-content-parent',
template: '''
<div class="parent">
<h2>AfterContent</h2>
<after-content>
<input [(ngModel)]="hero">
<button (click)="showChild = !showChild">Toggle child view</button>
<my-child *ngIf="showChild" [hero]="hero"></my-child>
</after-content>
<h4>-- Lifecycle Hook Log --</h4>
<div *ngFor="#msg of hookLog">{{msg}}</div>
</div>
''',
styles: const [
'.parent {background: powderblue; padding: 8px; margin:100px 8px;}'
],
directives: const [AfterContentComponent, ChildComponent],
providers: const [LoggerService])
class AfterContentParentComponent {
List<String> hookLog;
String hero = 'Magneta';
bool showChild = true;
AfterContentParentComponent(LoggerService logger) {
hookLog = logger.logs;
}
}

View File

@ -0,0 +1,76 @@
// #docregion
import 'package:angular2/angular2.dart';
import 'child_component.dart';
import 'logger_service.dart';
@Component(
selector: 'after-view-parent',
template: '''
<div class="parent">
<h2>AfterView</h2>
<div>
<input [(ngModel)]="hero">
<button (click)="showChild = !showChild">Toggle child view</button>
<my-child *ngIf="showChild" [hero]="hero"></my-child>
</div>
<h4>-- Lifecycle Hook Log --</h4>
<div *ngFor="#msg of hookLog">{{msg}}</div>
</div>
''',
styles: const [
'.parent {background: burlywood; padding: 8px; margin:100px 8px;}'
],
directives: const [ChildComponent],
providers: const [LoggerService])
class AfterViewParentComponent
implements AfterContentInit, AfterViewChecked, AfterViewInit {
LoggerService _logger;
List<String> hookLog;
String hero = 'Magneta';
bool showChild = true;
// Query for a CONTENT child of type `ChildComponent`
// No such CONTENT child exists!
// This component holds a view but no content of that type.
@ContentChild(ChildComponent)
ChildComponent contentChild;
// Query for a VIEW child of type `ChildComponent`
@ViewChild(ChildComponent)
ChildComponent viewChild;
String _prevHero;
AfterViewParentComponent(this._logger) {
hookLog = _logger.logs;
_logger.log('AfterView ctor: $message');
}
bool get _hasContentChild => contentChild != null;
bool get _hasViewChild => viewChild != null;
///// Hooks
ngAfterContentInit() {
_logger.log(
'AfterContentInit: There is ${ _hasContentChild ? 'a' : 'no'} content child');
}
ngAfterViewInit() {
// viewChild is set after the view has been initialized
_logger.log('AfterViewInit: $message');
}
ngAfterViewChecked() {
// viewChild is updated after the view has been checked
// Called frequently; only report when the hero changes
if (!_hasViewChild || _prevHero == viewChild.hero) return;
_prevHero = viewChild.hero;
_logger.log('AfterViewChecked: $message');
}
String get message =>
_hasViewChild ? '"${viewChild.hero}" child view' : 'no child view';
}

View File

@ -0,0 +1,28 @@
// #docregion
import 'package:angular2/angular2.dart';
import 'peek_a_boo_parent_component.dart';
import 'on_changes_component.dart';
import 'after_view_component.dart';
import 'after_content_parent.dart';
import 'spy_component.dart';
import 'counter_component.dart';
@Component(
selector: 'my-app',
template: '''
<peek-a-boo-parent></peek-a-boo-parent>
<on-changes-parent></on-changes-parent>
<after-view-parent></after-view-parent>
<after-content-parent></after-content-parent>
<spy-parent></spy-parent>
<counter-parent></counter-parent>
''',
directives: const [
PeekABooParentComponent,
OnChangesParentComponent,
AfterViewParentComponent,
AfterContentParentComponent,
SpyParentComponent,
CounterParentComponent
])
class AppComponent {}

View File

@ -0,0 +1,19 @@
// #docregion
import 'package:angular2/angular2.dart';
@Component(
selector: 'my-child',
template: '''
<div class="my-child">
<div>-- child view begins --</div>
<div class="child">{{hero}} is my hero.</div>
<div>-- child view ends --</div>
</div>
''',
styles: const [
'.child {background: Yellow; padding: 8px; }',
'.my-child {background: LightYellow; padding: 8px; margin-top: 8px}'
])
class ChildComponent {
@Input() String hero;
}

View File

@ -0,0 +1,76 @@
// #docregion
import 'package:angular2/angular2.dart';
import 'spy_directive.dart';
import 'logger_service.dart';
@Component(
selector: 'my-counter',
template: '''
<div class="counter">
Counter = {{counter}}
<h5>-- Counter Change Log --</h5>
<div *ngFor="#chg of changeLog" mySpy>{{chg}}</div>
</div>
''',
styles: const [
'.counter {background: LightYellow; padding: 8px; margin-top: 8px}'
],
directives: const [Spy])
class MyCounter implements OnChanges {
@Input() num counter;
List<String> changeLog = [];
ngOnChanges(Map<String, SimpleChange> changes) {
// Empty the changeLog whenever counter goes to zero
// hint: this is a way to respond programmatically to external value changes.
if (this.counter == 0) {
changeLog.clear();
}
// A change to `counter` is the only change we care about
SimpleChange prop = changes['counter'];
var prev = prop.isFirstChange() ? "{}" : prop.previousValue;
changeLog.add(
'counter: currentValue = ${prop.currentValue}, previousValue = $prev');
}
}
@Component(
selector: 'counter-parent',
template: '''
<div class="parent">
<h2>Counter Spy</h2>
<button (click)="updateCounter()">Update counter</button>
<button (click)="reset()">Reset Counter</button>
<my-counter [counter]="value"></my-counter>
<h4>-- Spy Lifecycle Hook Log --</h4>
<div *ngFor="#msg of spyLog">{{msg}}</div>
</div>
''',
styles: const [
'.parent {background: gold; padding: 10px; margin:100px 8px;}'
],
directives: const [MyCounter],
providers: const [LoggerService])
class CounterParentComponent {
num value;
List<String> spyLog = [];
LoggerService _logger;
CounterParentComponent(this._logger) {
spyLog = _logger.logs;
reset();
}
updateCounter() => value += 1;
reset() {
_logger.log('-- reset --');
value = 0;
}
}

View File

@ -0,0 +1,18 @@
import 'package:angular2/angular2.dart';
import 'dart:async';
@Injectable()
class LoggerService {
List<String> logs = [];
log(String msg, [bool noTick = false]) {
if (!noTick) {
tick();
}
logs.add(msg);
}
clear() => logs.clear();
tick() => new Future(() {});
}

View File

@ -0,0 +1,80 @@
// #docregion
import 'package:angular2/angular2.dart';
import 'dart:convert';
class Hero {
String name;
Hero(this.name);
Map toJson() => {'name': name};
}
@Component(
selector: 'my-hero',
template: '''
<div class="hero">
<p>{{hero.name}} can {{power}}</p>
<h4>-- Change Log --</h4>
<div *ngFor="#chg of changeLog">{{chg}}</div>
</div>
''',
styles: const [
'.hero {background: LightYellow; padding: 8px; margin-top: 8px}',
'p {background: Yellow; padding: 8px; margin-top: 8px}'
])
class MyHeroComponent implements OnChanges {
@Input() Hero hero;
@Input() String power;
@Input() bool reset;
List<String> changeLog = [];
ngOnChanges(Map<String, SimpleChange> changes) {
// Empty the changeLog whenever 'reset' property changes
// hint: this is a way to respond programmatically to external value changes.
if (changes.containsKey('reset')) changeLog.clear();
changes.forEach((String key, SimpleChange change) {
String cur = JSON.encode(change.currentValue);
String prev =
change.isFirstChange() ? "{}" : JSON.encode(change.previousValue);
changeLog.add('$key: currentValue = ${cur}, previousValue = $prev');
});
}
}
@Component(
selector: 'on-changes-parent',
template: '''
<div class="parent">
<h2>OnChanges</h2>
<div>Hero.name: <input [(ngModel)]="hero.name"> <i>does NOT trigger onChanges</i></div>
<div>Power: <input [(ngModel)]="power"> <i>DOES trigger onChanges</i></div>
<div><button (click)="reset()">Reset Log</button> <i>triggers onChanges and clears the change log</i></div>
<my-hero [hero]="hero" [power]="power" [reset]="resetTrigger"></my-hero>
</div>
''',
styles: const [
'.parent {background: Lavender; padding: 10px; margin:100px 8px;}'
],
directives: const [MyHeroComponent])
class OnChangesParentComponent {
Hero hero;
String power;
bool resetTrigger = false;
OnChangesParentComponent() {
reset();
}
reset() {
// new Hero object every time; triggers onChange
hero = new Hero('Windstorm');
// setting power only triggers onChange if this value is different
power = 'sing';
// always triggers onChange ... which is interpreted as a reset
resetTrigger = !resetTrigger;
}
}

View File

@ -0,0 +1,77 @@
// #docregion
// #docregion lc-imports
import 'package:angular2/angular2.dart';
import 'package:lifecycle_hooks/logger_service.dart';
int nextId = 1;
@Component(
selector: 'peek-a-boo',
template: '<p>Now you see my hero, {{name}}</p>',
styles: const ['p {background: LightYellow; padding: 8px}'])
class PeekABooComponent
implements
OnChanges,
OnInit,
AfterContentInit,
AfterContentChecked,
AfterViewInit,
AfterViewChecked,
OnDestroy {
@Input() String name;
int _afterContentCheckedCounter = 1;
int _afterViewCheckedCounter = 1;
int _id = nextId++;
LoggerService _logger;
int _onChangesCounter = 1;
String _verb = 'initialized';
PeekABooComponent(this._logger);
// Only called if there is an @input variable set by parent.
ngOnChanges(Map<String, SimpleChange> changes) {
List<String> messages = [];
changes.forEach((String propName, SimpleChange change) {
if (propName == 'name') {
var name = changes['name'].currentValue;
messages.add('name $_verb to "$name"');
} else {
messages.add('$propName $_verb');
}
});
_logIt('onChanges (${_onChangesCounter++}): ${messages.join('; ')}');
_verb = 'changed'; // Next time it will be a change
}
ngOnInit() => _logIt('onInit');
ngAfterContentInit() => _logIt('afterContentInit');
// Called after every change detection check
// of the component (directive) CONTENT
// Beware! Called frequently!
ngAfterContentChecked() {
int counter = _afterContentCheckedCounter++;
_logIt('afterContentChecked (${counter})');
}
ngAfterViewInit() => _logIt('afterViewInit');
// Called after every change detection check
// of the component (directive) VIEW
// Beware! Called frequently!
ngAfterViewChecked() {
int counter = _afterViewCheckedCounter++;
_logIt('afterViewChecked ($counter)');
}
ngOnDestroy() => _logIt('onDestroy');
_logIt(String msg) {
// Don't tick or else
// the AfterContentChecked and AfterViewChecked recurse.
// Let parent call tick()
_logger.log("#$_id $msg", true);
}
}

View File

@ -0,0 +1,53 @@
// #docregion
import 'package:angular2/angular2.dart';
import 'package:lifecycle_hooks/logger_service.dart';
import 'package:lifecycle_hooks/peek_a_boo_component.dart';
@Component(
selector: 'peek-a-boo-parent',
template: '''
<div class="parent">
<h2>Peek-A-Boo</h2>
<button (click)="toggleChild()">
{{hasChild ? 'Destroy' : 'Create'}} PeekABooComponent
</button>
<button (click)="updateHero()" [hidden]="!hasChild">Update Hero</button>
<peek-a-boo *ngIf="hasChild" [name]="heroName">
</peek-a-boo>
<h4>-- Lifecycle Hook Log --</h4>
<div *ngFor="#msg of hookLog">{{msg}}</div>
</div>
''',
styles: const [
'.parent {background: moccasin; padding: 10px; margin:100px 8px}'
],
directives: const [PeekABooComponent],
providers: const [LoggerService])
class PeekABooParentComponent {
bool hasChild = false;
List<String> hookLog;
String heroName = 'Windstorm';
LoggerService _logger;
PeekABooParentComponent(this._logger) {
hookLog = _logger.logs;
}
toggleChild() {
hasChild = !hasChild;
if (hasChild) {
heroName = 'Windstorm';
_logger.clear(); // clear log on create
}
_logger.tick();
}
updateHero() {
heroName += '!';
_logger.tick();
}
}

View File

@ -0,0 +1,52 @@
// #docregion
import 'package:angular2/angular2.dart';
import 'logger_service.dart';
import 'spy_directive.dart';
@Component(
selector: 'spy-parent',
template: '''
<div class="parent">
<h2>Spy Directive</h2>
<input [(ngModel)]="newName" (keyup.enter)="addHero()">
<button (click)="addHero()">Add Hero</button>
<button (click)="reset()">Reset Heroes</button>
<p></p>
<div *ngFor="#hero of heroes" mySpy class="heroes">
{{hero}}
</div>
<h4>-- Spy Lifecycle Hook Log --</h4>
<div *ngFor="#msg of spyLog">{{msg}}</div>
</div>
''',
styles: const [
'.parent {background: khaki; padding: 10px; margin:100px 8px}',
'.heroes {background: LightYellow; padding: 0 8px}'
],
directives: const [Spy],
providers: const [LoggerService])
class SpyParentComponent {
String newName = 'Herbie';
List<String> heroes = ['Windstorm', 'Magneta'];
List<String> spyLog;
LoggerService _logger;
SpyParentComponent(this._logger) {
spyLog = _logger.logs;
}
addHero() {
if (newName.trim().isNotEmpty) {
heroes.add(newName.trim());
newName = '';
}
}
reset() {
_logger.log('-- reset --');
heroes.clear();
}
}

View File

@ -0,0 +1,21 @@
// #docregion
import 'package:angular2/angular2.dart';
import 'logger_service.dart';
int nextId = 1;
// Spy on any element to which it is applied.
// Usage: <div mySpy>...</div>
@Directive(selector: '[mySpy]')
class Spy implements OnInit, OnDestroy {
int _id = nextId++;
LoggerService _logger;
Spy(this._logger);
ngOnInit() => _logIt('onInit');
ngOnDestroy() => _logIt('onDestroy');
_logIt(String msg) => _logger.log('Spy #$_id $msg');
}

View File

@ -0,0 +1,16 @@
name: lifecycle_hooks
description: Lifecycle Hooks
version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.1
browser: ^0.10.0
dart_to_js_script_rewriter: ^0.1.0
transformers:
- angular2:
platform_directives:
- 'package:angular2/common.dart#CORE_DIRECTIVES'
- 'package:angular2/common.dart#FORM_DIRECTIVES'
entry_points: web/main.dart
- dart_to_js_script_rewriter

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<!-- #docregion -->
<html>
<head>
<title>Angular 2 Lifecycle Hooks</title>
<script defer src="main.dart" type="application/dart"></script>
<script defer src="packages/browser/dart.js"></script>
</head>
<body>
<my-app>Loading...</my-app>
</body>
</html>

View File

@ -0,0 +1,7 @@
// #docregion
import 'package:angular2/bootstrap.dart';
import 'package:lifecycle_hooks/app_component.dart';
main() {
bootstrap(AppComponent);
}