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
|
//- 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)
|
mixin includeShared(filePath, region)
|
||||||
- var newPath = translatePath(filePath, region);
|
- var newPath = translatePath(filePath, region);
|
||||||
!=partial(newPath)
|
!=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
|
// #docregion
|
||||||
import 'package:angular2/core.dart';
|
import 'package:angular2/core.dart';
|
||||||
|
|
||||||
import 'child_component.dart';
|
|
||||||
import 'logger_service.dart';
|
import 'logger_service.dart';
|
||||||
|
|
||||||
|
//////////////////
|
||||||
|
// #docregion child-view
|
||||||
@Component(
|
@Component(
|
||||||
selector: 'after-view-parent',
|
selector: 'my-child',
|
||||||
template: '''
|
template: '<input [(ngModel)]="hero">')
|
||||||
<div class="parent">
|
class ChildViewComponent {
|
||||||
<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;
|
|
||||||
String hero = 'Magneta';
|
String hero = 'Magneta';
|
||||||
bool showChild = true;
|
}
|
||||||
|
// #enddocregion child-view
|
||||||
|
|
||||||
// Query for a CONTENT child of type `ChildComponent`
|
//////////////////////
|
||||||
// No such CONTENT child exists!
|
@Component(
|
||||||
// This component holds a view but no content of that type.
|
selector: 'after-view',
|
||||||
@ContentChild(ChildComponent)
|
// #docregion template
|
||||||
ChildComponent contentChild;
|
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`
|
AfterViewComponent(this._logger) {
|
||||||
@ViewChild(ChildComponent)
|
_logIt('AfterView constructor');
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #docregion hooks
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
// viewChild is set after the view has been initialized
|
// viewChild is set after the view has been initialized
|
||||||
_logger.log('AfterViewInit: $message');
|
_logIt('AfterViewInit');
|
||||||
|
_doSomething();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewChecked() {
|
ngAfterViewChecked() {
|
||||||
// viewChild is updated after the view has been checked
|
// viewChild is updated after the view has been checked
|
||||||
// Called frequently; only report when the hero changes
|
if (_prevHero == viewChild.hero) {
|
||||||
if (!_hasViewChild || _prevHero == viewChild.hero) return;
|
_logIt('AfterViewChecked (no change)');
|
||||||
_prevHero = viewChild.hero;
|
} else {
|
||||||
_logger.log('AfterViewChecked: $message');
|
_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
|
// #docregion
|
||||||
import 'package:angular2/core.dart';
|
import 'package:angular2/core.dart';
|
||||||
|
|
||||||
import 'after_content_parent.dart';
|
import 'after_content_component.dart';
|
||||||
import 'after_view_component.dart';
|
import 'after_view_component.dart';
|
||||||
import 'counter_component.dart';
|
import 'counter_component.dart';
|
||||||
|
import 'do_check_component.dart';
|
||||||
import 'on_changes_component.dart';
|
import 'on_changes_component.dart';
|
||||||
import 'peek_a_boo_parent_component.dart';
|
import 'peek_a_boo_parent_component.dart';
|
||||||
import 'spy_component.dart';
|
import 'spy_component.dart';
|
||||||
|
|
||||||
@Component(
|
@Component(
|
||||||
selector: 'my-app',
|
selector: 'my-app',
|
||||||
template: '''
|
templateUrl: 'app_component.html',
|
||||||
<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 [
|
directives: const [
|
||||||
PeekABooParentComponent,
|
|
||||||
OnChangesParentComponent,
|
|
||||||
AfterViewParentComponent,
|
|
||||||
AfterContentParentComponent,
|
AfterContentParentComponent,
|
||||||
|
AfterViewParentComponent,
|
||||||
|
CounterParentComponent,
|
||||||
|
DoCheckParentComponent,
|
||||||
|
OnChangesParentComponent,
|
||||||
|
PeekABooParentComponent,
|
||||||
SpyParentComponent,
|
SpyParentComponent,
|
||||||
CounterParentComponent
|
|
||||||
])
|
])
|
||||||
class AppComponent {}
|
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
|
// A change to `counter` is the only change we care about
|
||||||
SimpleChange prop = changes['counter'];
|
SimpleChange chng = changes['counter'];
|
||||||
var prev = prop.isFirstChange() ? "{}" : prop.previousValue;
|
var cur = chng.currentValue;
|
||||||
changeLog.add(
|
var prev = chng.isFirstChange() ? "{}" : chng.previousValue;
|
||||||
'counter: currentValue = ${prop.currentValue}, previousValue = $prev');
|
changeLog.add('counter: currentValue = $cur, previousValue = $prev');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,29 +49,30 @@ class MyCounter implements OnChanges {
|
|||||||
<my-counter [counter]="value"></my-counter>
|
<my-counter [counter]="value"></my-counter>
|
||||||
|
|
||||||
<h4>-- Spy Lifecycle Hook Log --</h4>
|
<h4>-- Spy Lifecycle Hook Log --</h4>
|
||||||
<div *ngFor="let msg of spyLog">{{msg}}</div>
|
<div *ngFor="let msg of logs">{{msg}}</div>
|
||||||
</div>
|
</div>
|
||||||
''',
|
''',
|
||||||
styles: const [
|
styles: const ['.parent {background: gold;}'],
|
||||||
'.parent {background: gold; padding: 10px; margin:100px 8px;}'
|
|
||||||
],
|
|
||||||
directives: const [MyCounter],
|
directives: const [MyCounter],
|
||||||
providers: const [LoggerService])
|
providers: const [LoggerService])
|
||||||
class CounterParentComponent {
|
class CounterParentComponent {
|
||||||
|
final LoggerService _logger;
|
||||||
num value;
|
num value;
|
||||||
List<String> spyLog = [];
|
|
||||||
|
|
||||||
LoggerService _logger;
|
|
||||||
|
|
||||||
CounterParentComponent(this._logger) {
|
CounterParentComponent(this._logger) {
|
||||||
spyLog = _logger.logs;
|
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCounter() => value += 1;
|
List<String> get logs => _logger.logs;
|
||||||
|
|
||||||
reset() {
|
void updateCounter() {
|
||||||
|
value += 1;
|
||||||
|
_logger.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
_logger.log('-- reset --');
|
_logger.log('-- reset --');
|
||||||
value = 0;
|
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()
|
@Injectable()
|
||||||
class LoggerService {
|
class LoggerService {
|
||||||
List<String> logs = [];
|
List<String> logs = [];
|
||||||
|
String _prevMsg = '';
|
||||||
|
int _prevMsgCount = 1;
|
||||||
|
|
||||||
log(String msg, [bool noTick = false]) {
|
void log(String msg) {
|
||||||
if (!noTick) {
|
if (msg == _prevMsg) {
|
||||||
tick();
|
// 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(() {});
|
tick() => new Future(() {});
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,11 @@ import 'package:angular2/core.dart';
|
|||||||
class Hero {
|
class Hero {
|
||||||
String name;
|
String name;
|
||||||
Hero(this.name);
|
Hero(this.name);
|
||||||
|
Map<String, dynamic> toJson() => {'name': name};
|
||||||
Map toJson() => {'name': name};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component(
|
@Component(
|
||||||
selector: 'my-hero',
|
selector: 'on-changes',
|
||||||
template: '''
|
template: '''
|
||||||
<div class="hero">
|
<div class="hero">
|
||||||
<p>{{hero.name}} can {{power}}</p>
|
<p>{{hero.name}} can {{power}}</p>
|
||||||
@ -24,58 +23,48 @@ class Hero {
|
|||||||
'.hero {background: LightYellow; padding: 8px; margin-top: 8px}',
|
'.hero {background: LightYellow; padding: 8px; margin-top: 8px}',
|
||||||
'p {background: Yellow; 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() Hero hero;
|
||||||
@Input() String power;
|
@Input() String power;
|
||||||
@Input() bool reset;
|
// #enddocregion inputs
|
||||||
|
|
||||||
List<String> changeLog = [];
|
List<String> changeLog = [];
|
||||||
|
|
||||||
|
// #docregion ng-on-changes
|
||||||
ngOnChanges(Map<String, SimpleChange> changes) {
|
ngOnChanges(Map<String, SimpleChange> changes) {
|
||||||
// Empty the changeLog whenever 'reset' property changes
|
changes.forEach((String propName, SimpleChange change) {
|
||||||
// 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 cur = JSON.encode(change.currentValue);
|
||||||
String prev =
|
String prev =
|
||||||
change.isFirstChange() ? "{}" : JSON.encode(change.previousValue);
|
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(
|
@Component(
|
||||||
selector: 'on-changes-parent',
|
selector: 'on-changes-parent',
|
||||||
template: '''
|
templateUrl: 'on_changes_parent_component.html',
|
||||||
<div class="parent">
|
styles: const ['.parent {background: Lavender}'],
|
||||||
<h2>OnChanges</h2>
|
directives: const [OnChangesComponent])
|
||||||
|
|
||||||
<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 {
|
class OnChangesParentComponent {
|
||||||
Hero hero;
|
Hero hero;
|
||||||
String power;
|
String power;
|
||||||
bool resetTrigger = false;
|
String title = 'OnChanges';
|
||||||
|
@ViewChild(OnChangesComponent) OnChangesComponent childView;
|
||||||
|
|
||||||
OnChangesParentComponent() {
|
OnChangesParentComponent() {
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
void reset() {
|
||||||
// new Hero object every time; triggers onChange
|
// new Hero object every time; triggers onChange
|
||||||
hero = new Hero('Windstorm');
|
hero = new Hero('Windstorm');
|
||||||
// setting power only triggers onChange if this value is different
|
// setting power only triggers onChange if this value is different
|
||||||
power = 'sing';
|
power = 'sing';
|
||||||
// always triggers onChange ... which is interpreted as a reset
|
childView?.reset();
|
||||||
resetTrigger = !resetTrigger;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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';
|
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(
|
@Component(
|
||||||
selector: 'peek-a-boo',
|
selector: 'peek-a-boo',
|
||||||
template: '<p>Now you see my hero, {{name}}</p>',
|
template: '<p>Now you see my hero, {{name}}</p>',
|
||||||
styles: const ['p {background: LightYellow; padding: 8px}'])
|
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
|
implements
|
||||||
OnChanges,
|
OnChanges,
|
||||||
OnInit,
|
OnInit,
|
||||||
|
DoCheck,
|
||||||
AfterContentInit,
|
AfterContentInit,
|
||||||
AfterContentChecked,
|
AfterContentChecked,
|
||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
@ -23,12 +44,13 @@ class PeekABooComponent
|
|||||||
|
|
||||||
int _afterContentCheckedCounter = 1;
|
int _afterContentCheckedCounter = 1;
|
||||||
int _afterViewCheckedCounter = 1;
|
int _afterViewCheckedCounter = 1;
|
||||||
int _id = nextId++;
|
|
||||||
LoggerService _logger;
|
|
||||||
int _onChangesCounter = 1;
|
int _onChangesCounter = 1;
|
||||||
String _verb = 'initialized';
|
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.
|
// Only called if there is an @input variable set by parent.
|
||||||
ngOnChanges(Map<String, SimpleChange> changes) {
|
ngOnChanges(Map<String, SimpleChange> changes) {
|
||||||
@ -38,41 +60,28 @@ class PeekABooComponent
|
|||||||
var name = changes['name'].currentValue;
|
var name = changes['name'].currentValue;
|
||||||
messages.add('name $_verb to "$name"');
|
messages.add('name $_verb to "$name"');
|
||||||
} else {
|
} 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
|
_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!
|
// Beware! Called frequently!
|
||||||
ngAfterContentChecked() {
|
// Called in every change detection cycle anywhere on the page
|
||||||
int counter = _afterContentCheckedCounter++;
|
ngDoCheck() => _logIt('DoCheck');
|
||||||
_logIt('afterContentChecked (${counter})');
|
|
||||||
}
|
|
||||||
|
|
||||||
ngAfterViewInit() => _logIt('afterViewInit');
|
ngAfterContentInit() => _logIt('AfterContentInit');
|
||||||
|
|
||||||
// Called after every change detection check
|
|
||||||
// of the component (directive) VIEW
|
|
||||||
// Beware! Called frequently!
|
// Beware! Called frequently!
|
||||||
ngAfterViewChecked() {
|
// Called in every change detection cycle anywhere on the page
|
||||||
int counter = _afterViewCheckedCounter++;
|
ngAfterContentChecked() { _logIt('AfterContentChecked (${_afterContentCheckedCounter++})'); }
|
||||||
_logIt('afterViewChecked ($counter)');
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy() => _logIt('onDestroy');
|
ngAfterViewInit() => _logIt('AfterViewInit');
|
||||||
|
|
||||||
_logIt(String msg) {
|
// Beware! Called frequently!
|
||||||
// Don't tick or else
|
// Called in every change detection cycle anywhere on the page
|
||||||
// the AfterContentChecked and AfterViewChecked recurse.
|
ngAfterViewChecked() { _logIt('AfterViewChecked (${_afterViewCheckedCounter++})'); }
|
||||||
// Let parent call tick()
|
|
||||||
_logger.log("#$_id $msg", true);
|
ngOnDestroy() => _logIt('OnDestroy');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,24 +19,20 @@ import 'peek_a_boo_component.dart';
|
|||||||
</peek-a-boo>
|
</peek-a-boo>
|
||||||
|
|
||||||
<h4>-- Lifecycle Hook Log --</h4>
|
<h4>-- Lifecycle Hook Log --</h4>
|
||||||
<div *ngFor="let msg of hookLog">{{msg}}</div>
|
<div *ngFor="let msg of logs">{{msg}}</div>
|
||||||
</div>
|
</div>
|
||||||
''',
|
''',
|
||||||
styles: const [
|
styles: const ['.parent {background: moccasin}'],
|
||||||
'.parent {background: moccasin; padding: 10px; margin:100px 8px}'
|
|
||||||
],
|
|
||||||
directives: const [PeekABooComponent],
|
directives: const [PeekABooComponent],
|
||||||
providers: const [LoggerService])
|
providers: const [LoggerService])
|
||||||
class PeekABooParentComponent {
|
class PeekABooParentComponent {
|
||||||
|
final LoggerService _logger;
|
||||||
bool hasChild = false;
|
bool hasChild = false;
|
||||||
List<String> hookLog;
|
|
||||||
|
|
||||||
String heroName = 'Windstorm';
|
String heroName = 'Windstorm';
|
||||||
LoggerService _logger;
|
|
||||||
|
|
||||||
PeekABooParentComponent(this._logger) {
|
PeekABooParentComponent(this._logger);
|
||||||
hookLog = _logger.logs;
|
|
||||||
}
|
List<String> get logs => _logger.logs;
|
||||||
|
|
||||||
toggleChild() {
|
toggleChild() {
|
||||||
hasChild = !hasChild;
|
hasChild = !hasChild;
|
||||||
|
@ -6,48 +6,35 @@ import 'spy_directive.dart';
|
|||||||
|
|
||||||
@Component(
|
@Component(
|
||||||
selector: 'spy-parent',
|
selector: 'spy-parent',
|
||||||
template: '''
|
templateUrl: 'spy_component.html',
|
||||||
<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>
|
|
||||||
''',
|
|
||||||
styles: const [
|
styles: const [
|
||||||
'.parent {background: khaki; padding: 10px; margin:100px 8px}',
|
'.parent {background: khaki}',
|
||||||
'.heroes {background: LightYellow; padding: 0 8px}'
|
'.heroes {background: LightYellow; padding: 0 8px}'
|
||||||
],
|
],
|
||||||
directives: const [Spy],
|
directives: const [Spy],
|
||||||
providers: const [LoggerService])
|
providers: const [LoggerService])
|
||||||
class SpyParentComponent {
|
class SpyParentComponent {
|
||||||
|
final LoggerService _logger;
|
||||||
String newName = 'Herbie';
|
String newName = 'Herbie';
|
||||||
List<String> heroes = ['Windstorm', 'Magneta'];
|
List<String> heroes = ['Windstorm', 'Magneta'];
|
||||||
List<String> spyLog;
|
|
||||||
LoggerService _logger;
|
|
||||||
|
|
||||||
SpyParentComponent(this._logger) {
|
SpyParentComponent(this._logger);
|
||||||
spyLog = _logger.logs;
|
|
||||||
}
|
List<String> get logs => _logger.logs;
|
||||||
|
|
||||||
addHero() {
|
addHero() {
|
||||||
if (newName.trim().isNotEmpty) {
|
if (newName.trim().isNotEmpty) {
|
||||||
heroes.add(newName.trim());
|
heroes.add(newName.trim());
|
||||||
newName = '';
|
newName = '';
|
||||||
|
_logger.tick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
// removeHero(String hero) { } is not used.
|
||||||
|
|
||||||
|
void reset() {
|
||||||
_logger.log('-- reset --');
|
_logger.log('-- reset --');
|
||||||
heroes.clear();
|
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';
|
import 'logger_service.dart';
|
||||||
|
|
||||||
int nextId = 1;
|
int _nextId = 1;
|
||||||
|
|
||||||
|
// #docregion spy-directive
|
||||||
// Spy on any element to which it is applied.
|
// Spy on any element to which it is applied.
|
||||||
// Usage: <div mySpy>...</div>
|
// Usage: <div mySpy>...</div>
|
||||||
@Directive(selector: '[mySpy]')
|
@Directive(selector: '[mySpy]')
|
||||||
class Spy implements OnInit, OnDestroy {
|
class Spy implements OnInit, OnDestroy {
|
||||||
int _id = nextId++;
|
final LoggerService _logger;
|
||||||
LoggerService _logger;
|
|
||||||
|
|
||||||
Spy(this._logger);
|
Spy(this._logger);
|
||||||
|
|
||||||
@ -18,5 +18,6 @@ class Spy implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
ngOnDestroy() => _logIt('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>
|
<head>
|
||||||
<title>Angular 2 Lifecycle Hooks</title>
|
<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="styles.css">
|
||||||
|
<link rel="stylesheet" href="sample.css">
|
||||||
|
|
||||||
<script defer src="main.dart" type="application/dart"></script>
|
<script defer src="main.dart" type="application/dart"></script>
|
||||||
<script defer src="packages/browser/dart.js"></script>
|
<script defer src="packages/browser/dart.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
13
public/docs/_examples/lifecycle-hooks/dart/web/sample.css
Normal file
13
public/docs/_examples/lifecycle-hooks/dart/web/sample.css
Normal file
@ -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
|
// #docregion hooks
|
||||||
ngAfterContentInit() {
|
ngAfterContentInit() {
|
||||||
// viewChild is set after the view has been initialized
|
// contentChild is set after the content has been initialized
|
||||||
this.logIt('AfterContentInit');
|
this.logIt('AfterContentInit');
|
||||||
this.doSomething();
|
this.doSomething();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterContentChecked() {
|
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) {
|
if (this.prevHero === this.contentChild.hero) {
|
||||||
this.logIt('AfterContentChecked (no change)');
|
this.logIt('AfterContentChecked (no change)');
|
||||||
} else {
|
} else {
|
||||||
@ -59,8 +59,6 @@ export class AfterContentComponent implements AfterContentChecked, AfterContentI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// #enddocregion hooks
|
// #enddocregion hooks
|
||||||
|
|
||||||
|
|
||||||
// #docregion do-something
|
// #docregion do-something
|
||||||
|
|
||||||
// This surrogate for real business logic sets the `comment`
|
// This surrogate for real business logic sets the `comment`
|
||||||
@ -69,8 +67,8 @@ export class AfterContentComponent implements AfterContentChecked, AfterContentI
|
|||||||
}
|
}
|
||||||
|
|
||||||
private logIt(method: string) {
|
private logIt(method: string) {
|
||||||
let vc = this.contentChild;
|
let child = this.contentChild;
|
||||||
let message = `${method}: ${vc ? vc.hero : 'no'} child view`;
|
let message = `${method}: ${child ? child.hero : 'no'} child content`;
|
||||||
this.logger.log(message);
|
this.logger.log(message);
|
||||||
}
|
}
|
||||||
// #docregion hooks
|
// #docregion hooks
|
||||||
@ -85,7 +83,7 @@ export class AfterContentComponent implements AfterContentChecked, AfterContentI
|
|||||||
<div class="parent">
|
<div class="parent">
|
||||||
<h2>AfterContent</h2>
|
<h2>AfterContent</h2>
|
||||||
|
|
||||||
<div *ngIf="show">` +
|
<div *ngIf="show">` +
|
||||||
// #docregion parent-template
|
// #docregion parent-template
|
||||||
`<after-content>
|
`<after-content>
|
||||||
<my-child></my-child>
|
<my-child></my-child>
|
||||||
@ -106,7 +104,7 @@ export class AfterContentParentComponent {
|
|||||||
logs: string[];
|
logs: string[];
|
||||||
show = true;
|
show = true;
|
||||||
|
|
||||||
constructor(logger: LoggerService) {
|
constructor(private logger: LoggerService) {
|
||||||
this.logs = logger.logs;
|
this.logs = logger.logs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,6 +112,6 @@ export class AfterContentParentComponent {
|
|||||||
this.logs.length = 0;
|
this.logs.length = 0;
|
||||||
// quickly remove and reload AfterContentComponent which recreates it
|
// quickly remove and reload AfterContentComponent which recreates it
|
||||||
this.show = false;
|
this.show = false;
|
||||||
setTimeout(() => this.show = true, 0);
|
this.logger.tick_then(() => this.show = true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,6 @@ export class ChildViewComponent {
|
|||||||
{{comment}}
|
{{comment}}
|
||||||
</p>
|
</p>
|
||||||
`,
|
`,
|
||||||
|
|
||||||
directives: [ChildViewComponent]
|
directives: [ChildViewComponent]
|
||||||
})
|
})
|
||||||
// #docregion hooks
|
// #docregion hooks
|
||||||
@ -71,14 +70,14 @@ export class AfterViewComponent implements AfterViewChecked, AfterViewInit {
|
|||||||
let c = this.viewChild.hero.length > 10 ? "That's a long name" : '';
|
let c = this.viewChild.hero.length > 10 ? "That's a long name" : '';
|
||||||
if (c !== this.comment) {
|
if (c !== this.comment) {
|
||||||
// Wait a tick because the component's view has already been checked
|
// 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
|
// #enddocregion do-something
|
||||||
|
|
||||||
private logIt(method:string){
|
private logIt(method:string){
|
||||||
let vc = this.viewChild;
|
let child = this.viewChild;
|
||||||
let message = `${method}: ${vc ? vc.hero:'no'} child view`
|
let message = `${method}: ${child ? child.hero:'no'} child view`
|
||||||
this.logger.log(message);
|
this.logger.log(message);
|
||||||
}
|
}
|
||||||
// #docregion hooks
|
// #docregion hooks
|
||||||
@ -108,7 +107,7 @@ export class AfterViewParentComponent {
|
|||||||
logs:string[];
|
logs:string[];
|
||||||
show = true;
|
show = true;
|
||||||
|
|
||||||
constructor(logger:LoggerService){
|
constructor(private logger: LoggerService) {
|
||||||
this.logs = logger.logs;
|
this.logs = logger.logs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,6 +115,6 @@ export class AfterViewParentComponent {
|
|||||||
this.logs.length=0;
|
this.logs.length=0;
|
||||||
// quickly remove and reload AfterViewComponent which recreates it
|
// quickly remove and reload AfterViewComponent which recreates it
|
||||||
this.show = false;
|
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
|
// A change to `counter` is the only change we care about
|
||||||
let prop = changes['counter'];
|
let chng = changes['counter'];
|
||||||
let cur = prop.currentValue;
|
let cur = chng.currentValue;
|
||||||
let prev = JSON.stringify(prop.previousValue); // first time is {}; after is integer
|
let prev = JSON.stringify(chng.previousValue); // first time is {}; after is integer
|
||||||
this.changeLog.push(`counter: currentValue = ${cur}, previousValue = ${prev}`);
|
this.changeLog.push(`counter: currentValue = ${cur}, previousValue = ${prev}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,9 +69,9 @@ export class DoCheckComponent implements DoCheck, OnChanges {
|
|||||||
// Copied from OnChangesComponent
|
// Copied from OnChangesComponent
|
||||||
ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
|
ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
|
||||||
for (let propName in changes) {
|
for (let propName in changes) {
|
||||||
let prop = changes[propName];
|
let chng = changes[propName];
|
||||||
let cur = JSON.stringify(prop.currentValue);
|
let cur = JSON.stringify(chng.currentValue);
|
||||||
let prev = JSON.stringify(prop.previousValue);
|
let prev = JSON.stringify(chng.previousValue);
|
||||||
this.changeLog.push(`OnChanges: ${propName}: currentValue = ${cur}, previousValue = ${prev}`);
|
this.changeLog.push(`OnChanges: ${propName}: currentValue = ${cur}, previousValue = ${prev}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,9 +21,6 @@ export class LoggerService {
|
|||||||
clear() { this.logs.length = 0; }
|
clear() { this.logs.length = 0; }
|
||||||
|
|
||||||
// schedules a view refresh to ensure display catches up
|
// schedules a view refresh to ensure display catches up
|
||||||
tick() {
|
tick() { this.tick_then(() => { }); }
|
||||||
setTimeout(() => {
|
tick_then(fn: () => any) { setTimeout(fn, 0); }
|
||||||
// console.log('tick')
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/* tslint:disable:forin */
|
/* tslint:disable:forin */
|
||||||
// #docregion
|
// #docregion
|
||||||
import {
|
import {
|
||||||
Component, Input, Onchanges,
|
Component, Input, OnChanges,
|
||||||
SimpleChange, ViewChild
|
SimpleChange, ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
|
||||||
@ -36,9 +36,9 @@ export class OnChangesComponent implements OnChanges {
|
|||||||
// #docregion ng-on-changes
|
// #docregion ng-on-changes
|
||||||
ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
|
ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
|
||||||
for (let propName in changes) {
|
for (let propName in changes) {
|
||||||
let prop = changes[propName];
|
let chng = changes[propName];
|
||||||
let cur = JSON.stringify(prop.currentValue);
|
let cur = JSON.stringify(chng.currentValue);
|
||||||
let prev = JSON.stringify(prop.previousValue);
|
let prev = JSON.stringify(chng.previousValue);
|
||||||
this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
|
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({
|
@Component({
|
||||||
selector: 'spy-parent',
|
selector: 'spy-parent',
|
||||||
template: `
|
templateUrl: 'app/spy.component.html',
|
||||||
<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>
|
|
||||||
`,
|
|
||||||
styles: [
|
styles: [
|
||||||
'.parent {background: khaki;}',
|
'.parent {background: khaki;}',
|
||||||
'.heroes {background: LightYellow; padding: 0 8px}'
|
'.heroes {background: LightYellow; padding: 0 8px}'
|
||||||
|
@ -1,9 +1,24 @@
|
|||||||
include ../../../_includes/_util-fns
|
include ../../../_includes/_util-fns
|
||||||
|
|
||||||
mixin liveExLinks(name)
|
- var _docsFor = 'dart';
|
||||||
:marked
|
|
||||||
[Run the live example](https://angular-examples.github.io/#{name}) |
|
mixin privateVar(varName)
|
||||||
[View its source code](https://github.com/angular-examples/#{name})
|
| _#{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) {
|
- var adjustExamplePath = function(_path) {
|
||||||
- if(!_path) return _path;
|
- if(!_path) return _path;
|
||||||
@ -15,7 +30,7 @@ mixin liveExLinks(name)
|
|||||||
- var baseNameNoExt = baseName.substr(0,baseName.length - (extn.length + 1));
|
- var baseNameNoExt = baseName.substr(0,baseName.length - (extn.length + 1));
|
||||||
- var inWebFolder = baseNameNoExt.match(/^(main|index)$/);
|
- var inWebFolder = baseNameNoExt.match(/^(main|index)$/);
|
||||||
- // Adjust the folder path, e.g., ts -> dart
|
- // 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
|
- // In file name, replace special characters with underscore
|
||||||
- baseNameNoExt = baseNameNoExt.replace(/[\-\.]/g, '_');
|
- baseNameNoExt = baseNameNoExt.replace(/[\-\.]/g, '_');
|
||||||
- // Adjust the file extension
|
- // Adjust the file extension
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
include ../_util-fns
|
extends ../../../ts/latest/guide/lifecycle-hooks.jade
|
||||||
|
|
||||||
:marked
|
block includes
|
||||||
We're working on the Dart version of this chapter.
|
include ../_util-fns
|
||||||
In the meantime, please see these resources:
|
|
||||||
|
|
||||||
* [Lifecycle Hooks](/docs/ts/latest/guide/lifecycle-hooks.html):
|
block optional-interfaces
|
||||||
The TypeScript version of this chapter
|
//- n/a for Dart
|
||||||
|
|
||||||
* [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 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"
|
- var top="vertical-align:top"
|
||||||
|
|
||||||
@ -22,14 +23,10 @@ include ../_util-fns
|
|||||||
* [DoCheck](#docheck)
|
* [DoCheck](#docheck)
|
||||||
* [AfterViewInit and AfterViewChecked](#afterview)
|
* [AfterViewInit and AfterViewChecked](#afterview)
|
||||||
* [AfterContentInit and AfterContentChecked](#aftercontent)
|
* [AfterContentInit and AfterContentChecked](#aftercontent)
|
||||||
|
|
||||||
Try the [Live Example](/resources/live-examples/lifecycle-hooks/ts/plnkr.html)
|
|
||||||
|
|
||||||
<!--
|
p Try the #[+liveExampleLink2('live example', 'lifecycle-hooks')].
|
||||||
https://github.com/angular/angular/blob/master/modules/angular2/src/core/linker/interfaces.ts
|
|
||||||
-->
|
|
||||||
|
|
||||||
a(id="hooks-overview")
|
a#hooks-overview
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Component lifecycle Hooks
|
## Component lifecycle Hooks
|
||||||
@ -37,7 +34,7 @@ a(id="hooks-overview")
|
|||||||
as Angular creates, updates, and destroys them.
|
as Angular creates, updates, and destroys them.
|
||||||
|
|
||||||
Developers can tap into key moments in that lifecycle by implementing
|
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`.
|
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`.
|
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='.')
|
+makeExample('lifecycle-hooks/ts/app/peek-a-boo.component.ts', 'ngOnInit', 'peek-a-boo.component.ts (excerpt)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
No directive or component will implement all of them and some of the hooks only make sense for components.
|
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*.
|
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
|
Fortunately, they aren't necessary.
|
||||||
:marked
|
We don't have to add the lifecycle hook interfaces to our directives and components to benefit from the hooks themselves.
|
||||||
### 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.
|
Angular instead inspects our directive and component classes and calls the hook methods *if they are defined*.
|
||||||
We don't have to add the lifecycle hook interfaces to our directives and components to benefit from the hooks themselves.
|
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*.
|
Nonetheless, we strongly recommend adding interfaces to TypeScript directive classes
|
||||||
Angular will find and call methods like `ngOnInit()`, with or without the interfaces.
|
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
|
:marked
|
||||||
Here are the component lifecycle hook methods:
|
Here are the component lifecycle hook methods:
|
||||||
@ -189,7 +185,7 @@ a(id="other-lifecycles")
|
|||||||
:marked
|
:marked
|
||||||
## Other lifecycle hooks
|
## 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)
|
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.
|
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
|
3rd party libraries might implement their hooks as well in order to give us, the developers, more
|
||||||
control over how these libraries are used.
|
control over how these libraries are used.
|
||||||
|
|
||||||
a(id="the-sample")
|
a#the-sample
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
h2 Lifecycle exercises
|
||||||
## Lifecycle exercises
|
p.
|
||||||
|
The #[+liveExampleLink('live example', 'lifecycle-hooks')]
|
||||||
The [live example](/resources/live-examples/lifecycle-hooks/ts/plnkr.html)
|
|
||||||
demonstrates the lifecycle hooks in action through a series of exercises
|
demonstrates the lifecycle hooks in action through a series of exercises
|
||||||
presented as components under the control of the root `AppComponent`.
|
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
|
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.
|
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 <a href="#docheck">DoCheck</a>
|
||||||
td
|
td
|
||||||
:marked
|
: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.
|
See how often Angular calls this hook and watch it post changes to a log.
|
||||||
tr(style=top)
|
tr(style=top)
|
||||||
td <a href="#afterview">AfterView</a>
|
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.
|
We log in it to confirm that input properties (the `name` property in this case) have no assigned values at construction.
|
||||||
:marked
|
:marked
|
||||||
Had we clicked the *Update Hero* button, we'd have seen another `OnChanges` and two more triplets of
|
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
|
Clearly these three hooks fire a *lot* and we must keep the logic we put in these hooks
|
||||||
as lean as possible!
|
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
|
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.
|
at the same time as that element.
|
||||||
Here we attach it to the repeated hero `<div>`
|
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
|
:marked
|
||||||
Each spy's birth and death marks the birth and death of the attached hero `<div>`
|
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 —
|
The `ngDoCheck` hook is called with enormous frequency —
|
||||||
after _every_ change detection cycle no matter where the change occurred.
|
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*.
|
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.
|
Mere mousing into another input box triggers a call.
|
||||||
@ -466,14 +461,14 @@ figure.image-display
|
|||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
: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).
|
[incorrect API documentation](../api/core/DoCheck-interface.html).
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## AfterView
|
## AfterView
|
||||||
The *AfterView* sample explores the `AfterViewInit` and `AfterViewChecked` hooks that Angular calls
|
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:
|
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=".")
|
+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=".")
|
+makeExample('lifecycle-hooks/ts/app/after-view.component.ts', 'do-something', 'AfterViewComponent (doSomething)')(format=".")
|
||||||
:marked
|
: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.
|
we may not update the view *after* it has been composed.
|
||||||
Both hooks fire after the component's view 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!).
|
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
|
Here's *AfterView* in action
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/lifecycle-hooks/after-view-anim.gif' alt="AfterView")
|
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.
|
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=".")
|
+makeExample('lifecycle-hooks/ts/app/after-content.component.ts', 'parent-template', 'AfterContentParentComponent (template excerpt)')(format=".")
|
||||||
:marked
|
: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
|
We never put content between a component's element tags *unless we intend to project that content
|
||||||
into the component*.
|
into the component*.
|
||||||
|
|
||||||
Now look at the component's template:
|
Now look at the component's template:
|
||||||
+makeExample('lifecycle-hooks/ts/app/after-content.component.ts', 'template', 'AfterContentComponent (template)')(format=".")
|
+makeExample('lifecycle-hooks/ts/app/after-content.component.ts', 'template', 'AfterContentComponent (template)')(format=".")
|
||||||
:marked
|
: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.
|
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
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/lifecycle-hooks/projected-child-view.png' width="230" alt="Projected Content")
|
img(src='/resources/images/devguide/lifecycle-hooks/projected-child-view.png' width="230" alt="Projected Content")
|
||||||
:marked
|
:marked
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
The tell-tale signs of *content projection* are (a) HTML between component element tags
|
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
|
:marked
|
||||||
### AfterContent hooks
|
### AfterContent hooks
|
||||||
*AfterContent* hooks are similar to the *AfterView* hooks. The key difference is the kind of child component
|
*AfterContent* hooks are similar to the *AfterView* hooks. The key difference is the kind of child component
|
||||||
|
Loading…
x
Reference in New Issue
Block a user