Skip to content

Problem with async/awaits and ui-router #3522

@glabrat

Description

@glabrat

This is a:

  • Bug Report

Bug Report

Current Behavior:

Angular-ui-router doesn't recognize the way the service resolves the promise, making imposible the HTML to render when I try to use async/awaits, but when I use normal promises, this works.

The problem: Async/Awaits vs Normal promise

 //This doesn't work
 async getTodos() \{
 const apiResponse = await this.http.get(this.url)
 return apiResponse.data.todos
 }
//this works
getTodos() \{
return this.http.get(this.url).then(res => res.data.todos)
}

The 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", () => {
    const todosHttpResponse = require(`${__dirname}/../../stubs/todos_get.json`)
    const treeDOMBody = document.querySelectorAll("body")[0]

    //Register once time all the ones involved in the scene on the angular module system
    beforeAll(() => {
        angular
            .module("Test", [
                "ui.router"
            ])
            .config(routes)
            .constant("BASE_URL", "http://localhost:5000/api")
            .component("todoList", TodoListComponent)
            .service("TodoService", TodoService)
    })
    //Inject the "Test" module. (@TODO: Research why this module injection it's necessary en each test)
    beforeEach(angular.mock.module("Test"))
    //Configure the basic setup for each test
    beforeEach(inject(($rootScope, $compile, $location, $httpBackend) => {
        //@TODO: It's possible inject the $httpBackend once instead in each test?
        //1st Building the scene: register the http interceptor
        $httpBackend
            .whenGET(/.+\/todos/)
            .respond((method, url, data, headers, params) => {
                return [200, todosHttpResponse]
            })
        //2nd Building the scene: render the root element of scene. In this case
        //it is the ui-router view
        const componentDOMelement = angular.element("<div ui-view></div>")
        //@TODO: It's possible avoid this js-dom hack?
        document.body.appendChild(componentDOMelement[0])
        //compile to the tell angular that render the generated element
        //in the js-DOM
        $compile(componentDOMelement)($rootScope.$new())

        //3rd Building the scene: go to the root location.
        $location.url("/")
        //Because in the root location exists a http resolution involved
        //(the resolve configuration for the "home" state)
        $httpBackend.flush()

        //Show the html generated
        console.log(document.querySelectorAll("body")[0].outerHTML)


    }))

    //After each test clean the ui-view
    afterEach(() => {
        treeDOMBody.querySelectorAll("div[ui-view]")[0].remove()
    })

    it("Should be render a todo list based on the httpResponse", () => {
        const todosHTMLNodeList = treeDOMBody.querySelectorAll(".todo-item")
        const todosHttpResponseLength = todosHttpResponse.todos.length
        expect(todosHTMLNodeList.length).toBe(todosHttpResponseLength)
    })
    it("Should be delete a todo when a todo item was clicked on the delete button", () => {
        let todosHTMLNodeList = treeDOMBody.querySelectorAll(".todo-item")
        let targetTodo = todosHTMLNodeList[3]
        let todosInitialLenth = todosHTMLNodeList.length
        let todosListNewLength

        targetTodo.querySelectorAll(".btn-delete-todo")[0].click()
        todosHTMLNodeList = treeDOMBody.querySelectorAll(".todo-item")
        todosListNewLength = todosHTMLNodeList.length

        expect(todosListNewLength).toEqual(todosInitialLenth - 1)

        targetTodo = todosHTMLNodeList[0]
        targetTodo.querySelectorAll(".btn-delete-todo")[0].click()
        todosHTMLNodeList = treeDOMBody.querySelectorAll(".todo-item")
        todosListNewLength = todosHTMLNodeList.length

        expect(todosListNewLength).toEqual(todosInitialLenth - 2)
    })
    it("Should be toggle a todo item if the checkbox it's pressed", () => {
        let todosHTMLNodeList = treeDOMBody.querySelectorAll(".todo-item")
        let targetTodo = todosHTMLNodeList[0]
        let targetTodoCheckbox = targetTodo.querySelectorAll("input[type='checkbox']")[0]
        let todoInitialCheckedState = targetTodoCheckbox.checked
        let todoNewCheckedState

        targetTodoCheckbox.click()
        todoNewCheckedState = targetTodoCheckbox.checked

        expect(todoNewCheckedState).toEqual(!todoInitialCheckedState)

        todoInitialCheckedState = todoNewCheckedState

        targetTodoCheckbox.click()
        todoNewCheckedState = targetTodoCheckbox.checked

        expect(todoNewCheckedState).toEqual(!todoInitialCheckedState)
    })

})

