38 votes

Le routeur Angular UI ne traite pas la fonction resolve lorsque j'utilise la fonction async/await ?

J'ai essayé de rendre certains modèles liés à un état et à un composant, comme suit article

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

Auparavant, lors des tests, lorsque j'utilisais "l'ancienne méthode" en utilisant "template" au lieu de "component" avec ui-router, j'exécutais $rootScope.$digest() était suffisant pour ajouter le modèle à l'intérieur du <div ui-view></div> mais en utilisant cette nouvelle méthode, cela ne fonctionne plus.

Qu'est-ce que je fais de mal ?

Editar : J'ai essayé de comprendre profondément le problème et je vois que le problème est lié à la requête HTTP qui a été faite. Peut-être est-ce lié à la façon dont ma promesse résout le callback de résolution en utilisant async/await. Veuillez vérifier le service :

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>

De plus, j'ai tracé le stateChange 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 2 Nous avons enfin 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é à async/await fonction javascript :

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

Mes nouvelles questions sont donc :

  • Pourquoi la façon dont j'utilise la fonctionnalité async/await n'est pas compatible avec la résolution de l'ui-router dans l'environnement de test mais fonctionne dans le code de production ?
  • Peut-être est-ce lié à l'appel de $httpBackend.flush() ?

Edit 3 La question 3522 signalé dans le dépôt de routeur angular UI

1voto

jovi De Croock Points 346

Le problème est qu'angular attend une Promise angulaire, c'est pourquoi votre then fonctionnera mais pas votre await. Vous pouvez résoudre ce problème en utilisant une bibliothèque comme : https://www.npmjs.com/package/angular-async-await 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 !

0voto

AL the X Points 352

C'est juste une supposition fondée sur ma compréhension de la façon dont resolve - et ce qu'ils font ngMock fait. Dans votre premier exemple, votre resolve para getTodos ne revient pas avant que le $http a été résolue, à ce moment-là vous arrachez la valeur de la réponse et la renvoyez. Cependant, resolve -Les personnes attendent un $q.Promise comme une sentinelle pour maintenir le rendu du routeur jusqu'à ce qu'il soit résolu. Dans votre code, en fonction de la façon dont il est transposé, la balise await y return ne produit probablement pas la valeur sentinelle correcte, il est donc traité comme une réponse synchrone.

Une façon de tester serait de demander à la resolve -er dans le contrôleur pour votre todolist et inspecter la valeur. Je parie que c'est PAS a $q.Promise bien qu'il puisse s'agir d'une promesse indigène.

Lorsque vous utilisez resolve mais il suffit d'enchaîner la promesse en ajoutant une balise then et le rendre. Le routeur se chargera du reste.

Ou mieux, passez aux Observables ! (/me duck incoming tomatoes)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X