From c4ae1f1b1b6f916788a719f0cb3e381af9eabd99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=A3o=20J=C3=BAnior?= Date: Wed, 20 Jan 2016 19:14:46 -0200 Subject: [PATCH] docs(guide/lifecycle-hooks): add Dart version of example NOTE: The Dart version generates fewer lifecycle events than the TS version. See angular/angular#6498 for details. closes #733 --- .../dart/lib/after_content_parent.dart | 94 +++++++++++++++++++ .../dart/lib/after_view_component.dart | 76 +++++++++++++++ .../dart/lib/app_component.dart | 28 ++++++ .../dart/lib/child_component.dart | 19 ++++ .../dart/lib/counter_component.dart | 76 +++++++++++++++ .../dart/lib/logger_service.dart | 18 ++++ .../dart/lib/on_changes_component.dart | 80 ++++++++++++++++ .../dart/lib/peek_a_boo_component.dart | 77 +++++++++++++++ .../dart/lib/peek_a_boo_parent_component.dart | 53 +++++++++++ .../dart/lib/spy_component.dart | 52 ++++++++++ .../dart/lib/spy_directive.dart | 21 +++++ .../lifecycle-hooks/dart/pubspec.yaml | 16 ++++ .../lifecycle-hooks/dart/web/index.html | 15 +++ .../lifecycle-hooks/dart/web/main.dart | 7 ++ 14 files changed, 632 insertions(+) create mode 100644 public/docs/_examples/lifecycle-hooks/dart/lib/after_content_parent.dart create mode 100644 public/docs/_examples/lifecycle-hooks/dart/lib/after_view_component.dart create mode 100644 public/docs/_examples/lifecycle-hooks/dart/lib/app_component.dart create mode 100644 public/docs/_examples/lifecycle-hooks/dart/lib/child_component.dart create mode 100644 public/docs/_examples/lifecycle-hooks/dart/lib/counter_component.dart create mode 100644 public/docs/_examples/lifecycle-hooks/dart/lib/logger_service.dart create mode 100644 public/docs/_examples/lifecycle-hooks/dart/lib/on_changes_component.dart create mode 100644 public/docs/_examples/lifecycle-hooks/dart/lib/peek_a_boo_component.dart create mode 100644 public/docs/_examples/lifecycle-hooks/dart/lib/peek_a_boo_parent_component.dart create mode 100644 public/docs/_examples/lifecycle-hooks/dart/lib/spy_component.dart create mode 100644 public/docs/_examples/lifecycle-hooks/dart/lib/spy_directive.dart create mode 100644 public/docs/_examples/lifecycle-hooks/dart/pubspec.yaml create mode 100644 public/docs/_examples/lifecycle-hooks/dart/web/index.html create mode 100644 public/docs/_examples/lifecycle-hooks/dart/web/main.dart diff --git a/public/docs/_examples/lifecycle-hooks/dart/lib/after_content_parent.dart b/public/docs/_examples/lifecycle-hooks/dart/lib/after_content_parent.dart new file mode 100644 index 0000000000..ceffec0936 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/dart/lib/after_content_parent.dart @@ -0,0 +1,94 @@ +// #docregion +import 'package:angular2/angular2.dart'; +import 'logger_service.dart'; +import 'child_component.dart'; + +@Component( + selector: 'after-content', + template: ''' +
+
-- child content begins --
+ + + +
-- child content ends --
+
+ ''', + 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: ''' +
+

AfterContent

+ + + + + + + + +

-- Lifecycle Hook Log --

+
{{msg}}
+
+ ''', + styles: const [ + '.parent {background: powderblue; padding: 8px; margin:100px 8px;}' + ], + directives: const [AfterContentComponent, ChildComponent], + providers: const [LoggerService]) +class AfterContentParentComponent { + List hookLog; + String hero = 'Magneta'; + bool showChild = true; + + AfterContentParentComponent(LoggerService logger) { + hookLog = logger.logs; + } +} diff --git a/public/docs/_examples/lifecycle-hooks/dart/lib/after_view_component.dart b/public/docs/_examples/lifecycle-hooks/dart/lib/after_view_component.dart new file mode 100644 index 0000000000..12b6cb4dd8 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/dart/lib/after_view_component.dart @@ -0,0 +1,76 @@ +// #docregion +import 'package:angular2/angular2.dart'; +import 'child_component.dart'; +import 'logger_service.dart'; + +@Component( + selector: 'after-view-parent', + template: ''' +
+

