3

for a given webapp -for example if facebook- , i want to split into different modules

each module are only depended on core, and unaware of anyother component -so that in future if i want to add or remove a feature all i have to do is develope this feature with its html and js and directives what ever inside it self, and just add dependency in core module and im good to go.

for example app will contain 1. wall "path = "/components/wall.js" 2. user profile " /components/profile.js" 3. chat "/components/chat.js"

all need to know current logged in user details, so may be the core module will handle this by exposing user details to $rootScope. "/core.js"

then entire app user must be logged in to access, so i will let core.js handle all the authentication

EDIT TO NARROW DOWN Question

var core = angular.module("core", ['core.wall']);

var wall = angular.module("core.wall", []);

Now core DEPENDE on wall, yet in my design , wall is the one that depends on core. yet at same time, core routing is altered by core.wall since wall should declare its own routing rules.

is this dependency injection doable ?

Zalaboza
  • 8,899
  • 16
  • 77
  • 142
  • This is too broad of a topic for StackOverflow and you're asking too many different questions. There are many Angular style guides online, but ultimately these things come down to preference. – Anid Monsur Oct 01 '15 at 19:15
  • @AnidMonsur sorry i was a bit brain storming first. i added a small edit that conclude the question in 4 lines ! – Zalaboza Oct 01 '15 at 19:20
  • This looks like [circular dependency](http://stackoverflow.com/questions/19344214/problems-with-circular-dependency-and-oop-in-angularjs) and Angular is likely to return an error if attempted. – Boaz Oct 01 '15 at 19:27
  • @Boaz exactly, that why i tagged question `circular-reference`, so how does people organize code in such situation ? – Zalaboza Oct 01 '15 at 19:29
  • 1
    There are a lot of similarities to this approach and John Papa's hot towel, but he injects each module into the main app module. You might be able to get some insight by taking a look at that. – Mike Feltman Oct 01 '15 at 19:31

1 Answers1

1

so i didnt get any answers but after lots of playing around i figured out a solution.

Angularjs DI (dependency injection) for modules works as follows.

  1. when you bootstrap a module -automatic using ng-app="module" or manually by triggering angular.bootstrap(document,"module"); the first thing that happens is that all its dependencies are looped and all config blocks run.

//i created a github example https://github.com/alzalabany/angular-material-modular-starter/

example :

angular.module('zalabany',['app.core','app.wall','app.blog']);
angular.bootstrap(document, ['zalabany']);
//1-->app.core config run, app.wall config run, app.blog config run, zalabany.config runs last
//2-->then .run blocks run in same order.

so since we are trying to be modular in nature. the core as i explained my question should depend on no other app module yet all other modules depends on it. so to do this, i used an intermediate module, that link them together.

Final code. -will upload git soon-

///Linking module, sole function is to connect all modules sharing same $rootScope and making sure system loads in correct order
angular.module('zalabany',['app.core','app.wall','app.blog']);

//I'm the Core.js i depend on external libs only. i'm not aware of my own system modules.
angular.module('app.core', ['ui.router', 'ngAnimate', 'toastr', 'ngMaterial','ngMdIcons'])
.config(function($stateProvider, $urlRouterProvider, $httpProvider) {
    ///http interceptor in case 403,401 on any request, redirect to login right away.
    $httpProvider.interceptors.push(function($q, $rootScope, $injector, $timeout, $window) {
        var toastr, $state, $http;
        //timeout is used to prevent circuler dependency error !
        $timeout(function() {
            toastr = $injector.get('toastr');
            $http = $injector.get('$http');
            $state = $injector.get('$state');
        });
        return {
            responseError: function(rejection) {
                if (rejection.data && rejection.data.hasOwnProperty('message')) {
                    toastr.error('request failed. try again later');
                }
                if (rejection.status === 401 || rejection.status === 403) {
                    console.log('rejected and redirecting', rejection);
                    $state.go('login');
                }
                return $q.reject(rejection);
            }
        };
    });

    $urlRouterProvider.otherwise("/login");

    $stateProvider
    .state('login', {
        url: "/login",
        templateUrl: "modules/core/login.html"
    });

    console.log('im config core');

})

.controller('loginCtrl', function($scope,$user,$rootScope){//remember $user is available every where
    var self=this;
    this.username = $user.username;


    if($user.token && $user.id){
        //validate token by sending get to auth.
        $http.defaults.headers.common["auth-token"] = $user.token;
        $http.defaults.headers.common["auth-uid"] = $user.id;

        $http.get($oauth).then(function(){$rootScope.Login($user);},$rootScope.Logout);

    }

    this.login= function(){
        $http.post($oauth,{username:self.username,password:self.password})
            .then(function(r){
                $rootScope.Login(r); //use login method on root to expose it for all modules
            });
    }
})

.run(function($rootScope, $state, $user,$http,$oauth) {
    ///$rootscope is shared between all modules. so i use it for sharing auth data since its just an object.
    $rootScope.$user = $user;

    $rootScope.$homepage = null;
    //default home page of appchild should overide it;
    //all children modules can edit this.


    ///FUNTION 1.
    ///LISTEN FOR ROUTE CHANGE AND PREVENT IF USER IS NOT LOGGED IN
    $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {

        if (!$rootScope.$user.hasOwnProperty('token') && toState.name !== 'login') {
            console.log('prevented');
            event.preventDefault();
            $state.go('login');
        }
    });


    $rootScope.Login = function(r){
    // login login ...
      $http.defaults.headers.common["auth-uid"] = r.token;
        angular.module('zalabany').value('$user',r);

        console.log('oki lets go hom',$state.go($rootScope.$homepage));
    }

    $rootScope.Logout = function(){
        window.localStorage.clear();
        $state.go('login');
    }

});


///BOOTSTRAP
$(function(){
    $.getJSON('PING API WITH TOKEN FROM LOCALSTORAGE', function(json) {
        $user = json || data;//json is our user record
    }).fail(function() {
        $user=data;
        window.localStorage.clear();
        console.log( "login error" );
    }).always(function() {
        angular.module('app.core')
                .value('$user',$user);
        angular.bootstrap(document, ['zalabany']); //bootstrap the link module
    });
});

from now on to adding new component to my app is as easy as

angular.module('app.wall', ['app.core'])
.config(function($stateProvider, $urlRouterProvider, $httpProvider) {

    $stateProvider
    .state('wall', {
        url: "/wall",
        templateUrl: "modules/wall/wall.html"
    });
})

.run(function($rootScope, $state, $user,$http,$oauth) {
    $rootScope.$homepage = 'wall';//Set homepage to me
    // or i can use
    $rootScope.$homepage = $rootScope.$homepage || 'wall';//set homepage to me if no other modules declared it it.

   //Push me into the sidebar.
   $rootScope.$sidebar.push({order:1, link:'/wall',title:'Feed',icon:'fa fa-star'});
});

Benefit of this structure :- 1. i hv $user info available for all modules, 2. module push it self to sidebar , 3. routes are declared inside each module in its own config block. 4. adding new module i create new folder, add link to linking module "zalabany in this example". and i'm good to go, with authentication and everything :-)

Zalaboza
  • 8,899
  • 16
  • 77
  • 142