Search code examples
angularjsangularjs-scopeangular-ui-routerangular-uingboilerplate

Angular model/controller "hierarchy" setup


I am building an angular app that is essentially an admin panel for all my sites & their environments using ngboilerplate. I need to setup a kind of hierarchy of functions & model data such that any "child" controller/model doesn't work without the "parent" being set. Here is a breakdown of what I'm trying to explain.

model -> Environment (prod, stage, dev) once the env is selected you would then be able to select the site

model -> sites (all sites from current env) once a site is selected you then get the site data

model -> site (site data json contains things like page configuration values and such)

What is the proper way to setup the structure for something like this? I am currently just using individual controllers & routes (ui-router) for each within route within the page. The main functionality I need to ensure is if the environment is changed with a site selected that the site's data be reload from the proper environment. I am thinking I would be using $watch to ensure that? Any suggestions/tips on best practices on appreciated!

UPDATE: to clarify here are some specifics:

The main model I need "watched" is the environment model. Depending on which env was set I would adjust the api url being used as well as change the display name. As well it would also load the corresponding sites list for the env (currently a static json file, but it could be an api call instead). Here is the code I wrote before asking this question, when I got the the SitesCtrl I realized I was probably doing it wrong (or not optimally anyway).

Tools.js

angular.module( 'SupportBase.tools', [
  'ui.router',
  'placeholders',
  'ui.bootstrap',
  'SupportBase.tools.sites'
])

.config(function config( $stateProvider ) {
  $stateProvider.state( 'tools', {
    url: '/tools',
    views: {
      "main": {
        controller: 'ToolsCtrl',
        templateUrl: 'tools/tools.tpl.html'
      },
      "sites": {
        controller: 'SitesCtrl',
        templateUrl: 'tools/sites/sites.tpl.html'
      }
    },
    data:{ pageTitle: 'Site Tools' }
  });
})

.controller( 'ToolsCtrl', function ToolCtrl( $scope ) {
  $scope.envModel = '';

});

Tools.tpl.hmtl

    <div class="row">
    <h1 class="page-header">
    Site Tools
    <small>For the lazy and impatient. {or the smart & productive}</small>
  </h1>
</div>
<div class="row">
    <div class="well col-md-5">
        <h4>Current Working Environment:
        <code class="env">{{envModel || 'null'}}</code></h4>
        <div class="btn-group col-md-10 col-md-offset-2">
            <label class="btn btn-primary" ui-sref="tools.sites({env: envModel})" ng-model="envModel" btn-radio="'Production'">Production</label>
            <label class="btn btn-primary" ui-sref="tools.sites({env: envModel})" ng-model="envModel" btn-radio="'Stage'">Stage</label>
            <label class="btn btn-primary" ui-sref="tools.sites({env: envModel})" ng-model="envModel" btn-radio="'QA'">QA</label>
            <label class="btn btn-primary" ui-sref="tools.sites({env: envModel})" ng-model="envModel" btn-radio="'Dev'">Dev</label>
        </div>
    </div>
    <div class="col-md-6" ui-view="sites"></div>
</div>

Sites.js

angular.module('SupportBase.tools.sites', [
    'ui.router',
    'placeholders',
    'ui.bootstrap',
    'SupportBase.tools'
])

.config(function config($stateProvider) {
    $stateProvider.state('tools.sites', {
        url: '/{env:[a-z]{1,10}}/sites',
        views: {
            "sites": {
                controller: 'SitesCtrl',
                templateUrl: 'tools/sites/sites.tpl.html'
            }
        },
        data: {
            pageTitle: 'Site Tools | SupportBase'
        }
    });
})

.controller('SitesCtrl', function SitesCtrl($scope, $stateParams, $http) {
    $scope.env = $stateParams.env.toLowerCase();
    $scope.disabled = $stateParams.env !== '' ? false : true;
    if ($stateParams.env.toLowerCase() === 'production') {
        $http.get('./src/app/sites/sites.json').success(function(data) {
            $scope.sitesModel = data;
        });
    } else {
        $scope.sitesModel = [$stateParams.env, 'something', 'face'];
    }


});

Sites.tpl.html

    <div class="well" collapse="disabled">
    <h1>Site Selector</h1>
    <h2>{{sitesModel}}</h2>
    </div>

Solution

  • I don't use ui.router so I'll be very generic and you can apply as needed.

    Also I have not tested this exact code so use it as a guide.

    // setup constants to refer to and change when needed
    .constant('site_config', {
        'api_path' : '//prod.example.com',
        'name' : 'Production',
        'collection' : '/production'
    });
    
    
    
    
    .controller( 'homepage_controller', function($scope, $location, $log, site_config, environment){
    
        var load_site = function() {
            $scope.title = site_config.name;
            environment.get().then(function(data){
                $log.log('Do something with this ',data)
            })
        }
    
        // looking for sandbox.example.com 
        if(window.location.host.indexOf('sandbox') > -1 ) {
            environment.set('prod').then(function(){
                $log.log('sandbox is loaded')
                load_site()
            })
    
        // looking for qa.example.com 
        } else if (window.location.host.indexOf('qa') > -1) {
            environment.set('qa').then(function(){
                $log.log('qa is loaded')
                load_site()
            })
    
        // looking for www.example.com 
        } else {
            environment.set('prod').then(function(){
                $log.log('production is loaded')
                load_site()
            })
        }
    
    })
    
    
    
    
    
    .factory('environment', function ($window, $q, $http, $log, $rootScope, site_config) { 
        var environment = {};
    
        environment.set = function(type) {
            var deferred = $q.defer();
    
            if(type == 'sandbox') {
                site_config.api_path = '//sandbox.example.com';
                site_config.name = 'Sandbox';
                site_config.collection = '/development';
                deferred.resolve(true)
            }
            if(type == 'qa') {
                site_config.api_path = '//qa.example.com';
                site_config.name = 'QA';
                site_config.collection = '/qa';
                deferred.resolve(true)
            }
            if(type == 'production') {
                site_config.api_path = '//prod.example.com';
                site_config.name = 'Production';
                site_config.collection = '/production';
                deferred.resolve(true)
            }
            return deferred.promise;
        };
    
        // just showing this as an example
        environment.get = function() {
    
            var deferred = $q.defer();
            $http({
                method:'GET',
                url: site_config.api_path+site_config.collection
            })
            .success(function(data) {
                deferred.resolve(data);
            })
            .error(function(status, headers, config) {
                deferred.reject(false);
            });
            return deferred.promise;
        }
    
        return environment;
    })