33 * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
44 */
55
6+ import { ENTER , ESCAPE } from '@angular/cdk/keycodes' ;
67import {
78 ChangeDetectionStrategy ,
89 ChangeDetectorRef ,
@@ -11,15 +12,15 @@ import {
1112 EventEmitter ,
1213 Input ,
1314 NgZone ,
14- OnDestroy ,
1515 OnInit ,
1616 Output ,
1717 ViewChild ,
1818 ViewEncapsulation
1919} from '@angular/core' ;
20- import { Subject } from 'rxjs' ;
21- import { take , takeUntil } from 'rxjs/operators' ;
20+ import { BehaviorSubject , fromEvent , Observable } from 'rxjs' ;
21+ import { filter , switchMap , take , takeUntil , withLatestFrom } from 'rxjs/operators' ;
2222
23+ import { NzDestroyService } from 'ng-zorro-antd/core/services' ;
2324import { NzTSType } from 'ng-zorro-antd/core/types' ;
2425import { NzI18nService , NzTextI18nInterface } from 'ng-zorro-antd/i18n' ;
2526import { NzAutosizeDirective } from 'ng-zorro-antd/input' ;
@@ -28,70 +29,125 @@ import { NzAutosizeDirective } from 'ng-zorro-antd/input';
2829 selector : 'nz-text-edit' ,
2930 exportAs : 'nzTextEdit' ,
3031 template : `
31- <button
32- *ngIf="!editing"
33- nz-tooltip
34- nz-trans-button
35- class="ant-typography-edit"
36- [nzTooltipTitle]="tooltip === null ? null : tooltip || locale?.edit"
37- (click)="onClick()"
38- >
39- <ng-container *nzStringTemplateOutlet="icon; let icon">
40- <i nz-icon [nzType]="icon"></i>
41- </ng-container>
42- </button>
43- <ng-container *ngIf="editing">
44- <textarea
45- #textarea
46- nz-input
47- nzAutosize
48- (input)="onInput($event)"
49- (blur)="confirm()"
50- (keydown.esc)="onCancel()"
51- (keydown.enter)="onEnter($event)"
52- ></textarea>
32+ <ng-template [ngIf]="editing" [ngIfElse]="notEditing">
33+ <textarea #textarea nz-input nzAutosize (blur)="confirm()"></textarea>
5334 <button nz-trans-button class="ant-typography-edit-content-confirm" (click)="confirm()">
5435 <i nz-icon nzType="enter"></i>
5536 </button>
56- </ng-container>
37+ </ng-template>
38+
39+ <ng-template #notEditing>
40+ <button
41+ nz-tooltip
42+ nz-trans-button
43+ class="ant-typography-edit"
44+ [nzTooltipTitle]="tooltip === null ? null : tooltip || locale?.edit"
45+ (click)="onClick()"
46+ >
47+ <ng-container *nzStringTemplateOutlet="icon; let icon">
48+ <i nz-icon [nzType]="icon"></i>
49+ </ng-container>
50+ </button>
51+ </ng-template>
5752 ` ,
5853 changeDetection : ChangeDetectionStrategy . OnPush ,
5954 encapsulation : ViewEncapsulation . None ,
60- preserveWhitespaces : false
55+ preserveWhitespaces : false ,
56+ providers : [ NzDestroyService ]
6157} )
62- export class NzTextEditComponent implements OnInit , OnDestroy {
58+ export class NzTextEditComponent implements OnInit {
6359 editing = false ;
6460 locale ! : NzTextI18nInterface ;
65- private destroy$ = new Subject ( ) ;
6661
6762 @Input ( ) text ?: string ;
6863 @Input ( ) icon : NzTSType = 'edit' ;
6964 @Input ( ) tooltip ?: null | NzTSType ;
7065 @Output ( ) readonly startEditing = new EventEmitter < void > ( ) ;
7166 @Output ( ) readonly endEditing = new EventEmitter < string > ( true ) ;
72- @ViewChild ( 'textarea' , { static : false } ) textarea ! : ElementRef < HTMLTextAreaElement > ;
67+ @ViewChild ( 'textarea' , { static : false } )
68+ set textarea ( textarea : ElementRef < HTMLTextAreaElement > | undefined ) {
69+ if ( textarea ) {
70+ this . textarea$ . next ( textarea ) ;
71+ }
72+ }
7373 @ViewChild ( NzAutosizeDirective , { static : false } ) autosizeDirective ! : NzAutosizeDirective ;
7474
7575 beforeText ?: string ;
7676 currentText ?: string ;
7777 nativeElement = this . host . nativeElement ;
78+
79+ // We could've saved the textarea within some private property (e.g. `_textarea`) and have a getter,
80+ // but having subject makes the code more reactive and cancellable (e.g. event listeners will be
81+ // automatically removed and re-added through the `switchMap` below).
82+ private textarea$ = new BehaviorSubject < ElementRef < HTMLTextAreaElement > | null > ( null ) ;
83+
7884 constructor (
79- private zone : NgZone ,
80- private host : ElementRef ,
85+ private ngZone : NgZone ,
86+ private host : ElementRef < HTMLElement > ,
8187 private cdr : ChangeDetectorRef ,
82- private i18n : NzI18nService
88+ private i18n : NzI18nService ,
89+ private destroy$ : NzDestroyService
8390 ) { }
8491
8592 ngOnInit ( ) : void {
8693 this . i18n . localeChange . pipe ( takeUntil ( this . destroy$ ) ) . subscribe ( ( ) => {
8794 this . locale = this . i18n . getLocaleData ( 'Text' ) ;
8895 this . cdr . markForCheck ( ) ;
8996 } ) ;
90- }
9197
92- ngOnDestroy ( ) : void {
93- this . destroy$ . next ( ) ;
94- this . destroy$ . complete ( ) ;
98+ const textarea$ : Observable < ElementRef < HTMLTextAreaElement > > = this . textarea$ . pipe (
99+ filter ( ( textarea ) : textarea is ElementRef < HTMLTextAreaElement > => textarea !== null )
100+ ) ;
101+
102+ textarea$
103+ . pipe (
104+ switchMap (
105+ textarea =>
106+ // Caretaker note: we explicitly should call `subscribe()` within the root zone.
107+ // `runOutsideAngular(() => fromEvent(...))` will just create an observable within the root zone,
108+ // but `addEventListener` is called when the `fromEvent` is subscribed.
109+ new Observable < KeyboardEvent > ( subscriber =>
110+ this . ngZone . runOutsideAngular ( ( ) =>
111+ fromEvent < KeyboardEvent > ( textarea . nativeElement , 'keydown' ) . subscribe ( subscriber )
112+ )
113+ )
114+ ) ,
115+ takeUntil ( this . destroy$ )
116+ )
117+ . subscribe ( event => {
118+ // Caretaker note: adding modifier at the end (for instance `(keydown.esc)`) will tell Angular to add
119+ // an event listener through the `KeyEventsPlugin`, which always runs `ngZone.runGuarded()` internally.
120+ // We're interested only in escape and enter keyboard buttons, otherwise Angular will run change detection
121+ // on any `keydown` event.
122+ if ( event . keyCode !== ESCAPE && event . keyCode !== ENTER ) {
123+ return ;
124+ }
125+
126+ this . ngZone . run ( ( ) => {
127+ if ( event . keyCode === ESCAPE ) {
128+ this . onCancel ( ) ;
129+ } else {
130+ this . onEnter ( event ) ;
131+ }
132+ this . cdr . markForCheck ( ) ;
133+ } ) ;
134+ } ) ;
135+
136+ textarea$
137+ . pipe (
138+ switchMap (
139+ textarea =>
140+ new Observable < KeyboardEvent > ( subscriber =>
141+ this . ngZone . runOutsideAngular ( ( ) =>
142+ fromEvent < KeyboardEvent > ( textarea . nativeElement , 'input' ) . subscribe ( subscriber )
143+ )
144+ )
145+ ) ,
146+ takeUntil ( this . destroy$ )
147+ )
148+ . subscribe ( event => {
149+ this . currentText = ( event . target as HTMLTextAreaElement ) . value ;
150+ } ) ;
95151 }
96152
97153 onClick ( ) : void {
@@ -107,11 +163,6 @@ export class NzTextEditComponent implements OnInit, OnDestroy {
107163 this . endEditing . emit ( this . currentText ) ;
108164 }
109165
110- onInput ( event : Event ) : void {
111- const target = event . target as HTMLTextAreaElement ;
112- this . currentText = target . value ;
113- }
114-
115166 onEnter ( event : Event ) : void {
116167 event . stopPropagation ( ) ;
117168 event . preventDefault ( ) ;
@@ -124,13 +175,15 @@ export class NzTextEditComponent implements OnInit, OnDestroy {
124175 }
125176
126177 focusAndSetValue ( ) : void {
127- this . zone . onStable . pipe ( take ( 1 ) , takeUntil ( this . destroy$ ) ) . subscribe ( ( ) => {
128- if ( this . textarea ?. nativeElement ) {
129- this . textarea . nativeElement . focus ( ) ;
130- this . textarea . nativeElement . value = this . currentText || '' ;
131- this . autosizeDirective . resizeToFitContent ( ) ;
132- this . cdr . markForCheck ( ) ;
133- }
134- } ) ;
178+ this . ngZone . onStable
179+ . pipe ( take ( 1 ) , withLatestFrom ( this . textarea$ ) , takeUntil ( this . destroy$ ) )
180+ . subscribe ( ( [ , textarea ] ) => {
181+ if ( textarea ) {
182+ textarea . nativeElement . focus ( ) ;
183+ textarea . nativeElement . value = this . currentText || '' ;
184+ this . autosizeDirective . resizeToFitContent ( ) ;
185+ this . cdr . markForCheck ( ) ;
186+ }
187+ } ) ;
135188 }
136189}
0 commit comments