The HTML render error

 TodoListComponent rendering and interaction on '/' base path › Should be toggle a todo item if the checkbox it's pressed

    TypeError: Cannot read property 'querySelectorAll' of undefined
      
      at Object.<anonymous> (test/integration/state_home_spec.js:84:44)

  TodoListComponent rendering and interaction on '/' base path
    ✕ Should be render a todo list based on the httpResponse (117ms)
    ✕ Should be delete a todo when a todo item was clicked on the delete button (25ms)
    ✕ Should be toggle a todo item if the checkbox it's pressed (28ms)

  console.log test/integration/state_home_spec.js:47
    <body><!-- uiView: --><div ui-view="" class="ng-scope"></div></body>

  console.log test/integration/state_home_spec.js:47
    <body><!-- uiView: --><!-- uiView: --><div ui-view="" class="ng-scope"></div></body>

  console.log test/integration/state_home_spec.js:47
    <body><!-- uiView: --><!-- uiView: --><!-- uiView: --><div ui-view="" class="ng-scope"></div></body>

Expected Behavior:

<body><!-- uiView: --><div ui-view="" class="ng-scope"><todo-list todos-list="::$resolve.todosList" class="ng-scope ng-isolate-scope"><div class="todo-list">
        <!-- ngRepeat: todo in $ctrl.todosList track by $index --><div class="todo-item ng-scope" ng-repeat="todo in $ctrl.todosList track by $index">
            <label class="ng-binding">
                <input type="checkbox" ng-model="todo.completed" class="ng-pristine ng-untouched ng-valid ng-not-empty"> Learn Programming using component based approach
                <button class="btn-delete-todo" ng-click="$ctrl.deleteTodo($index)">x</button>
            </label>
        </div><!-- end ngRepeat: todo in $ctrl.todosList track by $index --><div class="todo-item ng-scope" ng-repeat="todo in $ctrl.todosList track by $index">
            <label class="ng-binding">
                <input type="checkbox" ng-model="todo.completed" class="ng-pristine ng-untouched ng-valid ng-empty"> Learn Machine Learning
                <button class="btn-delete-todo" ng-click="$ctrl.deleteTodo($index)">x</button>
            </label>
        </div><!-- end ngRepeat: todo in $ctrl.todosList track by $index --><div class="todo-item ng-scope" ng-repeat="todo in $ctrl.todosList track by $index">
            <label class="ng-binding">
                <input type="checkbox" ng-model="todo.completed" class="ng-pristine ng-untouched ng-valid ng-not-empty"> Finish Medium article
                <button class="btn-delete-todo" ng-click="$ctrl.deleteTodo($index)">x</button>
            </label>
        </div><!-- end ngRepeat: todo in $ctrl.todosList track by $index --><div class="todo-item ng-scope" ng-repeat="todo in $ctrl.todosList track by $index">
            <label class="ng-binding">
                <input type="checkbox" ng-model="todo.completed" class="ng-pristine ng-untouched ng-valid ng-empty"> Learn to play Jazz music
                <button class="btn-delete-todo" ng-click="$ctrl.deleteTodo($index)">x</button>
            </label>
        </div><!-- end ngRepeat: todo in $ctrl.todosList track by $index --><div class="todo-item ng-scope" ng-repeat="todo in $ctrl.todosList track by $index">
            <label class="ng-binding">
                <input type="checkbox" ng-model="todo.completed" class="ng-pristine ng-untouched ng-valid ng-empty"> Build a experimental app using google A.I.
                <button class="btn-delete-todo" ng-click="$ctrl.deleteTodo($index)">x</button>
            </label>
        </div><!-- end ngRepeat: todo in $ctrl.todosList track by $index -->
    </div>
    </todo-list></div></body>

Link to reproduce the issue:

https://github.com/gpincheiraa/angularjs-tdd-jest/tree/async/await-integration-doesnt-work

https://stackoverflow.com/questions/45863496/angular-ui-router-doesnt-process-the-resolve-function-when-i-use-async-await-fu?noredirect=1#comment78754409_45863496

General Query

  • I have already asked my question on StackOverflow and nobody could answer the question

  • I have already reviewed the sample application for examples of common approaches

  • I believe my question can only be answered by the UI-Router maintainers

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions