4

I a have a question for you guys. I am using Angular 2, set up with Angular-CLI, with the UiRouter (ui-router-ng2). All of which are fantastic, and I am making progress. But, I've run into a problem, that I'm sure is easily resolvable, I just don't know how to do it.

So I have a "menu service" which grabs categories from a mock db I've set up. The service works great, and I am able to list out the categories, and use those categories to get sub categories and items. I am using URL-less paramaters, i.e. I am declaring them as objects, and passing them using [UiParams]. All of that is working well, and I can even use a back button 'uiSref="^"' to get to the parent state. All awesome. BUT! For testing purposes, I am using the ui-router-visualizer, and I've come into the issue where I can still transition into a state, even though the object is empty. See, from my understanding, the object is resolved, and returned empty. This prevents errors, surely, but if a user magically ends up in a sub state, an empty object is placed in, then nothing shows. This causes errors in its own right, since the view has nothing to interpolate upon. This also leads to the back button malfunctioning, since ui-params aren't available.

Now, all of that being said, how do I reject the state transition if the return object from my menu service is empty?

CODE For the state. I am using Ng2StateDeclaration for this.

{
    name:'category',
    parent:'main.categories',
    params:{
        categoryid:{}
    },
    views:{
        '$default@main': { component: SubcategoryComponent },
        receipt: { component: ReceiptComponent }
    },
    resolve:[
        {
            token:'category',
            deps: [Transition, MenuService],
            resolveFn: (trans, menuSvc) => {
                menuSvc.getCategory(trans.params().categoryid)
            }

        }
    ]
}`

Here is the code for the menu service in its entirety. It's super simple.

import { Injectable, Inject } from '@angular/core';
import { Headers, Http, Response} from '@angular/http';
import 'rxjs/add/operator/toPromise';

@Injectable()
export class MenuService {

constructor(@Inject(Http) public http:Http) { }

getCategories() {
    return this.http.get('api/menu')
            .map(this.extractData)
            .toPromise();

}
private extractData(res: Response) {
    let body = res.json();
    return body.data || { };
}
getCategory(id){
    console.log(id);
    return this.getCategories()
        .then(cats => cats.find(cat => cat.id == id));
}
}

And also the component, also very basic. I am using their quickstart-app as my main learning tool.

import { Component, Input, Inject } from '@angular/core';

@Component({
  templateUrl: './subcategory.component.html',
  styleUrls: ['./subcategory.component.css'],
    inputs: ["category"],
})
export class SubcategoryComponent {

category;
}

If anyone could either point me to somewhere that answers this question, or tell me where and how to put in an If/Else statement that returns either the object or rejects the state transition, that'd be amazing. I am also structuring things like the quickstart example, so this is all housed within a separate module from the main app's module.

I am still very green to this whole process, so I am not too sure on where exactly to do work when it comes to business logic controlling state transitions.

Thanks in advance!

2 Answers2

2

Now, all of that being said, how do I reject the state transition if the return object from my menu service is empty?

If the resolve function returns a rejected promise, the transition will err and not run. Since your getCategory function returns and empty object if it is not found (instead of a rejected promise), you will either have to reject it in the service, or in the resolve.

Either way, you can write a helper like this:

const rejectWhenEmpty = (val) =>
    val ? val : Promise.reject('value is empty)

and then use it to reject the resolve when no value was loaded

        resolveFn: (trans, menuSvc) =>
            menuSvc.getCategory(trans.params().categoryid).then(rejectWhenEmpty)

or in the service:

getCategory(id){
    return this.getCategories()
        .then(cats => cats.find(cat => cat.id == id))
        .then(rejectWhenEmpty);
}
Chris T
  • 8,186
  • 2
  • 29
  • 39
1

So I have solved my own question. It may not be the best way, so I am always open to a better solution. But it works for me, for now.

I imported StateService from ui-router-ng2

import {Ng2StateDeclaration,Transition, StateService} from 'ui-router-ng2';

Then I take the returned item, the state service, and trans.from() parameter and pass it to a second function via .then

deps: [Transition, MenuService, StateService],
            resolveFn: (trans, itemSvc, $state) => {
                return itemSvc.getItems(trans.params().groupid).then(items =>{
                    return stateContinue(items, $state, trans.from());
                })
            }

The state continue function is

function stateContinue(thing, $state, from){
if (!isEmpty(thing)){
    return thing;
}else {
    $state.go(from);
}}

This if the "thing" isn't empty, it will return the "thing". Else you will be returned to you previous state/won't leave the current state.

And the isEmpty function is

function isEmpty(obj) {
if (obj == null) return true;
if (obj.length > 0)    return false;
if (obj.length === 0)  return true;
if (typeof obj !== "object") return true;
for (var key in obj) {
    if (hasOwnProperty.call(obj, key)) return false;
}

return true; }

Which I unabashedly took from this is-empty-object q/a

Again, if anyone has a better solution, I am all for it. Like for instance how to actually tell the transition to be declined without forcing a state change.

Thanks!

Community
  • 1
  • 1