javascript - Angular 2: Devolución de llamada cuando ngFor ha finalizado
angularjs (8)
En Angular 1, he escrito una directiva personalizada ("lista para el repetidor") para usar con
ng-repeat
para invocar un método de devolución de llamada cuando la iteración se ha completado:
if ($scope.$last === true)
{
$timeout(() =>
{
$scope.$parent.$parent.$eval(someCallbackMethod);
});
}
Uso en marcado:
<li ng-repeat="item in vm.Items track by item.Identifier"
repeater-ready="vm.CallThisWhenNgRepeatHasFinished()">
¿Cómo puedo lograr una funcionalidad similar con
ngFor
en Angular 2?
En lugar de [listo], use [attr.ready] como a continuación
<li *ngFor="#item in Items; #last = last" [attr.ready]="last ? false : true">
Encontré en RC3 que la respuesta aceptada no funciona. Sin embargo, he encontrado una manera de lidiar con esto. Para mí, necesito saber cuándo ngFor ha terminado de ejecutar el MDL componentHandler para actualizar los componentes.
Primero necesitará una directiva.
upgradeComponents.directive.ts
import { Directive, ElementRef, Input } from ''@angular/core'';
declare var componentHandler : any;
@Directive({ selector: ''[upgrade-components]'' })
export class UpgradeComponentsDirective{
@Input(''upgrade-components'')
set upgradeComponents(upgrade : boolean){
if(upgrade) componentHandler.upgradeAllRegistered();
}
}
Luego importe esto en su componente y agréguelo a las directivas
import {UpgradeComponentsDirective} from ''./upgradeComponents.directive'';
@Component({
templateUrl: ''templates/mytemplate.html'',
directives: [UpgradeComponentsDirective]
})
Ahora, en HTML, establezca el atributo "upgrade-components" en verdadero.
<div *ngFor=''let item of items;let last=last'' [upgrade-components]="last ? true : false">
Cuando este atributo se establece en verdadero, ejecutará el método bajo la declaración @Input (). En mi caso, ejecuta componentHandler.upgradeAllRegistered (). Sin embargo, podría usarse para cualquier cosa que elija. Al vincularse a la propiedad ''last'' de la instrucción ngFor, esto se ejecutará cuando haya terminado.
No necesitará usar [attr.upgrade-components] aunque este no sea un atributo nativo debido a que ahora es una directiva de buena fe.
Escribo una demostración para este problema.
La teoría se basa en
la respuesta aceptada,
pero esta respuesta no está completa porque el
li
debería ser un componente personalizado que pueda aceptar una entrada
ready
.
Escribo github.com/xmeng1/ngfor-finish-callback de este problema.
Definir un nuevo componente:
import {Component, Input, OnInit} desde ''@ angular / core'';
@Component({
selector: ''app-li-ready'',
templateUrl: ''./li-ready.component.html'',
styleUrls: [''./li-ready.component.css'']
})
export class LiReadyComponent implements OnInit {
items: string[] = [];
@Input() item;
constructor() { }
ngOnInit(): void {
console.log(''LiReadyComponent'');
}
@Input()
set ready(isReady: boolean) {
if (isReady) {
console.log(''===isReady!'');
}
}
}
modelo
{{item}}
uso en el componente de la aplicación
<app-li-ready *ngFor="let item of items; let last1 = last;" [ready]="last1" [item]="item"></app-li-ready>
Verá que el registro en la consola imprimirá toda la cadena de elementos y luego imprimirá isReady.
La solución es bastante trivial.
Si necesita saber cuándo
ngFor
completa la impresión de todos los elementos DOM en la ventana del navegador, haga lo siguiente:
1. Agregar un marcador de posición
Agregue un marcador de posición para el contenido que se imprime:
<div *ngIf="!contentPrinted">Rendering content...</div>
2. Agregar un contenedor
Cree un contenedor con
display: none
para el contenido.
Cuando se imprimen todos los elementos,
display: block
.
contentPrinted
es una propiedad de indicador de componente, cuyo valor predeterminado es
false
:
<ul [class.visible]="contentPrinted"> ...items </ul>
3. Crear un método de devolución de llamada
Agregue
onContentPrinted()
al componente, que se desactiva después de que
ngFor
complete:
onContentPrinted() { this.contentPrinted = true; this.changeDetector.detectChanges(); }
Y no olvide usar
ChangeDetectorRef
para evitar
ExpressionChangedAfterItHasBeenCheckedError
.
4. Use ng para el
last
valor
Declare la
last
variable en
ngFor
.
Úselo dentro de
li
para ejecutar un método cuando este elemento sea el
último
:
<li *ngFor="let item of items; let last = last"> ... <ng-container *ngIf="last && !contentPrinted"> {{ onContentPrinted() }} </ng-container> <li>
-
Use la propiedad de indicador de componente
onContentPrinted()
para ejecutaronContentPrinted()
solo una vez . -
Use
ng-container
para no afectar el diseño.
Para mí trabaja en Angular2 usando el mecanografiado.
<li *ngFor="let item in Items; let last = last">
...
<span *ngIf="last">{{ngForCallback()}}</span>
</li>
Entonces puedes manejar esta función
public ngForCallback() {
...
}
Puede usar algo como esto ( angular.io/docs/ts/latest/api/common/NgFor-directive.html ):
<li *ngFor="#item in Items; #last = last" [ready]="last ? false : true">
Entonces puede Interceptar los cambios de propiedad de entrada con un setter
@Input()
set ready(isReady: boolean) {
if (isReady) someCallbackMethod();
}
Todavía no he analizado en profundidad cómo ngFor renderiza elementos bajo el capó. Pero por observación, he notado que a menudo tiende a evaluar expresiones más de una vez por cada elemento que está iterando.
Esto hace que cualquier llamada al método mecanografiado realizada al verificar ngFor ''last'' variable se active, a veces, más de una vez.
Para garantizar una llamada única a su método de mecanografiado por ngFor cuando finalice correctamente la iteración a través de los elementos, debe agregar una pequeña protección contra la reevaluación de múltiples expresiones que ngFor hace bajo el capó.
Aquí hay una forma de hacerlo (a través de una directiva), espero que ayude:
El codigo directivo
import { Directive, OnDestroy, Input, AfterViewInit } from ''@angular/core'';
@Directive({
selector: ''[callback]''
})
export class CallbackDirective implements AfterViewInit, OnDestroy {
is_init:boolean = false;
called:boolean = false;
@Input(''callback'') callback:()=>any;
constructor() { }
ngAfterViewInit():void{
this.is_init = true;
}
ngOnDestroy():void {
this.is_init = false;
this.called = false;
}
@Input(''callback-condition'')
set condition(value: any) {
if (value==false || this.called) return;
// in case callback-condition is set prior ngAfterViewInit is called
if (!this.is_init) {
setTimeout(()=>this.condition = value, 50);
return;
}
if (this.callback) {
this.callback();
this.called = true;
}
else console.error("callback is null");
}
}
Después de declarar la directiva anterior en su módulo (suponiendo que sepa cómo hacerlo, si no, pregunte y espero actualizar esto con un fragmento de código), aquí le mostramos cómo usar la directiva con ngFor:
<li *ngFor="let item of some_list;let last = last;" [callback]="doSomething" [callback-condition]="last">{{item}}</li>
''doSomething'' es el nombre del método en su archivo TypeScript al que desea llamar cuando ngFor termina de iterar por los elementos.
Nota: ''doSomething'' no tiene corchetes ''()'' aquí, ya que solo estamos pasando una referencia al método de mecanografiado y en realidad no lo estamos llamando aquí.
Y finalmente, así es como se ve el método ''doSomething'' en su archivo de mecanografía:
public doSomething=()=> {
console.log("triggered from the directive''s parent component when ngFor finishes iterating");
}
Puede usar @ViewChildren para ese propósito
@Component({
selector: ''my-app'',
template: `
<ul *ngIf="!isHidden">
<li #allTheseThings *ngFor="let i of items; let last = last">{{i}}</li>
</ul>
<br>
<button (click)="items.push(''another'')">Add Another</button>
<button (click)="isHidden = !isHidden">{{isHidden ? ''Show'' : ''Hide''}}</button>
`,
})
export class App {
items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
@ViewChildren(''allTheseThings'') things: QueryList<any>;
ngAfterViewInit() {
this.things.changes.subscribe(t => {
this.ngForRendred();
})
}
ngForRendred() {
console.log(''NgFor is Rendered'');
}
}
La respuesta original está aquí https://.com/a/37088348/5700401