I've been trying to render certain template related to a state and component according to this article
In my project running under dev-server it all works fine and when I execute $state.go("home")
the component template is loaded how I expect but when I do this in a testing environment, this doesn't work.
Before, in testing, when I use the "old way" using "template" instead "component" with ui-router, execute $rootScope.$digest()
was enough for add the template inside the <div ui-view></div>
but using this new way this doesn't work anymore.
What am I doing wrong?
Edit: I've been trying to deeply understand the problem and I see that the problem is related to the HTTP request that was done. Maybe it's related to the way that my promise resolves on the resolve callback using async/await. Please check the 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 } }
Router
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) }) })
The HTML result that not rendering
<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>
Also, I traced the stateChange before the 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")
I see a problem in a transition but no reason was given.
========================================================================
Edit 2 Finally we found the problem but I can't figure out the real problem. I created a branch in my project for showing the problem. This it's related to async/await
javascript feature:
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) // } }
The repository
So my new Questions are:
Edit 3 The issue 3522 reported in angular UI router repository
The issue is that angular expects an angular Promise that's why your then will work but your await won't, you can solve this by using a library like: https://www.npmjs.com/package/angular-async-await or make a construction like they're demonstrating here https://medium.com/@alSkachkov/using-async-await-function-in-angular-1-5-babel-6-387f7c43948c
Good luck with your problem!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With