AfterView

+ +
+ + + + +
+ +

-- Lifecycle Hook Log --

+
{{msg}}
+
+ ''', + 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 hookLog; + String hero = 'Magneta'; + bool showChild = true; + + // Query for a CONTENT child of type `ChildComponent` + // No such CONTENT child exists! + // This component holds a view but no content of that type. + @ContentChild(ChildComponent) + ChildComponent contentChild; + + // Query for a VIEW child of type `ChildComponent` + @ViewChild(ChildComponent) + ChildComponent viewChild; + + String _prevHero; + + AfterViewParentComponent(this._logger) { + hookLog = _logger.logs; + _logger.log('AfterView ctor: $message'); + } + + bool get _hasContentChild => contentChild != null; + bool get _hasViewChild => viewChild != null; + + ///// Hooks + ngAfterContentInit() { + _logger.log( + 'AfterContentInit: There is ${ _hasContentChild ? 'a' : 'no'} content child'); + } + + ngAfterViewInit() { + // viewChild is set after the view has been initialized + _logger.log('AfterViewInit: $message'); + } + + ngAfterViewChecked() { + // viewChild is updated after the view has been checked + // Called frequently; only report when the hero changes + if (!_hasViewChild || _prevHero == viewChild.hero) return; + _prevHero = viewChild.hero; + _logger.log('AfterViewChecked: $message'); + } + + String get message => + _hasViewChild ? '"${viewChild.hero}" child view' : 'no child view'; +} diff --git a/public/docs/_examples/lifecycle-hooks/dart/lib/app_component.dart b/public/docs/_examples/lifecycle-hooks/dart/lib/app_component.dart new file mode 100644 index 0000000000..3d3cfa3bd5 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/dart/lib/app_component.dart @@ -0,0 +1,28 @@ +// #docregion +import 'package:angular2/angular2.dart'; +import 'peek_a_boo_parent_component.dart'; +import 'on_changes_component.dart'; +import 'after_view_component.dart'; +import 'after_content_parent.dart'; +import 'spy_component.dart'; +import 'counter_component.dart'; + +@Component( + selector: 'my-app', + template: ''' + + + + + + + ''', + directives: const [ + PeekABooParentComponent, + OnChangesParentComponent, + AfterViewParentComponent, + AfterContentParentComponent, + SpyParentComponent, + CounterParentComponent + ]) +class AppComponent {} diff --git a/public/docs/_examples/lifecycle-hooks/dart/lib/child_component.dart b/public/docs/_examples/lifecycle-hooks/dart/lib/child_component.dart new file mode 100644 index 0000000000..dc43e899dc --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/dart/lib/child_component.dart @@ -0,0 +1,19 @@ +// #docregion +import 'package:angular2/angular2.dart'; + +@Component( + selector: 'my-child', + template: ''' +
+
-- child view begins --
+
{{hero}} is my hero.
+
-- child view ends --
+
+ ''', + styles: const [ + '.child {background: Yellow; padding: 8px; }', + '.my-child {background: LightYellow; padding: 8px; margin-top: 8px}' + ]) +class ChildComponent { + @Input() String hero; +} diff --git a/public/docs/_examples/lifecycle-hooks/dart/lib/counter_component.dart b/public/docs/_examples/lifecycle-hooks/dart/lib/counter_component.dart new file mode 100644 index 0000000000..5473e4a725 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/dart/lib/counter_component.dart @@ -0,0 +1,76 @@ +// #docregion +import 'package:angular2/angular2.dart'; +import 'spy_directive.dart'; +import 'logger_service.dart'; + +@Component( + selector: 'my-counter', + template: ''' +
+ Counter = {{counter}} + +
-- Counter Change Log --
+
{{chg}}
+
+ ''', + styles: const [ + '.counter {background: LightYellow; padding: 8px; margin-top: 8px}' + ], + directives: const [Spy]) +class MyCounter implements OnChanges { + @Input() num counter; + List changeLog = []; + + ngOnChanges(Map changes) { + // Empty the changeLog whenever counter goes to zero + // hint: this is a way to respond programmatically to external value changes. + if (this.counter == 0) { + changeLog.clear(); + } + + // A change to `counter` is the only change we care about + SimpleChange prop = changes['counter']; + var prev = prop.isFirstChange() ? "{}" : prop.previousValue; + changeLog.add( + 'counter: currentValue = ${prop.currentValue}, previousValue = $prev'); + } +} + +@Component( + selector: 'counter-parent', + template: ''' +
+

