web-dev-qa-db-fra.com

Angular Le routeur de l'interface utilisateur ne traite pas la fonction de résolution lorsque j'utilise la fonctionnalité async/wait?

J'ai essayé de rendre certains modèles liés à un état et à un composant conformément à cet article article

Dans mon projet fonctionnant sous dev-server, tout fonctionne correctement et lorsque j'exécute $state.go("home"), le modèle de composant est chargé comme prévu, mais lorsque je le fais dans un environnement de test, cela ne fonctionne pas. 

Auparavant, lors des tests, lorsque j'utilisais "l'ancienne méthode" en utilisant "modèle" à la place "composant" avec ui-router, exécuter $rootScope.$digest() suffisait pour ajouter le modèle dans le <div ui-view></div>, mais en utilisant cette nouvelle méthode, cela ne fonctionnait plus.

Qu'est-ce que je fais mal?

Edit: J'ai essayé de comprendre le problème en profondeur et je constate que le problème est lié à la requête HTTP effectuée. Peut-être est-ce lié à la façon dont ma promesse résout le rappel de résolution en utilisant async/wait. S'il vous plaît vérifier le service:

Un service

export class TodoService {
    constructor($http, BASE_URL) {
        this.http = $http;
        this.url = `${BASE_URL}/todos`
    }
    async getTodos() {
        const apiResponse = await this.http.get(this.url)
        return apiResponse.data.todos
    }
}

Routeur

import '@uirouter/angularjs'

export function routes($stateProvider, $locationProvider) {
    $locationProvider.html5Mode({
        enabled: true,
        requireBase: false,
        rewriteLinks: true,
    })

    $stateProvider
        .state("home", {
            url: "/",
            component: "todoList",
            resolve: {
                todosList: TodoService => TodoService.getTodos()
            }
        })
}

Test

import { routes } from "routes"
import { TodoListComponent } from "components/todoList.component"
import { TodoService } from "services/todo.service"

describe("TodoListComponent rendering and interaction on '/' base path", () => {
    let componentDOMelement
    let stateService

    beforeAll(() => {
        angular
            .module("Test", [
                "ui.router"
            ])
            .config(routes)
            .constant("BASE_URL", "http://localhost:5000/api")
            .component("todoList", TodoListComponent)
            .service("TodoService", TodoService)
            //I enable this for better logs about the problem
            .run(['$rootScope','$trace', function($rootScope, $trace) {
               $trace.enable("TRANSITION")
             }])
    })
    beforeEach(angular.mock.module("Test"))

    beforeEach(inject(($rootScope, $compile, $state, $httpBackend) => {
        //build the scene
        //1st render the root element of scene: We needs a router view for load the base path
        let scope = $rootScope.$new()
        componentDOMelement = angular.element("<div ui-view></div>")

        $compile(componentDOMelement)(scope)
        scope.$digest()
        
         document.body.appendChild(componentDOMelement[0]) //This is a hack for jsdom before the $rootScope.$digest() call
        //2nd let's create a fake server for intercept the http requests and fake the responses
        const todosResponse = require(`${__dirname}/../../stubs/todos_get.json`)
        $httpBackend
            .whenGET(/.+\/todos/)
            .respond((method, url, data, headers, params) => {
                return [200, todosResponse]
            })

        //3rd Let's generate the basic scenario: Go at home state ("/" path)
        $state.go("home")
        $rootScope.$digest()
        $httpBackend.flush()
    }))

    it("Should be render a list", () => {
        console.log("HTML rendered")
        console.log(document.querySelectorAll("html")[0].outerHTML)
    })
})

Le résultat HTML qui ne rend pas

<html>
<head>
<style type="text/css">
@charset "UTF-8";[ng\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate) {
  display:none !important;
}
ng\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{
  position:absolute;
}
</style>
</head>
<body><!-- uiView: -->
</body>
</html>

En outre, j'ai tracé l'étatChange avant le HTML:

console.log node_modules/@uirouter/core/_bundles/ui-router-core.js:1276
    Transition #0-0: Started  -> "Transition#0( ''{} -> 'home'{} )"

console.log node_modules/@uirouter/core/_bundles/ui-router-core.js:1282
    Transition #1-0: Ignored  <> "Transition#1( ''{} -> 'home'{} )"

console.log node_modules/@uirouter/core/_bundles/ui-router-core.js:1313
    Transition #1-0: <- Rejected "Transition#1( ''{} -> 'home'{} )", reason: Transition Rejection($id: 0 type: 5, message: The transition was ignored, detail: "undefined")

Je vois un problème dans une transition mais aucune raison n'a été donnée.

=============================================== =======================

Edit 2Enfin, nous avons trouvé le problème mais je n'arrive pas à comprendre le vrai problème. J'ai créé une branche dans mon projet pour montrer le problème. Ceci est lié à la fonctionnalité javascript async/await:

export class TodoService {
    constructor($http, BASE_URL) {
        this.http = $http;
        this.url = `${BASE_URL}/todos`
    }
    //Interchange the comment on the getTodos method and run `npm run tdd` for see the problem:
    //When async/await doesn't used, the html associated to the resolve in the
    // "/" route that used this service, the promise was resolved that expected.
    //The idea for this branch it's research about the problem and propose a way
    //for we can use async/await on the production code and on the testing environment
    async getTodos() {
        const apiResponse = await this.http.get(this.url)
        return apiResponse.data.todos
    }
    // getTodos() {
    //     return this.http.get(this.url).then(res => res.data.todos)
    // }
}

Le référentiel

Donc mes nouvelles questions sont:

  • Pourquoi la façon dont j'utilise la fonctionnalité async/wait n'est-elle pas compatible avec la résolution du routeur ui sur l'environnement de test, mais fonctionne-t-elle avec le code de production?
  • Peut-être que c'est lié à l'appel $ httpBackend.flush ()?

Edit 3 Le problème 3522 signalé dans le référentiel de routeurs d'interface utilisateur angulaire

Le problème, c’est que angular s’attend à une promesse angulaire, c’est la raison pour laquelle votre travail fonctionnera mais que votre attente ne fonctionnera pas. Vous pouvez résoudre ce problème en utilisant une bibliothèque du type: https://www.npmjs.com/package/angular-async- attendre ou faire une construction comme ils le démontrent ici https://medium.com/@alSkachkov/using-async-await-function-in-angular-1-5-babel-6-387f7c43948c

Bonne chance avec votre problème!

1
jovi De Croock

Ceci est juste une supposition éclairée basée sur ma compréhension de la façon dont resolveers fonctionnent et ce que ngMock fait. Dans votre premier exemple, votre resolve pour getTodos ne sera renvoyée que lorsque la promesse $http aura été résolue. À ce stade, vous extrayerez la valeur de la réponse et vous la retournerez. Cependant, resolveers attend une valeur $q.Promise en tant que sentinelle pour conserver le rendu du routeur jusqu'à sa résolution. Dans votre code, en fonction de la manière dont il est transpilé, l'appel await et return ne produit probablement pas la valeur sentinelle correcte; il est donc traité comme une réponse synchrone.

Une méthode de test consiste à demander la variable resolve- dans le contrôleur pour votre composant todolist et à inspecter la valeur. Je parie que c'estPASun $q.Promise, bien que cela puisse être une promesse native.

Cependant, lorsque vous utilisez resolve, enchaînez simplement la promesse en ajoutant un then et renvoyez-la. Le routeur se chargera du reste.

Ou mieux, passez à Observables! (/ moi les canards arrivent des tomates)

0
AL the X