Búsqueda de sitios web

typescript - La vista de Angular 6 no se actualiza después de cambiar una variable dentro de subscribe


¿Por qué la vista no se actualiza cuando una variable cambia dentro de un subscribe?

Tengo este código:

example.component.ts

testVariable: string;

ngOnInit() {
    this.testVariable = 'foo';

    this.someService.someObservable.subscribe(
        () => console.log('success'),
        (error) => console.log('error', error),
        () => {
            this.testVariable += '-bar';

            console.log('completed', this.testVariable);
            // prints: foo-Hello-bar
        }
    );

    this.testVariable += '-Hello';
}

example.component.html

{{testVariable}}

Pero la vista muestra: foo-Hello.

¿Por qué no se muestra: foo-Hello-bar?

Si llamo a ChangeDetectorRef.detectChanges() dentro del subscribe, mostrará el valor correcto, pero ¿por qué tengo que hacer esto?

No debería estar llamando a este método desde cada suscripción, o, en absoluto (Angular debería manejar esto). ¿Hay una manera correcta?

¿Me perdí algo en la actualización de Angular/rxjs 5 a 6?

En este momento tengo Angular versión 6.0.2 y rxjs 6.0.0. El mismo código funciona bien en Angular 5.2 y rxjs 5.5.10 sin la necesidad de llamar a detectChanges.

Respuesta #1

Hasta donde sé, Angular solo actualiza la vista si cambias los datos en la "zona de Angular". La llamada asíncrona en tu ejemplo no califica para esto. Pero si quieres, puedes ponerla en la zona de Angular o usar rxjs o extraer parte del código a un nuevo componente para resolver este problema. Explicaré todo:

EDITAR: Parece que no todas las soluciones están funcionando más. Para la mayoría de los usuarios, la primera solución "Zona Angular" funciona.

1 Zona Angular

El uso más común de este servicio es optimizar el rendimiento al iniciar un trabajo que consiste en una o más tareas asincrónicas que no requieren que las actualizaciones de la interfaz de usuario o el manejo de errores sean gestionados por Angular. Dichas tareas se pueden iniciar mediante runOutsideAngular y, si es necesario, estas tareas pueden reingresar a la zona de Angular mediante run. https://angular.io/api/core/NgZone

La parte clave es la función "run". Podrías inyectar NgZone y poner la actualización de tu valor en la devolución de llamada run del objeto NgZone:

constructor(private ngZone: NgZone ) { }
testVariable: string;

ngOnInit() {
   this.testVariable = 'foo';

   this.someService.someObservable.subscribe(
      () => console.log('success'),
      (error) => console.log('error', error),
      () => {
      this.ngZone.run( () => {
         this.testVariable += '-bar';
      });
      }
   );
}

Según esta respuesta, causaría que toda la aplicación detectara cambios, mientras que tu enfoque con ChangeDetectorRef.detectChanges solo detectaría cambios en tu componente y sus descendientes.

2 RxJS

Otra forma sería usar rxjs para actualizar la vista. Cuando te suscribes por primera vez a un ReplaySubject, te dará el valor más reciente. Un BehaviorSubject es básicamente lo mismo, pero te permite definir un valor predeterminado (tiene sentido en tu ejemplo, pero no necesariamente es la elección correcta todo el tiempo). Después de esta emisión inicial, básicamente es un ReplaySubject normal:

this.testVariable = 'foo';
testEmitter$ = new BehaviorSubject<string>(this.testVariable);


ngOnInit() {

   this.someService.someObservable.subscribe(
      () => console.log('success'),
      (error) => console.log('error', error),
      () => {
         this.testVariable += '-bar';
         this.testEmitter.next(this.testVariable);
      }
   );
}

En su opinión, podría suscribirse al Subject usando el pipe async:

{{testEmitter$ | async}}

3 Extraer código a un nuevo Componente

Si envías la cadena a otro componente, también se actualizará. Tendrías que usar el selector @Input() en el nuevo componente.

Así que el nuevo componente tiene código como este:

@Input() testVariable = '';

Y la variable testVariable se asigna en el HTML como antes con llaves.

En la vista HTML principal, puedes pasar la variable del elemento padre al elemento hijo:

<app-child [testVariable]="testVariable"></app-child>

De esta manera estás en la zona Angular.

4 Preferencia personal

Mi preferencia personal es usar rxjs o la forma de componente. Usar detectChanges o NGZone me parece más hacky.

Respuesta #2

Para mí, nada de lo anterior funcionó. Sin embargo, ChangeDetectorRef funcionó.

De la documentación de Angular; así es como puedes implementarlo

    class GiantList {
      constructor(private ref: ChangeDetectorRef, public dataProvider: DataListProvider) {
        ref.detach();
        setInterval(() => { this.ref.detectChanges(); }, 5000);
      }
    }

https://angular.io/api/core/ChangeDetectorRef

Artículos relacionados