Counter Spy

+ + + + + + +

-- Spy Lifecycle Hook Log --

+
{{msg}}
+
+ ''', + styles: const [ + '.parent {background: gold; padding: 10px; margin:100px 8px;}' + ], + directives: const [MyCounter], + providers: const [LoggerService]) +class CounterParentComponent { + num value; + List spyLog = []; + + LoggerService _logger; + + CounterParentComponent(this._logger) { + spyLog = _logger.logs; + reset(); + } + + updateCounter() => value += 1; + + reset() { + _logger.log('-- reset --'); + value = 0; + } +} diff --git a/public/docs/_examples/lifecycle-hooks/dart/lib/logger_service.dart b/public/docs/_examples/lifecycle-hooks/dart/lib/logger_service.dart new file mode 100644 index 0000000000..b5ea0a9429 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/dart/lib/logger_service.dart @@ -0,0 +1,18 @@ +import 'package:angular2/angular2.dart'; +import 'dart:async'; + +@Injectable() +class LoggerService { + List logs = []; + + log(String msg, [bool noTick = false]) { + if (!noTick) { + tick(); + } + logs.add(msg); + } + + clear() => logs.clear(); + + tick() => new Future(() {}); +} diff --git a/public/docs/_examples/lifecycle-hooks/dart/lib/on_changes_component.dart b/public/docs/_examples/lifecycle-hooks/dart/lib/on_changes_component.dart new file mode 100644 index 0000000000..7509e84d2a --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/dart/lib/on_changes_component.dart @@ -0,0 +1,80 @@ +// #docregion +import 'package:angular2/angular2.dart'; +import 'dart:convert'; + +class Hero { + String name; + Hero(this.name); + + Map toJson() => {'name': name}; +} + +@Component( + selector: 'my-hero', + template: ''' +
+

{{hero.name}} can {{power}}

+ +

-- Change Log --

+
{{chg}}
+
+ ''', + styles: const [ + '.hero {background: LightYellow; padding: 8px; margin-top: 8px}', + 'p {background: Yellow; padding: 8px; margin-top: 8px}' + ]) +class MyHeroComponent implements OnChanges { + @Input() Hero hero; + @Input() String power; + @Input() bool reset; + List changeLog = []; + + ngOnChanges(Map changes) { + // Empty the changeLog whenever 'reset' property changes + // hint: this is a way to respond programmatically to external value changes. + if (changes.containsKey('reset')) changeLog.clear(); + + changes.forEach((String key, SimpleChange change) { + String cur = JSON.encode(change.currentValue); + String prev = + change.isFirstChange() ? "{}" : JSON.encode(change.previousValue); + changeLog.add('$key: currentValue = ${cur}, previousValue = $prev'); + }); + } +} + +@Component( + selector: 'on-changes-parent', + template: ''' +
+

OnChanges

