docs(dev guide): lifecycle-hooks - updated dart/ts code and new dart prose
Mainly copyedits, but also - Dart .jade extends TS .jade file with minor overrides - Significant update of example code (so it matches the ts example in its appearance and behavior). - Tweaks to Dart code. - A few extra/corrected mixin definitions in `_util-fns.jade`.
This commit is contained in:
parent
ce6c645501
commit
ac92e77611
|
@ -1,5 +1,17 @@
|
|||
//- Mixins and associated functions
|
||||
|
||||
- var _docsFor = 'ts'; // or 'dart' or 'js'.
|
||||
|
||||
mixin ifDocsFor(lang)
|
||||
if _docsFor.toLowerCase() === lang.toLowerCase()
|
||||
block
|
||||
|
||||
mixin adjExPath(path)
|
||||
if adjustExamplePath
|
||||
| #{adjustExamplePath(path)}
|
||||
else
|
||||
| #{path}
|
||||
|
||||
mixin includeShared(filePath, region)
|
||||
- var newPath = translatePath(filePath, region);
|
||||
!=partial(newPath)
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'logger_service.dart';
|
||||
|
||||
//////////////////
|
||||
@Component(
|
||||
selector: 'my-child',
|
||||
template: '<input [(ngModel)]="hero">')
|
||||
class ChildComponent {
|
||||
String hero = 'Magneta';
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
@Component(
|
||||
selector: 'after-content',
|
||||
// #docregion template
|
||||
template: '''
|
||||
<div>-- projected content begins --</div>
|
||||
<ng-content></ng-content>
|
||||
<div>-- projected content ends --</div>
|
||||
<p *ngIf="comment != null" class="comment">{{comment}}</p>
|
||||
'''
|
||||
// #enddocregion template
|
||||
)
|
||||
// #docregion hooks
|
||||
class AfterContentComponent implements AfterContentChecked, AfterContentInit {
|
||||
String _prevHero = '';
|
||||
String comment = '';
|
||||
|
||||
// Query for a CONTENT child of type `ChildComponent`
|
||||
@ContentChild(ChildComponent) ChildComponent contentChild;
|
||||
|
||||
// #enddocregion hooks
|
||||
final LoggerService _logger;
|
||||
|
||||
AfterContentComponent(this._logger) {
|
||||
_logIt('AfterContent constructor');
|
||||
}
|
||||
|
||||
// #docregion hooks
|
||||
ngAfterContentInit() {
|
||||
// contentChild is set after the content has been initialized
|
||||
_logIt('AfterContentInit');
|
||||
_doSomething();
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
// contentChild is updated after the content has been checked
|
||||
if (_prevHero == contentChild?.hero) {
|
||||
_logIt('AfterContentChecked (no change)');
|
||||
} else {
|
||||
_prevHero = contentChild?.hero;
|
||||
_logIt('AfterContentChecked');
|
||||
_doSomething();
|
||||
}
|
||||
}
|
||||
// #enddocregion hooks
|
||||
// #docregion do-something
|
||||
/// This surrogate for real business logic; sets the `comment`
|
||||
void _doSomething() {
|
||||
comment = contentChild.hero.length > 10 ? "That's a long name" : '';
|
||||
}
|
||||
// #enddocregion do-something
|
||||
|
||||
void _logIt(String method) {
|
||||
var child = contentChild;
|
||||
var message = "${method}: ${child?.hero ?? 'no'} child content";
|
||||
_logger.log(message);
|
||||
}
|
||||
// #docregion hooks
|
||||
// ...
|
||||
}
|
||||
// #enddocregion hooks
|
||||
|
||||
//////////////
|
||||
@Component(
|
||||
selector: 'after-content-parent',
|
||||
// #docregion parent-template
|
||||
template: '''
|
||||
<div class="parent">
|
||||
<h2>AfterContent</h2>
|
||||
|
||||
<div *ngIf="show">
|
||||
<after-content>
|
||||
<my-child></my-child>
|
||||
</after-content>
|
||||
</div>
|
||||
|
||||
<h4>-- AfterContent Logs --</h4>
|
||||
<p><button (click)="reset()">Reset</button></p>
|
||||
<div *ngFor="let msg of logs">{{msg}}</div>
|
||||
</div>
|
||||
''',
|
||||
// #enddocregion parent-template
|
||||
styles: const ['.parent {background: burlywood}'],
|
||||
providers: const [LoggerService],
|
||||
directives: const [AfterContentComponent, ChildComponent])
|
||||
class AfterContentParentComponent {
|
||||
final LoggerService _logger;
|
||||
bool show = true;
|
||||
|
||||
AfterContentParentComponent(this._logger);
|
||||
|
||||
List<String> get logs => _logger.logs;
|
||||
|
||||
void reset() {
|
||||
logs.clear();
|
||||
// quickly remove and reload AfterViewComponent which recreates it
|
||||
show = false;
|
||||
_logger.tick().then((_) { show = true; });
|
||||
}
|
||||
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
// #docregion
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'child_component.dart';
|
||||
import 'logger_service.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="let 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;
|
||||
}
|
||||
}
|
|
@ -1,77 +1,117 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'child_component.dart';
|
||||
import 'logger_service.dart';
|
||||
|
||||
//////////////////
|
||||
// #docregion child-view
|
||||
@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="let 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;
|
||||
selector: 'my-child',
|
||||
template: '<input [(ngModel)]="hero">')
|
||||
class ChildViewComponent {
|
||||
String hero = 'Magneta';
|
||||
bool showChild = true;
|
||||
}
|
||||
// #enddocregion child-view
|
||||
|
||||
// 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;
|
||||
//////////////////////
|
||||
@Component(
|
||||
selector: 'after-view',
|
||||
// #docregion template
|
||||
template: '''
|
||||
<div>-- child view begins --</div>
|
||||
<my-child></my-child>
|
||||
<div>-- child view ends --</div>
|
||||
<p *ngIf="comment != null" class="comment">{{comment}}</p>''',
|
||||
// #enddocregion template
|
||||
directives: const [ChildViewComponent])
|
||||
// #docregion hooks
|
||||
class AfterViewComponent implements AfterViewChecked, AfterViewInit {
|
||||
var _prevHero = '';
|
||||
|
||||
// Query for a VIEW child of type `ChildViewComponent`
|
||||
@ViewChild(ChildViewComponent) ChildViewComponent viewChild;
|
||||
|
||||
// #enddocregion hooks
|
||||
final LoggerService _logger;
|
||||
|
||||
// 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');
|
||||
AfterViewComponent(this._logger) {
|
||||
_logIt('AfterView constructor');
|
||||
}
|
||||
|
||||
// #docregion hooks
|
||||
ngAfterViewInit() {
|
||||
// viewChild is set after the view has been initialized
|
||||
_logger.log('AfterViewInit: $message');
|
||||
_logIt('AfterViewInit');
|
||||
_doSomething();
|
||||
}
|
||||
|
||||
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');
|
||||
if (_prevHero == viewChild.hero) {
|
||||
_logIt('AfterViewChecked (no change)');
|
||||
} else {
|
||||
_prevHero = viewChild.hero;
|
||||
_logIt('AfterViewChecked');
|
||||
_doSomething();
|
||||
}
|
||||
}
|
||||
// #enddocregion hooks
|
||||
|
||||
String comment = '';
|
||||
|
||||
// #docregion do-something
|
||||
// This surrogate for real business logic sets the `comment`
|
||||
void _doSomething() {
|
||||
var c = viewChild.hero.length > 10 ? "That's a long name" : '';
|
||||
if (c != comment) {
|
||||
// Wait a tick because the component's view has already been checked
|
||||
_logger.tick().then((_) { comment = c; });
|
||||
}
|
||||
}
|
||||
// #enddocregion do-something
|
||||
|
||||
void _logIt(String method) {
|
||||
var child = viewChild;
|
||||
var message = "${method}: ${child != null ? child.hero:'no'} child view";
|
||||
_logger.log(message);
|
||||
}
|
||||
// #docregion hooks
|
||||
// ...
|
||||
}
|
||||
// #enddocregion hooks
|
||||
|
||||
//////////////
|
||||
@Component(
|
||||
selector: 'after-view-parent',
|
||||
template: '''
|
||||
<div class="parent">
|
||||
<h2>AfterView</h2>
|
||||
|
||||
<after-view *ngIf="show"></after-view>
|
||||
|
||||
<h4>-- AfterView Logs --</h4>
|
||||
<p><button (click)="reset()">Reset</button></p>
|
||||
<div *ngFor="let msg of logs">{{msg}}</div>
|
||||
</div>
|
||||
''',
|
||||
styles: const ['.parent {background: burlywood}'],
|
||||
providers: const [LoggerService],
|
||||
directives: const [AfterViewComponent])
|
||||
class AfterViewParentComponent {
|
||||
final LoggerService _logger;
|
||||
bool show = true;
|
||||
|
||||
AfterViewParentComponent(this._logger);
|
||||
|
||||
List<String> get logs => _logger.logs;
|
||||
|
||||
void reset() {
|
||||
logs.clear();
|
||||
// quickly remove and reload AfterViewComponent which recreates it
|
||||
show = false;
|
||||
_logger.tick().then((_) { show = true; });
|
||||
}
|
||||
|
||||
String get message =>
|
||||
_hasViewChild ? '"${viewChild.hero}" child view' : 'no child view';
|
||||
}
|
||||
// #enddocregion
|
||||
|
|
|
@ -1,29 +1,24 @@
|
|||
// #docregion
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'after_content_parent.dart';
|
||||
import 'after_content_component.dart';
|
||||
import 'after_view_component.dart';
|
||||
import 'counter_component.dart';
|
||||
import 'do_check_component.dart';
|
||||
import 'on_changes_component.dart';
|
||||
import 'peek_a_boo_parent_component.dart';
|
||||
import 'spy_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>
|
||||
''',
|
||||
templateUrl: 'app_component.html',
|
||||
directives: const [
|
||||
PeekABooParentComponent,
|
||||
OnChangesParentComponent,
|
||||
AfterViewParentComponent,
|
||||
AfterContentParentComponent,
|
||||
AfterViewParentComponent,
|
||||
CounterParentComponent,
|
||||
DoCheckParentComponent,
|
||||
OnChangesParentComponent,
|
||||
PeekABooParentComponent,
|
||||
SpyParentComponent,
|
||||
CounterParentComponent
|
||||
])
|
||||
class AppComponent {}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<a id="top"></a>
|
||||
<h1>Component Lifecycle Hooks</h1>
|
||||
<a href="#hooks">Peek-a-boo: (most) lifecycle hooks</a><br>
|
||||
<a href="#onchanges">OnChanges</a><br>
|
||||
<a href="#docheck">DoCheck</a><br>
|
||||
<a href="#after-view">AfterViewInit & AfterViewChecked</a><br>
|
||||
<a href="#after-content">AfterContentInit & AfterContentChecked</a><br>
|
||||
<a href="#spy">Spy: directive with OnInit & OnDestroy</a><br>
|
||||
<a href="#counter">Counter: OnChanges + Spy directive</a><br>
|
||||
|
||||
<a id="hooks"></a>
|
||||
<peek-a-boo-parent></peek-a-boo-parent>
|
||||
<a href="#top">back to top</a>
|
||||
|
||||
<a id="spy"></a>
|
||||
<spy-parent></spy-parent>
|
||||
<a href="#top">back to top</a>
|
||||
|
||||
<a id="onchanges"></a>
|
||||
<on-changes-parent></on-changes-parent>
|
||||
<a href="#top">back to top</a>
|
||||
|
||||
<a id="docheck"></a>
|
||||
<do-check-parent></do-check-parent>
|
||||
<a href="#top">back to top</a>
|
||||
|
||||
<a id="after-view"></a>
|
||||
<after-view-parent></after-view-parent>
|
||||
<a href="#top">back to top</a>
|
||||
|
||||
<a id="after-content"></a>
|
||||
<after-content-parent></after-content-parent>
|
||||
<a href="#top">back to top</a>
|
||||
|
||||
<a id="counter"></a>
|
||||
<counter-parent></counter-parent>
|
||||
<a href="#top">back to top</a>
|
|
@ -1,19 +0,0 @@
|
|||
// #docregion
|
||||
import 'package:angular2/core.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;
|
||||
}
|
|
@ -30,10 +30,10 @@ class MyCounter implements OnChanges {
|
|||
}
|
||||
|
||||
// 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');
|
||||
SimpleChange chng = changes['counter'];
|
||||
var cur = chng.currentValue;
|
||||
var prev = chng.isFirstChange() ? "{}" : chng.previousValue;
|
||||
changeLog.add('counter: currentValue = $cur, previousValue = $prev');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,29 +49,30 @@ class MyCounter implements OnChanges {
|
|||
<my-counter [counter]="value"></my-counter>
|
||||
|
||||
<h4>-- Spy Lifecycle Hook Log --</h4>
|
||||
<div *ngFor="let msg of spyLog">{{msg}}</div>
|
||||
<div *ngFor="let msg of logs">{{msg}}</div>
|
||||
</div>
|
||||
''',
|
||||
styles: const [
|
||||
'.parent {background: gold; padding: 10px; margin:100px 8px;}'
|
||||
],
|
||||
styles: const ['.parent {background: gold;}'],
|
||||
directives: const [MyCounter],
|
||||
providers: const [LoggerService])
|
||||
class CounterParentComponent {
|
||||
final LoggerService _logger;
|
||||
num value;
|
||||
List<String> spyLog = [];
|
||||
|
||||
LoggerService _logger;
|
||||
|
||||
CounterParentComponent(this._logger) {
|
||||
spyLog = _logger.logs;
|
||||
reset();
|
||||
}
|
||||
|
||||
updateCounter() => value += 1;
|
||||
List<String> get logs => _logger.logs;
|
||||
|
||||
reset() {
|
||||
void updateCounter() {
|
||||
value += 1;
|
||||
_logger.tick();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
_logger.log('-- reset --');
|
||||
value = 0;
|
||||
_logger.tick();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
// #docregion
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
class Hero {
|
||||
String name;
|
||||
Hero(this.name);
|
||||
Map<String, dynamic> toJson() => {'name': name};
|
||||
}
|
||||
|
||||
@Component(
|
||||
selector: 'do-check',
|
||||
template: '''
|
||||
<div class="hero">
|
||||
<p>{{hero.name}} can {{power}}</p>
|
||||
|
||||
<h4>-- Change Log --</h4>
|
||||
<div *ngFor="let 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 DoCheckComponent implements DoCheck, OnChanges {
|
||||
@Input()
|
||||
Hero hero;
|
||||
@Input()
|
||||
String power;
|
||||
|
||||
bool changeDetected = false;
|
||||
List<String> changeLog = [];
|
||||
|
||||
String oldHeroName = '';
|
||||
String oldPower = '';
|
||||
int oldLogLength = 0;
|
||||
int noChangeCount = 0;
|
||||
|
||||
// #docregion ng-do-check
|
||||
ngDoCheck() {
|
||||
if (hero.name != oldHeroName) {
|
||||
changeDetected = true;
|
||||
changeLog.add(
|
||||
'DoCheck: Hero name changed to "${hero.name}" from "$oldHeroName"');
|
||||
oldHeroName = hero.name;
|
||||
}
|
||||
|
||||
if (power != oldPower) {
|
||||
changeDetected = true;
|
||||
changeLog.add('DoCheck: Power changed to "$power" from "$oldPower"');
|
||||
oldPower = power;
|
||||
}
|
||||
|
||||
if (changeDetected) {
|
||||
noChangeCount = 0;
|
||||
} else {
|
||||
// log that hook was called when there was no relevant change.
|
||||
var count = noChangeCount += 1;
|
||||
var noChangeMsg =
|
||||
'DoCheck called ${count}x when no change to hero or power';
|
||||
if (count == 1) {
|
||||
// add new "no change" message
|
||||
changeLog.add(noChangeMsg);
|
||||
} else {
|
||||
// update last "no change" message
|
||||
changeLog[changeLog.length - 1] = noChangeMsg;
|
||||
}
|
||||
}
|
||||
|
||||
changeDetected = false;
|
||||
}
|
||||
// #enddocregion ng-do-check
|
||||
|
||||
// Copied from OnChangesComponent
|
||||
ngOnChanges(Map<String, SimpleChange> changes) {
|
||||
changes.forEach((String propName, SimpleChange change) {
|
||||
String cur = JSON.encode(change.currentValue);
|
||||
String prev =
|
||||
change.isFirstChange() ? "{}" : JSON.encode(change.previousValue);
|
||||
changeLog.add('$propName: currentValue = $cur, previousValue = $prev');
|
||||
});
|
||||
}
|
||||
|
||||
void reset() {
|
||||
changeDetected = true;
|
||||
changeLog.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
|
||||
@Component(
|
||||
selector: 'do-check-parent',
|
||||
templateUrl: 'on_changes_parent_component.html',
|
||||
styles: const ['.parent {background: Lavender}'],
|
||||
directives: const [DoCheckComponent])
|
||||
class DoCheckParentComponent {
|
||||
Hero hero;
|
||||
String power;
|
||||
String title = 'DoCheck';
|
||||
@ViewChild(DoCheckComponent)
|
||||
DoCheckComponent childView;
|
||||
|
||||
DoCheckParentComponent() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
hero = new Hero('Windstorm');
|
||||
power = 'sing';
|
||||
childView?.reset();
|
||||
}
|
||||
}
|
|
@ -5,15 +5,23 @@ import 'package:angular2/core.dart';
|
|||
@Injectable()
|
||||
class LoggerService {
|
||||
List<String> logs = [];
|
||||
String _prevMsg = '';
|
||||
int _prevMsgCount = 1;
|
||||
|
||||
log(String msg, [bool noTick = false]) {
|
||||
if (!noTick) {
|
||||
tick();
|
||||
void log(String msg) {
|
||||
if (msg == _prevMsg) {
|
||||
// Repeat message; update last log entry with count.
|
||||
logs[logs.length - 1] = "$msg (${_prevMsgCount += 1}x)";
|
||||
} else {
|
||||
// New message; log it.
|
||||
_prevMsg = msg;
|
||||
_prevMsgCount = 1;
|
||||
logs.add(msg);
|
||||
}
|
||||
logs.add(msg);
|
||||
}
|
||||
|
||||
clear() => logs.clear();
|
||||
void clear() => logs.clear();
|
||||
|
||||
// schedules a view refresh to ensure display catches up
|
||||
tick() => new Future(() {});
|
||||
}
|
||||
|
|
|
@ -6,12 +6,11 @@ import 'package:angular2/core.dart';
|
|||
class Hero {
|
||||
String name;
|
||||
Hero(this.name);
|
||||
|
||||
Map toJson() => {'name': name};
|
||||
Map<String, dynamic> toJson() => {'name': name};
|
||||
}
|
||||
|
||||
@Component(
|
||||
selector: 'my-hero',
|
||||
selector: 'on-changes',
|
||||
template: '''
|
||||
<div class="hero">
|
||||
<p>{{hero.name}} can {{power}}</p>
|
||||
|
@ -24,58 +23,48 @@ class Hero {
|
|||
'.hero {background: LightYellow; padding: 8px; margin-top: 8px}',
|
||||
'p {background: Yellow; padding: 8px; margin-top: 8px}'
|
||||
])
|
||||
class MyHeroComponent implements OnChanges {
|
||||
class OnChangesComponent implements OnChanges {
|
||||
// #docregion inputs
|
||||
@Input() Hero hero;
|
||||
@Input() String power;
|
||||
@Input() bool reset;
|
||||
// #enddocregion inputs
|
||||
|
||||
List<String> changeLog = [];
|
||||
|
||||
// #docregion ng-on-changes
|
||||
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) {
|
||||
changes.forEach((String propName, SimpleChange change) {
|
||||
String cur = JSON.encode(change.currentValue);
|
||||
String prev =
|
||||
change.isFirstChange() ? "{}" : JSON.encode(change.previousValue);
|
||||
changeLog.add('$key: currentValue = ${cur}, previousValue = $prev');
|
||||
changeLog.add('$propName: currentValue = $cur, previousValue = $prev');
|
||||
});
|
||||
}
|
||||
// #enddocregion ng-on-changes
|
||||
|
||||
void reset() { changeLog.clear(); }
|
||||
}
|
||||
|
||||
@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])
|
||||
templateUrl: 'on_changes_parent_component.html',
|
||||
styles: const ['.parent {background: Lavender}'],
|
||||
directives: const [OnChangesComponent])
|
||||
class OnChangesParentComponent {
|
||||
Hero hero;
|
||||
String power;
|
||||
bool resetTrigger = false;
|
||||
String title = 'OnChanges';
|
||||
@ViewChild(OnChangesComponent) OnChangesComponent childView;
|
||||
|
||||
OnChangesParentComponent() {
|
||||
reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
void 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;
|
||||
childView?.reset();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<div class="parent">
|
||||
<h2>{{title}}</h2>
|
||||
|
||||
<table>
|
||||
<tr><td>Power: </td><td><input [(ngModel)]="power"></td></tr>
|
||||
<tr><td>Hero.name: </td><td><input [(ngModel)]="hero.name"></td></tr>
|
||||
</table>
|
||||
<p><button (click)="reset()">Reset Log</button></p>
|
||||
|
||||
<!-- #docregion on-changes -->
|
||||
<on-changes [hero]="hero" [power]="power"></on-changes>
|
||||
<!-- #enddocregion on-changes -->
|
||||
<do-check [hero]="hero" [power]="power"></do-check>
|
||||
</div>
|
|
@ -4,16 +4,37 @@ import 'package:angular2/core.dart';
|
|||
|
||||
import 'logger_service.dart';
|
||||
|
||||
int nextId = 1;
|
||||
int _nextId = 1;
|
||||
|
||||
// #docregion ngOnInit
|
||||
class PeekABoo implements OnInit {
|
||||
final LoggerService _logger;
|
||||
|
||||
PeekABoo(this._logger);
|
||||
|
||||
// implement OnInit's `ngOnInit` method
|
||||
void ngOnInit() { _logIt('OnInit'); }
|
||||
|
||||
void _logIt(String msg) {
|
||||
// Don't tick or else
|
||||
// the AfterContentChecked and AfterViewChecked recurse.
|
||||
// Let parent call tick()
|
||||
_logger.log("#${_nextId++} $msg");
|
||||
}
|
||||
}
|
||||
// #enddocregion ngOnInit
|
||||
|
||||
@Component(
|
||||
selector: 'peek-a-boo',
|
||||
template: '<p>Now you see my hero, {{name}}</p>',
|
||||
styles: const ['p {background: LightYellow; padding: 8px}'])
|
||||
class PeekABooComponent
|
||||
// Don't HAVE to mention the Lifecycle Hook interfaces
|
||||
// unless we want typing and tool support.
|
||||
class PeekABooComponent extends PeekABoo
|
||||
implements
|
||||
OnChanges,
|
||||
OnInit,
|
||||
DoCheck,
|
||||
AfterContentInit,
|
||||
AfterContentChecked,
|
||||
AfterViewInit,
|
||||
|
@ -23,12 +44,13 @@ class PeekABooComponent
|
|||
|
||||
int _afterContentCheckedCounter = 1;
|
||||
int _afterViewCheckedCounter = 1;
|
||||
int _id = nextId++;
|
||||
LoggerService _logger;
|
||||
int _onChangesCounter = 1;
|
||||
String _verb = 'initialized';
|
||||
|
||||
PeekABooComponent(this._logger);
|
||||
PeekABooComponent(LoggerService logger) : super(logger) {
|
||||
var _is = name != null ? 'is' : 'is not';
|
||||
_logIt('name $_is known at construction');
|
||||
}
|
||||
|
||||
// Only called if there is an @input variable set by parent.
|
||||
ngOnChanges(Map<String, SimpleChange> changes) {
|
||||
|
@ -38,41 +60,28 @@ class PeekABooComponent
|
|||
var name = changes['name'].currentValue;
|
||||
messages.add('name $_verb to "$name"');
|
||||
} else {
|
||||
messages.add('$propName $_verb');
|
||||
messages.add('$propName $_verb');
|
||||
}
|
||||
});
|
||||
_logIt('onChanges (${_onChangesCounter++}): ${messages.join('; ')}');
|
||||
_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})');
|
||||
}
|
||||
// Called in every change detection cycle anywhere on the page
|
||||
ngDoCheck() => _logIt('DoCheck');
|
||||
|
||||
ngAfterViewInit() => _logIt('afterViewInit');
|
||||
ngAfterContentInit() => _logIt('AfterContentInit');
|
||||
|
||||
// Called after every change detection check
|
||||
// of the component (directive) VIEW
|
||||
// Beware! Called frequently!
|
||||
ngAfterViewChecked() {
|
||||
int counter = _afterViewCheckedCounter++;
|
||||
_logIt('afterViewChecked ($counter)');
|
||||
}
|
||||
// Called in every change detection cycle anywhere on the page
|
||||
ngAfterContentChecked() { _logIt('AfterContentChecked (${_afterContentCheckedCounter++})'); }
|
||||
|
||||
ngOnDestroy() => _logIt('onDestroy');
|
||||
ngAfterViewInit() => _logIt('AfterViewInit');
|
||||
|
||||
_logIt(String msg) {
|
||||
// Don't tick or else
|
||||
// the AfterContentChecked and AfterViewChecked recurse.
|
||||
// Let parent call tick()
|
||||
_logger.log("#$_id $msg", true);
|
||||
}
|
||||
// Beware! Called frequently!
|
||||
// Called in every change detection cycle anywhere on the page
|
||||
ngAfterViewChecked() { _logIt('AfterViewChecked (${_afterViewCheckedCounter++})'); }
|
||||
|
||||
ngOnDestroy() => _logIt('OnDestroy');
|
||||
}
|
||||
|
|
|
@ -19,24 +19,20 @@ import 'peek_a_boo_component.dart';
|
|||
</peek-a-boo>
|
||||
|
||||
<h4>-- Lifecycle Hook Log --</h4>
|
||||
<div *ngFor="let msg of hookLog">{{msg}}</div>
|
||||
<div *ngFor="let msg of logs">{{msg}}</div>
|
||||
</div>
|
||||
''',
|
||||
styles: const [
|
||||
'.parent {background: moccasin; padding: 10px; margin:100px 8px}'
|
||||
],
|
||||
styles: const ['.parent {background: moccasin}'],
|
||||
directives: const [PeekABooComponent],
|
||||
providers: const [LoggerService])
|
||||
class PeekABooParentComponent {
|
||||
final LoggerService _logger;
|
||||
bool hasChild = false;
|
||||
List<String> hookLog;
|
||||
|
||||
String heroName = 'Windstorm';
|
||||
LoggerService _logger;
|
||||
|
||||
PeekABooParentComponent(this._logger) {
|
||||
hookLog = _logger.logs;
|
||||
}
|
||||
PeekABooParentComponent(this._logger);
|
||||
|
||||
List<String> get logs => _logger.logs;
|
||||
|
||||
toggleChild() {
|
||||
hasChild = !hasChild;
|
||||
|
|
|
@ -6,48 +6,35 @@ 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="let hero of heroes" mySpy class="heroes">
|
||||
{{hero}}
|
||||
</div>
|
||||
|
||||
<h4>-- Spy Lifecycle Hook Log --</h4>
|
||||
<div *ngFor="let msg of spyLog">{{msg}}</div>
|
||||
</div>
|
||||
''',
|
||||
templateUrl: 'spy_component.html',
|
||||
styles: const [
|
||||
'.parent {background: khaki; padding: 10px; margin:100px 8px}',
|
||||
'.parent {background: khaki}',
|
||||
'.heroes {background: LightYellow; padding: 0 8px}'
|
||||
],
|
||||
directives: const [Spy],
|
||||
providers: const [LoggerService])
|
||||
class SpyParentComponent {
|
||||
final LoggerService _logger;
|
||||
String newName = 'Herbie';
|
||||
List<String> heroes = ['Windstorm', 'Magneta'];
|
||||
List<String> spyLog;
|
||||
LoggerService _logger;
|
||||
|
||||
SpyParentComponent(this._logger) {
|
||||
spyLog = _logger.logs;
|
||||
}
|
||||
SpyParentComponent(this._logger);
|
||||
|
||||
List<String> get logs => _logger.logs;
|
||||
|
||||
addHero() {
|
||||
if (newName.trim().isNotEmpty) {
|
||||
heroes.add(newName.trim());
|
||||
newName = '';
|
||||
_logger.tick();
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
// removeHero(String hero) { } is not used.
|
||||
|
||||
void reset() {
|
||||
_logger.log('-- reset --');
|
||||
heroes.clear();
|
||||
_logger.tick();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<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>
|
||||
<!-- #docregion template -->
|
||||
<div *ngFor="let hero of heroes" mySpy class="heroes">
|
||||
{{hero}}
|
||||
</div>
|
||||
<!-- #enddocregion template -->
|
||||
<h4>-- Spy Lifecycle Hook Log --</h4>
|
||||
<div *ngFor="let msg of logs">{{msg}}</div>
|
||||
</div>
|
|
@ -3,14 +3,14 @@ import 'package:angular2/core.dart';
|
|||
|
||||
import 'logger_service.dart';
|
||||
|
||||
int nextId = 1;
|
||||
int _nextId = 1;
|
||||
|
||||
// #docregion spy-directive
|
||||
// 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;
|
||||
final LoggerService _logger;
|
||||
|
||||
Spy(this._logger);
|
||||
|
||||
|
@ -18,5 +18,6 @@ class Spy implements OnInit, OnDestroy {
|
|||
|
||||
ngOnDestroy() => _logIt('onDestroy');
|
||||
|
||||
_logIt(String msg) => _logger.log('Spy #$_id $msg');
|
||||
_logIt(String msg) => _logger.log('Spy #${_nextId++} $msg');
|
||||
}
|
||||
// #enddocregion spy-directive
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
|
||||
<head>
|
||||
<title>Angular 2 Lifecycle Hooks</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="sample.css">
|
||||
|
||||
<script defer src="main.dart" type="application/dart"></script>
|
||||
<script defer src="packages/browser/dart.js"></script>
|
||||
</head>
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
.parent {
|
||||
color: #666;
|
||||
margin: 14px 0;
|
||||
padding: 8px;
|
||||
}
|
||||
input {
|
||||
margin: 4px;
|
||||
padding: 4px;
|
||||
}
|
||||
.comment {
|
||||
color: red;
|
||||
font-style: italic;
|
||||
}
|
|
@ -43,13 +43,13 @@ export class AfterContentComponent implements AfterContentChecked, AfterContentI
|
|||
|
||||
// #docregion hooks
|
||||
ngAfterContentInit() {
|
||||
// viewChild is set after the view has been initialized
|
||||
// contentChild is set after the content has been initialized
|
||||
this.logIt('AfterContentInit');
|
||||
this.doSomething();
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
// viewChild is updated after the view has been checked
|
||||
// contentChild is updated after the content has been checked
|
||||
if (this.prevHero === this.contentChild.hero) {
|
||||
this.logIt('AfterContentChecked (no change)');
|
||||
} else {
|
||||
|
@ -59,8 +59,6 @@ export class AfterContentComponent implements AfterContentChecked, AfterContentI
|
|||
}
|
||||
}
|
||||
// #enddocregion hooks
|
||||
|
||||
|
||||
// #docregion do-something
|
||||
|
||||
// This surrogate for real business logic sets the `comment`
|
||||
|
@ -69,8 +67,8 @@ export class AfterContentComponent implements AfterContentChecked, AfterContentI
|
|||
}
|
||||
|
||||
private logIt(method: string) {
|
||||
let vc = this.contentChild;
|
||||
let message = `${method}: ${vc ? vc.hero : 'no'} child view`;
|
||||
let child = this.contentChild;
|
||||
let message = `${method}: ${child ? child.hero : 'no'} child content`;
|
||||
this.logger.log(message);
|
||||
}
|
||||
// #docregion hooks
|
||||
|
@ -85,7 +83,7 @@ export class AfterContentComponent implements AfterContentChecked, AfterContentI
|
|||
<div class="parent">
|
||||
<h2>AfterContent</h2>
|
||||
|
||||
<div *ngIf="show">` +
|
||||
<div *ngIf="show">` +
|
||||
// #docregion parent-template
|
||||
`<after-content>
|
||||
<my-child></my-child>
|
||||
|
@ -106,7 +104,7 @@ export class AfterContentParentComponent {
|
|||
logs: string[];
|
||||
show = true;
|
||||
|
||||
constructor(logger: LoggerService) {
|
||||
constructor(private logger: LoggerService) {
|
||||
this.logs = logger.logs;
|
||||
}
|
||||
|
||||
|
@ -114,6 +112,6 @@ export class AfterContentParentComponent {
|
|||
this.logs.length = 0;
|
||||
// quickly remove and reload AfterContentComponent which recreates it
|
||||
this.show = false;
|
||||
setTimeout(() => this.show = true, 0);
|
||||
this.logger.tick_then(() => this.show = true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ export class ChildViewComponent {
|
|||
{{comment}}
|
||||
</p>
|
||||
`,
|
||||
|
||||
directives: [ChildViewComponent]
|
||||
})
|
||||
// #docregion hooks
|
||||
|
@ -71,14 +70,14 @@ export class AfterViewComponent implements AfterViewChecked, AfterViewInit {
|
|||
let c = this.viewChild.hero.length > 10 ? "That's a long name" : '';
|
||||
if (c !== this.comment) {
|
||||
// Wait a tick because the component's view has already been checked
|
||||
setTimeout(() => this.comment = c, 0);
|
||||
this.logger.tick_then(() => this.comment = c);
|
||||
}
|
||||
}
|
||||
// #enddocregion do-something
|
||||
|
||||
private logIt(method:string){
|
||||
let vc = this.viewChild;
|
||||
let message = `${method}: ${vc ? vc.hero:'no'} child view`
|
||||
let child = this.viewChild;
|
||||
let message = `${method}: ${child ? child.hero:'no'} child view`
|
||||
this.logger.log(message);
|
||||
}
|
||||
// #docregion hooks
|
||||
|
@ -108,7 +107,7 @@ export class AfterViewParentComponent {
|
|||
logs:string[];
|
||||
show = true;
|
||||
|
||||
constructor(logger:LoggerService){
|
||||
constructor(private logger: LoggerService) {
|
||||
this.logs = logger.logs;
|
||||
}
|
||||
|
||||
|
@ -116,6 +115,6 @@ export class AfterViewParentComponent {
|
|||
this.logs.length=0;
|
||||
// quickly remove and reload AfterViewComponent which recreates it
|
||||
this.show = false;
|
||||
setTimeout(() => this.show = true, 0)
|
||||
this.logger.tick_then(() => this.show = true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,9 +33,9 @@ export class MyCounter implements OnChanges {
|
|||
}
|
||||
|
||||
// A change to `counter` is the only change we care about
|
||||
let prop = changes['counter'];
|
||||
let cur = prop.currentValue;
|
||||
let prev = JSON.stringify(prop.previousValue); // first time is {}; after is integer
|
||||
let chng = changes['counter'];
|
||||
let cur = chng.currentValue;
|
||||
let prev = JSON.stringify(chng.previousValue); // first time is {}; after is integer
|
||||
this.changeLog.push(`counter: currentValue = ${cur}, previousValue = ${prev}`);
|
||||
}
|
||||
|
||||
|
|
|
@ -69,9 +69,9 @@ export class DoCheckComponent implements DoCheck, OnChanges {
|
|||
// Copied from OnChangesComponent
|
||||
ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
|
||||
for (let propName in changes) {
|
||||
let prop = changes[propName];
|
||||
let cur = JSON.stringify(prop.currentValue);
|
||||
let prev = JSON.stringify(prop.previousValue);
|
||||
let chng = changes[propName];
|
||||
let cur = JSON.stringify(chng.currentValue);
|
||||
let prev = JSON.stringify(chng.previousValue);
|
||||
this.changeLog.push(`OnChanges: ${propName}: currentValue = ${cur}, previousValue = ${prev}`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,9 +21,6 @@ export class LoggerService {
|
|||
clear() { this.logs.length = 0; }
|
||||
|
||||
// schedules a view refresh to ensure display catches up
|
||||
tick() {
|
||||
setTimeout(() => {
|
||||
// console.log('tick')
|
||||
}, 0);
|
||||
}
|
||||
tick() { this.tick_then(() => { }); }
|
||||
tick_then(fn: () => any) { setTimeout(fn, 0); }
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* tslint:disable:forin */
|
||||
// #docregion
|
||||
import {
|
||||
Component, Input, Onchanges,
|
||||
Component, Input, OnChanges,
|
||||
SimpleChange, ViewChild
|
||||
} from '@angular/core';
|
||||
|
||||
|
@ -36,9 +36,9 @@ export class OnChangesComponent implements OnChanges {
|
|||
// #docregion ng-on-changes
|
||||
ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
|
||||
for (let propName in changes) {
|
||||
let prop = changes[propName];
|
||||
let cur = JSON.stringify(prop.currentValue);
|
||||
let prev = JSON.stringify(prop.previousValue);
|
||||
let chng = changes[propName];
|
||||
let cur = JSON.stringify(chng.currentValue);
|
||||
let prev = JSON.stringify(chng.previousValue);
|
||||
this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<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>
|
||||
<!-- #docregion template -->
|
||||
<div *ngFor="let hero of heroes" mySpy class="heroes">
|
||||
{{hero}}
|
||||
</div>
|
||||
<!-- #enddocregion template -->
|
||||
<h4>-- Spy Lifecycle Hook Log --</h4>
|
||||
<div *ngFor="let msg of spyLog">{{msg}}</div>
|
||||
</div>
|
|
@ -6,25 +6,7 @@ import { Spy } from './spy.directive';
|
|||
|
||||
@Component({
|
||||
selector: 'spy-parent',
|
||||
template: `
|
||||
<div class="parent">
|
||||
<h2>Spy Directive</h2>
|
||||
<p>
|
||||
<input [(ngModel)]="newName"
|
||||
(keyup.enter)="addHero()"
|
||||
placeholder="Hero name">
|
||||
<button (click)="addHero()">Add Hero</button>
|
||||
<button (click)="reset()">Reset Heroes</button>
|
||||
</p>` +
|
||||
// #docregion template
|
||||
`<div *ngFor="let hero of heroes" mySpy class="heroes">
|
||||
{{hero}}
|
||||
</div>`
|
||||
// #enddocregion template
|
||||
+ `<h4>-- Spy Lifecycle Hook Log --</h4>
|
||||
<div *ngFor="let msg of spyLog">{{msg}}</div>
|
||||
</div>
|
||||
`,
|
||||
templateUrl: 'app/spy.component.html',
|
||||
styles: [
|
||||
'.parent {background: khaki;}',
|
||||
'.heroes {background: LightYellow; padding: 0 8px}'
|
||||
|
|
|
@ -1,9 +1,24 @@
|
|||
include ../../../_includes/_util-fns
|
||||
|
||||
mixin liveExLinks(name)
|
||||
:marked
|
||||
[Run the live example](https://angular-examples.github.io/#{name}) |
|
||||
[View its source code](https://github.com/angular-examples/#{name})
|
||||
- var _docsFor = 'dart';
|
||||
|
||||
mixin privateVar(varName)
|
||||
| _#{varName}
|
||||
|
||||
mixin liveExampleLink(linkText, exampleUrlPartName)
|
||||
a(href='https://angular-examples.github.io/#{exampleUrlPartName}')= linkText
|
||||
|
||||
mixin liveExampleLink2(linkText, exampleUrlPartName)
|
||||
- var liveExampleSourceLinkText = attributes.srcLinkText || 'view source'
|
||||
span.
|
||||
#[+liveExampleLink(linkText, exampleUrlPartName)]
|
||||
(#[a(href='https://github.com/angular-examples/#{exampleUrlPartName}') #{liveExampleSourceLinkText}])
|
||||
|
||||
//- Deprecated
|
||||
mixin liveExLinks(exampleUrlPartName)
|
||||
p.
|
||||
#[+liveExampleLink('Run the live example', exampleUrlPartName)] |
|
||||
#[a(href='https://github.com/angular-examples/#{exampleUrlPartName}') View its source code]
|
||||
|
||||
- var adjustExamplePath = function(_path) {
|
||||
- if(!_path) return _path;
|
||||
|
@ -15,7 +30,7 @@ mixin liveExLinks(name)
|
|||
- var baseNameNoExt = baseName.substr(0,baseName.length - (extn.length + 1));
|
||||
- var inWebFolder = baseNameNoExt.match(/^(main|index)$/);
|
||||
- // Adjust the folder path, e.g., ts -> dart
|
||||
- folder = folder.replace(/(^|\/)ts\//, '$1dart/').replace(/(^|\/)app$/, inWebFolder ? '$1web' : '$1lib');
|
||||
- folder = folder.replace(/(^|\/)ts\//, '$1dart/').replace(/(^|\/)app($|\/)/, inWebFolder ? '$1web$2' : '$1lib$2');
|
||||
- // In file name, replace special characters with underscore
|
||||
- baseNameNoExt = baseNameNoExt.replace(/[\-\.]/g, '_');
|
||||
- // Adjust the file extension
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
include ../_util-fns
|
||||
extends ../../../ts/latest/guide/lifecycle-hooks.jade
|
||||
|
||||
:marked
|
||||
We're working on the Dart version of this chapter.
|
||||
In the meantime, please see these resources:
|
||||
block includes
|
||||
include ../_util-fns
|
||||
|
||||
* [Lifecycle Hooks](/docs/ts/latest/guide/lifecycle-hooks.html):
|
||||
The TypeScript version of this chapter
|
||||
|
||||
* [Dart source code](https://github.com/angular/angular.io/tree/master/public/docs/_examples/lifecycle-hooks/dart):
|
||||
A preliminary version of the example code that will appear in this chapter
|
||||
block optional-interfaces
|
||||
//- n/a for Dart
|
||||
|
||||
block tick-methods
|
||||
:marked
|
||||
The `LoggerService.tick` method, which returns a `Future`, postpones the update one turn of the of the browser's update cycle ... and that's long enough.
|
||||
|
|
|
@ -1 +1,14 @@
|
|||
include ../../../_includes/_util-fns
|
||||
include ../../../_includes/_util-fns
|
||||
|
||||
- var docsFor = 'ts';
|
||||
|
||||
mixin privateVar(varName)
|
||||
| #{varName}
|
||||
|
||||
mixin liveExampleLink(linkText, exampleUrlPartName)
|
||||
a(href='/resources/live-examples/#{exampleUrlPartName}/ts/plnkr.html')= linkText
|
||||
|
||||
mixin liveExampleLink2(linkText, exampleUrlPartName)
|
||||
//- In Dart this also gives a link to the source.
|
||||
span.
|
||||
#[+liveExampleLink(linkText, exampleUrlPartName)]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
include ../_util-fns
|
||||
block includes
|
||||
include ../_util-fns
|
||||
|
||||
- var top="vertical-align:top"
|
||||
|
||||
|
@ -22,14 +23,10 @@ include ../_util-fns
|
|||
* [DoCheck](#docheck)
|
||||
* [AfterViewInit and AfterViewChecked](#afterview)
|
||||
* [AfterContentInit and AfterContentChecked](#aftercontent)
|
||||
|
||||
Try the [Live Example](/resources/live-examples/lifecycle-hooks/ts/plnkr.html)
|
||||
|
||||
<!--
|
||||
https://github.com/angular/angular/blob/master/modules/angular2/src/core/linker/interfaces.ts
|
||||
-->
|
||||
p Try the #[+liveExampleLink2('live example', 'lifecycle-hooks')].
|
||||
|
||||
a(id="hooks-overview")
|
||||
a#hooks-overview
|
||||
.l-main-section
|
||||
:marked
|
||||
## Component lifecycle Hooks
|
||||
|
@ -37,7 +34,7 @@ a(id="hooks-overview")
|
|||
as Angular creates, updates, and destroys them.
|
||||
|
||||
Developers can tap into key moments in that lifecycle by implementing
|
||||
one or more of the *Lifecycle Hook* interfaces in the `angular2/core` library.
|
||||
one or more of the *Lifecycle Hook* interfaces in the Angular `core` library.
|
||||
|
||||
Each interface has a single hook method whose name is the interface name prefixed with `ng`.
|
||||
For example, the `OnInit` interface has a hook method named `ngOnInit`.
|
||||
|
@ -45,24 +42,23 @@ a(id="hooks-overview")
|
|||
+makeExample('lifecycle-hooks/ts/app/peek-a-boo.component.ts', 'ngOnInit', 'peek-a-boo.component.ts (excerpt)')(format='.')
|
||||
:marked
|
||||
No directive or component will implement all of them and some of the hooks only make sense for components.
|
||||
|
||||
Angular only calls a directive/component hook method *if it is defined*.
|
||||
block optional-interfaces
|
||||
.l-sub-section
|
||||
:marked
|
||||
### Interface optional?
|
||||
The interfaces are optional for JavaScript and Typescript developers from a purely technical perspective.
|
||||
The JavaScript language doesn't have interfaces.
|
||||
Angular can't see TypeScript interfaces at runtime because they disappear from the transpiled JavaScript.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
### Interface optional?
|
||||
The interfaces are optional for JavaScript and Typescript developers from a purely technical perspective.
|
||||
The JavaScript language doesn't have interfaces.
|
||||
Angular can't see TypeScript interfaces at runtime because they disappear from the transpiled JavaScript.
|
||||
Fortunately, they aren't necessary.
|
||||
We don't have to add the lifecycle hook interfaces to our directives and components to benefit from the hooks themselves.
|
||||
|
||||
Fortunately, they aren't necessary.
|
||||
We don't have to add the lifecycle hook interfaces to our directives and components to benefit from the hooks themselves.
|
||||
Angular instead inspects our directive and component classes and calls the hook methods *if they are defined*.
|
||||
Angular will find and call methods like `ngOnInit()`, with or without the interfaces.
|
||||
|
||||
Angular instead inspects our directive and component classes and calls the hook methods *if they are defined*.
|
||||
Angular will find and call methods like `ngOnInit()`, with or without the interfaces.
|
||||
|
||||
Nonetheless, we strongly recommend adding interfaces to TypeScript directive classes
|
||||
in order to benefit from strong typing and editor tooling.
|
||||
Nonetheless, we strongly recommend adding interfaces to TypeScript directive classes
|
||||
in order to benefit from strong typing and editor tooling.
|
||||
|
||||
:marked
|
||||
Here are the component lifecycle hook methods:
|
||||
|
@ -189,7 +185,7 @@ a(id="other-lifecycles")
|
|||
:marked
|
||||
## Other lifecycle hooks
|
||||
|
||||
Other Angular sub-system may have their own lifecycle hooks apart from the component hooks we've listed.
|
||||
Other Angular sub-systems may have their own lifecycle hooks apart from the component hooks we've listed.
|
||||
The router, for instance, also has it's own [router lifecycle hooks](router.html#router-lifecycle-hooks)
|
||||
that allow us to tap into specific moments in route navigation.
|
||||
|
||||
|
@ -199,15 +195,14 @@ a(id="other-lifecycles")
|
|||
3rd party libraries might implement their hooks as well in order to give us, the developers, more
|
||||
control over how these libraries are used.
|
||||
|
||||
a(id="the-sample")
|
||||
a#the-sample
|
||||
.l-main-section
|
||||
:marked
|
||||
## Lifecycle exercises
|
||||
|
||||
The [live example](/resources/live-examples/lifecycle-hooks/ts/plnkr.html)
|
||||
h2 Lifecycle exercises
|
||||
p.
|
||||
The #[+liveExampleLink('live example', 'lifecycle-hooks')]
|
||||
demonstrates the lifecycle hooks in action through a series of exercises
|
||||
presented as components under the control of the root `AppComponent`.
|
||||
|
||||
:marked
|
||||
They follow a common pattern: a *parent* component serves as a test rig for
|
||||
a *child* component that illustrates one or more of the lifecycle hook methods.
|
||||
|
||||
|
@ -246,7 +241,7 @@ table(width="100%")
|
|||
td <a href="#docheck">DoCheck</a>
|
||||
td
|
||||
:marked
|
||||
Implements a `ngDoCheck` method with custom change detection.
|
||||
Implements an `ngDoCheck` method with custom change detection.
|
||||
See how often Angular calls this hook and watch it post changes to a log.
|
||||
tr(style=top)
|
||||
td <a href="#afterview">AfterView</a>
|
||||
|
@ -299,7 +294,7 @@ figure.image-display
|
|||
We log in it to confirm that input properties (the `name` property in this case) have no assigned values at construction.
|
||||
:marked
|
||||
Had we clicked the *Update Hero* button, we'd have seen another `OnChanges` and two more triplets of
|
||||
`DoCheck, `AfterContentChecked` and `AfterViewChecked`.
|
||||
`DoCheck`, `AfterContentChecked` and `AfterViewChecked`.
|
||||
Clearly these three hooks fire a *lot* and we must keep the logic we put in these hooks
|
||||
as lean as possible!
|
||||
|
||||
|
@ -338,7 +333,7 @@ figure.image-display
|
|||
We can apply the spy to any native or component element and it'll be initialized and destroyed
|
||||
at the same time as that element.
|
||||
Here we attach it to the repeated hero `<div>`
|
||||
+makeExample('lifecycle-hooks/ts/app/spy.component.ts', 'template')(format=".")
|
||||
+makeExample('lifecycle-hooks/ts/app/spy.component.html', 'template')(format=".")
|
||||
|
||||
:marked
|
||||
Each spy's birth and death marks the birth and death of the attached hero `<div>`
|
||||
|
@ -457,7 +452,7 @@ figure.image-display
|
|||
|
||||
The `ngDoCheck` hook is called with enormous frequency —
|
||||
after _every_ change detection cycle no matter where the change occurred.
|
||||
It's called over twenty time in this example before the user can do anything.
|
||||
It's called over twenty times in this example before the user can do anything.
|
||||
|
||||
Most of these initial checks are triggered by Angular's first rendering of *unrelated data elsewhere on the page*.
|
||||
Mere mousing into another input box triggers a call.
|
||||
|
@ -466,14 +461,14 @@ figure.image-display
|
|||
|
||||
.l-sub-section
|
||||
:marked
|
||||
We see also that the `ngOnChanges` method is called in contradiction of the
|
||||
We also see that the `ngOnChanges` method is called in contradiction of the
|
||||
[incorrect API documentation](../api/core/DoCheck-interface.html).
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## AfterView
|
||||
The *AfterView* sample explores the `AfterViewInit` and `AfterViewChecked` hooks that Angular calls
|
||||
*after* Angular creates a component's child views.
|
||||
*after* it creates a component's child views.
|
||||
|
||||
Here's a child view that displays a hero's name in an input box:
|
||||
+makeExample('lifecycle-hooks/ts/app/after-view.component.ts', 'child-view', 'ChildComponent')(format=".")
|
||||
|
@ -493,15 +488,18 @@ figure.image-display
|
|||
|
||||
+makeExample('lifecycle-hooks/ts/app/after-view.component.ts', 'do-something', 'AfterViewComponent (doSomething)')(format=".")
|
||||
:marked
|
||||
Why does the `doSomething` method waits a tick w/ `setTimeout` before updating `comment`?
|
||||
Why does the `doSomething` method wait a tick before updating `comment`?
|
||||
|
||||
We must adhere to Angular's unidirectional data flow rule which says that
|
||||
Because we must adhere to Angular's unidirectional data flow rule which says that
|
||||
we may not update the view *after* it has been composed.
|
||||
Both hooks fire after the component's view has been composed.
|
||||
|
||||
Angular throws an error if we update component's data-bound `comment` property immediately (try it!).
|
||||
The `setTimeout` postpones the update one turn of the of the browser's JavaScript cycle ... and that's long enough.
|
||||
block tick-methods
|
||||
:marked
|
||||
The `LoggerService.tick` methods, which are implemented by a call to `setTimeout`, postpone the update one turn of the of the browser's JavaScript cycle ... and that's long enough.
|
||||
|
||||
:marked
|
||||
Here's *AfterView* in action
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/lifecycle-hooks/after-view-anim.gif' alt="AfterView")
|
||||
|
@ -531,23 +529,23 @@ figure.image-display
|
|||
the `AfterContentComponent`'s parent. Here's the parent's template.
|
||||
+makeExample('lifecycle-hooks/ts/app/after-content.component.ts', 'parent-template', 'AfterContentParentComponent (template excerpt)')(format=".")
|
||||
:marked
|
||||
Notice that the `<child-view>` tag is tucked between the `<after-content>` tags.
|
||||
Notice that the `<my-child>` tag is tucked between the `<after-content>` tags.
|
||||
We never put content between a component's element tags *unless we intend to project that content
|
||||
into the component*.
|
||||
|
||||
Now look at the component's template:
|
||||
+makeExample('lifecycle-hooks/ts/app/after-content.component.ts', 'template', 'AfterContentComponent (template)')(format=".")
|
||||
:marked
|
||||
The `<ngContent>` tags are the *placeholder* for the external content.
|
||||
The `<ng-content>` tag is a *placeholder* for the external content.
|
||||
They tell Angular where to insert that content.
|
||||
In this case, the projected content is the `<child-view>` from the parent.
|
||||
In this case, the projected content is the `<my-child>` from the parent.
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/lifecycle-hooks/projected-child-view.png' width="230" alt="Projected Content")
|
||||
:marked
|
||||
.l-sub-section
|
||||
:marked
|
||||
The tell-tale signs of *content projection* are (a) HTML between component element tags
|
||||
and (b) the presence of `<ngContent>` tags in the component's template.
|
||||
and (b) the presence of `<ng-content>` tags in the component's template.
|
||||
:marked
|
||||
### AfterContent hooks
|
||||
*AfterContent* hooks are similar to the *AfterView* hooks. The key difference is the kind of child component
|
||||
|
|
Loading…
Reference in New Issue