0

Angular 1.6

I develop a dashboard application. There are two statically defined ui.router states for the registered dashboard components: home-dashboard and other-dashboard.

Now I want to define ui.router states dynamically based on dashboards data. For this, I do a loop inside app.config. But in order to get dashboards data, StorageService provider should be injected into the config.

The error received is:

Error: [$injector:unpr] Unknown provider: StorageService

How to inject provider? Is there a better way to achieve my goal?


Also, I tried to move $stateProvider into dashboardController, the parent controller. By attaching it to app inside app.config, like app.stateProvider = $stateProvider; and exporting the app placing return default app; in the end of app.js file.

The error I got was 'return' outside of function.


Provider services/storage.service.js(it is a class which simulates API, in the future it will get data from DB):

class Storage {

  constructor () {
      this.dashboards = {
        'home': {
            id: '1',
            name: 'Home',
        view: 'home',
        url: '/home',
        component: 'homeDashboard',
            widgets: [{
                col: 0,
                row: 0,
                sizeY: 1,
                sizeX: 1,
                name: "Widget 1"
            }, {
                col: 2,
                row: 1,
                sizeY: 1,
                sizeX: 1,
                name: "Widget 2"
            }]
        },
        'other': {
            id: '2',
            name: 'Other',
        view: 'other',
        url: '/other',
        component: 'otherDashboard',
            widgets: [{
                col: 1,
                row: 1,
                sizeY: 1,
                sizeX: 2,
                name: "Other Widget 1"
            }, {
                col: 1,
                row: 3,
                sizeY: 1,
                sizeX: 1,
                name: "Other Widget 2"
            }]
        }
      };
  }

  saveDashboards(dashboards) {
    this.dashboards = dashboards;
  }

  listDashboards() {
    return this.dashboards;
  }

  $get() {
    return this.dashboards;
  }
}

export { Storage };

app.js

import { DashboardCtrl } from './controllers/dashboardController';

import { homeDashboard } from './dashboards/home/homeDashboard.component';
import { otherDashboard } from './dashboards/other/otherDashboard.component';
import { aWidget } from './widgets/a_widget/aWidget.component';

import { Storage } from './services/storage.service.js';
import { Object2Array } from './filters/object2Array.js';

const app = angular.module('dashboardApp', [
    'ui.router',
    'ui.bootstrap',
    'gridster'
])
.controller('DashboardCtrl', DashboardCtrl)
.component('aWidget', aWidget)
.component('homeDashboard', homeDashboard)
.component('otherDashboard', otherDashboard)
//.factory('StorageService', () => new Storage())
.provider('StorageService', Storage)
.filter('object2Array', Object2Array);

app.config(function ($urlRouterProvider, $stateProvider, StorageService) {

  const dashboards = StorageService.listDashboards();

  _.forEach(dashboards, function (d) {
    $stateProvider.state({
      name: d.view,
      url: d.url,
      component: d.component
    });
  });

  /*
  const homeState = {
    name: 'home',
    url: '/home',
    component: 'homeDashboard'
  };

  const otherState = {
    name: 'other',
    url: '/other',
    component: 'otherDashboard'
  };

  $stateProvider.state(homeState);  
  $stateProvider.state(otherState); 
   */

  $urlRouterProvider.otherwise('/home');
});

App tree:

../angular-dashboard/
├── LICENSE
├── README.md
├── dist
├── package.json
├── src
│   ├── app
│   │   ├── app.js
│   │   ├── controllers
│   │   │   └── dashboardController.js
│   │   ├── dashboards
│   │   │   ├── home
│   │   │   │   ├── homeDashboard.component.js
│   │   │   │   ├── homeDashboard.controller.js
│   │   │   │   └── templates
│   │   │   │       └── homeDashboard.template.html
│   │   │   └── other
│   │   │       ├── otherDashboard.component.js
│   │   │       ├── otherDashboard.controller.js
│   │   │       └── templates
│   │   │           └── otherDashboard.template.html
│   │   ├── filters
│   │   │   └── object2Array.js
│   │   ├── services
│   │   │   └── storage.service.js
│   │   └── widgets
│   │       └── a_widget
│   │           ├── aWidget.component.js
│   │           ├── aWidget.controller.js
│   │           ├── aWidget.settings.controller.js
│   │           └── templates
│   │               ├── aWidget.settings.template.html
│   │               └── aWidget.template.html
│   ├── index.html
│   └── style
│       ├── style-common.css
│       └── style.css
└── webpack.config.js

15 directories, 22 files

UI look: enter image description here

srgbnd
  • 5,404
  • 9
  • 44
  • 80
  • Basically same issue as https://stackoverflow.com/questions/46150394/how-to-inject-provider-in-app-config . Wrong service type was chosen. – Estus Flask Sep 12 '17 at 23:50

2 Answers2

0

StorageService is provider service , and it is available as StorageServiceProvider service provider during config phase and as StorageService service instance during run phase.

When being injected as StorageServiceProvider it will be an instance of Storage class, and when being injected as StorageService it it will be return value of $get method (dashboards object).

provider, factory and service services are needed when service instance depends on other services. Otherwise constant or value service may be used.

Storage class is overkill because all it does is providing accessor methods for plain object. It can be constant service which will be available during both config and run phase under same name:

app.constant('StorageService', {
  home: { ... },
  ...
});
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • StorageService is a simulation of API. In the future I want to get the dasboards data from DB – srgbnd Sep 13 '17 at 05:52
  • Is it a bad practice to query DB in app.config? If yes, what should I do instead? – srgbnd Sep 13 '17 at 05:57
  • @trex This wasn't mentioned in the question. But yes. it is. DB request is asynchronous, isn't it? This will result in race condition, because app initialization will be completed before DB request completion. When this is data that should be used in app controllers only, this is done with route resolvers. But when this data is routing data that should be already available in `config`, it should be available before `angular.bootstrap` is called. I.e. you do DB request first, set StorageService `app.constant('StorageService', ...)` dynamically and only then call `angular.bootstrap`. – Estus Flask Sep 13 '17 at 06:19
  • The DB requests will be asynchronous. Do you say I have to define my DB request service as app.constant? – srgbnd Sep 13 '17 at 06:24
  • This depends. You will have to define it's **result** as app.constant. What kind of DB request are we talking? Local IndexDb or remote API call? – Estus Flask Sep 13 '17 at 06:26
  • Remote API call. The StorageService will return promise. – srgbnd Sep 13 '17 at 06:29
  • The Angular-friendly way is to make DB request on server side and provide DB req result in server-side page template as a global, i.e. `` and pick it up in Angular app. The alternative is to have **two** apps, the first one does DB request and bootstraps the second (main) one. See for example, https://stackoverflow.com/a/41660722/3731501 . This design issue was fixed in Angular 2, but in AngularJS this is how it should be done. – Estus Flask Sep 13 '17 at 06:35
0

It is possible to define routes directly in a controller.

First, we need to provide $stateProvider for the controller.

...
app.config(function ($urlRouterProvider, $stateProvider) {
  app.stateProvider = $stateProvider;
  $urlRouterProvider.otherwise('/home');
});

export default app;

Second, we define states inside the controller.

import app from '../app';
...
const DashboardCtrl = function ($scope, $timeout, StorageService) {
...
  forEach($scope.dashboards, function (dashboard) {
    app.stateProvider.state({
      name: dashboard.name,
      url: dashboard.url,
      component: dashboard.component
    });
  });
});
srgbnd
  • 5,404
  • 9
  • 44
  • 80