+ +
Hero.name: does NOT trigger onChanges
+
Power: DOES trigger onChanges
+
triggers onChanges and clears the change log
+ + +
+ ''', + styles: const [ + '.parent {background: Lavender; padding: 10px; margin:100px 8px;}' + ], + directives: const [MyHeroComponent]) +class OnChangesParentComponent { + Hero hero; + String power; + bool resetTrigger = false; + + OnChangesParentComponent() { + reset(); + } + + reset() { + // new Hero object every time; triggers onChange + hero = new Hero('Windstorm'); + // setting power only triggers onChange if this value is different + power = 'sing'; + // always triggers onChange ... which is interpreted as a reset + resetTrigger = !resetTrigger; + } +} diff --git a/public/docs/_examples/lifecycle-hooks/dart/lib/peek_a_boo_component.dart b/public/docs/_examples/lifecycle-hooks/dart/lib/peek_a_boo_component.dart new file mode 100644 index 0000000000..3db473fa86 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/dart/lib/peek_a_boo_component.dart @@ -0,0 +1,77 @@ +// #docregion +// #docregion lc-imports +import 'package:angular2/angular2.dart'; +import 'package:lifecycle_hooks/logger_service.dart'; + +int nextId = 1; + +@Component( + selector: 'peek-a-boo', + template: '

Now you see my hero, {{name}}

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

Peek-A-Boo

+ + + + + + + +

-- Lifecycle Hook Log --

+
{{msg}}
+
+ ''', + styles: const [ + '.parent {background: moccasin; padding: 10px; margin:100px 8px}' + ], + directives: const [PeekABooComponent], + providers: const [LoggerService]) +class PeekABooParentComponent { + bool hasChild = false; + List hookLog; + + String heroName = 'Windstorm'; + LoggerService _logger; + + PeekABooParentComponent(this._logger) { + hookLog = _logger.logs; + } + + toggleChild() { + hasChild = !hasChild; + if (hasChild) { + heroName = 'Windstorm'; + _logger.clear(); // clear log on create + } + _logger.tick(); + } + + updateHero() { + heroName += '!'; + _logger.tick(); + } +} diff --git a/public/docs/_examples/lifecycle-hooks/dart/lib/spy_component.dart b/public/docs/_examples/lifecycle-hooks/dart/lib/spy_component.dart new file mode 100644 index 0000000000..64d4faee04 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/dart/lib/spy_component.dart @@ -0,0 +1,52 @@ +// #docregion +import 'package:angular2/angular2.dart'; +import 'logger_service.dart'; +import 'spy_directive.dart'; + +@Component( + selector: 'spy-parent', + template: ''' +
+

Spy Directive

+ + + + + +

+
+ {{hero}} +
+ +

-- Spy Lifecycle Hook Log --

+
{{msg}}
+
+ ''', + styles: const [ + '.parent {background: khaki; padding: 10px; margin:100px 8px}', + '.heroes {background: LightYellow; padding: 0 8px}' + ], + directives: const [Spy], + providers: const [LoggerService]) +class SpyParentComponent { + String newName = 'Herbie'; + List heroes = ['Windstorm', 'Magneta']; + List spyLog; + LoggerService _logger; + + SpyParentComponent(this._logger) { + spyLog = _logger.logs; + } + + addHero() { + if (newName.trim().isNotEmpty) { + heroes.add(newName.trim()); + newName = ''; + } + } + + reset() { + _logger.log('-- reset --'); + heroes.clear(); + } +} diff --git a/public/docs/_examples/lifecycle-hooks/dart/lib/spy_directive.dart b/public/docs/_examples/lifecycle-hooks/dart/lib/spy_directive.dart new file mode 100644 index 0000000000..d6e268c713 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/dart/lib/spy_directive.dart @@ -0,0 +1,21 @@ +// #docregion +import 'package:angular2/angular2.dart'; +import 'logger_service.dart'; + +int nextId = 1; + +// Spy on any element to which it is applied. +// Usage:
...
+@Directive(selector: '[mySpy]') +class Spy implements OnInit, OnDestroy { + int _id = nextId++; + LoggerService _logger; + + Spy(this._logger); + + ngOnInit() => _logIt('onInit'); + + ngOnDestroy() => _logIt('onDestroy'); + + _logIt(String msg) => _logger.log('Spy #$_id $msg'); +} diff --git a/public/docs/_examples/lifecycle-hooks/dart/pubspec.yaml b/public/docs/_examples/lifecycle-hooks/dart/pubspec.yaml new file mode 100644 index 0000000000..b4f74a25df --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/dart/pubspec.yaml @@ -0,0 +1,16 @@ +name: lifecycle_hooks +description: Lifecycle Hooks +version: 0.0.1 +environment: + sdk: '>=1.13.0 <2.0.0' +dependencies: + angular2: 2.0.0-beta.1 + browser: ^0.10.0 + dart_to_js_script_rewriter: ^0.1.0 +transformers: +- angular2: + platform_directives: + - 'package:angular2/common.dart#CORE_DIRECTIVES' + - 'package:angular2/common.dart#FORM_DIRECTIVES' + entry_points: web/main.dart +- dart_to_js_script_rewriter diff --git a/public/docs/_examples/lifecycle-hooks/dart/web/index.html b/public/docs/_examples/lifecycle-hooks/dart/web/index.html new file mode 100644 index 0000000000..eb830008ad --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/dart/web/index.html @@ -0,0 +1,15 @@ + + + + + + Angular 2 Lifecycle Hooks + + + + + + Loading... + + + diff --git a/public/docs/_examples/lifecycle-hooks/dart/web/main.dart b/public/docs/_examples/lifecycle-hooks/dart/web/main.dart new file mode 100644 index 0000000000..6fc83949b7 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/dart/web/main.dart @@ -0,0 +1,7 @@ +// #docregion +import 'package:angular2/bootstrap.dart'; +import 'package:lifecycle_hooks/app_component.dart'; + +main() { + bootstrap(AppComponent); +}