Fix race condition with independent AngularJS controller - javascript

In my Angular application I have a main view that changes according to the route, and then a sidebar that is displayed no matter what. My HTML looks like this:
<div id="container">
<div id="sidebar" ng-controller="SidebarCtrl">(sidebar code)</div>
<div ng-view id="content">Loading...</div>
</div>
And the javascript is loaded in this order:
Vendor.js (angular, etc)
App.js (my application)
The SidebarCtrl is defined in App.js. However right when Angular loads it sees the ng-controller directive and tries to assign it to the sidebar right off the bat, leading to a "argument SidebarCtrl is not a function" error unless app.js is cached. I was thinking of having the sidebar in a separate file and loading it before Vendor.js, but the sidebar is dependent on some services defined in App.js (namely, my custom auth service):
MyApp.controller('SidebarCtrl',
['$scope', '$location', 'auth', function($scope, $location, auth) {
(...)
}])
So either way I'm at a loss.
I'm sure there is a better way of setting this up...any ideas? Can I define SidebarCtrl as a simple function but still have access to the auth service?
Thanks

You can dynamically add the ng-controller attribute to the sidebar when your app loads:
var $self = angular.element('.sidebar');
$self.attr('ng-controller', window.USER_LOGGED_IN ? 'sidebarCtrl' : 'noUserCtrl');
angular.bootstrap(angular.element('body'));
This method is useful when the controller to be used depends on the application state. In your case, angular.bootstrap should do the trick.
Source

Related

Dynamically update AngularJS component definition

I'm looking for a way to dynamically update a component, I have an object containing the information about the component (name, functions, variables, services, template, bindings, etc.). Then I generate the component dynamically using this data. And I need to regenerate the component whenever the definition is changed.
angular.module('components', [])
.component('dynamicComponent', {
bindings,
template,
controller
})
angular.module('app', ['components'])
Is there a way to update the component that has been already registered on a module? or is there a way to update the module itself?
so far I've tried recreating the components module like so:
angular.module('components', [])
.component('dynamicComponent', {
...newData
})
but nothing happens.
I also tried recompiling the element after I recreated the module. Still nothing happens.
I also tried calling angular.bootstrap after creating the modules but I found out that is not possible without destroying the element bootstrapped. It's throwing an error saying the element was already bootstrapped.
My goal here is to dynamically update the component definition, on the fly, without reloading the page.
I was able to do this by destroying the bootstrapped element, recreating that element, and calling angular.bootstrap again with the new updated modules and components.
Here's an example,
The initial code:
<body>
<div id="app" ng-controller="MainController as $ctrl">
<dynamic-component data="$ctrl.data">
</dynamic-component>
</div>
</body>
angular.module('components', [])
.component('dynamicComponent', {
bindings: {...},
controller,
template
})
angular.module('app', ['components'])
.controller('MainController', ...)
angular.bootstrap(document.querySelector('#app'), ['app'])
And to update dynamic-component,
reset and destroy the bootstrapped element. Doing so will enable us to call angular.bootsrap on this element again
document.body.innerHTML = `
<div id="app" ng-controller="MainController as $ctrl">
<dynamic-component data="$ctrl.data">
</dynamic-component>
</div>
`
Define your modules.
Here, we can update the old dynamic-component however we want to. We can also add more components or modules.
angular.module('components', [])
.component('newDynamicComponent', {
bindings: ...newBindings,
controller: newController,
template: newTemplate
})
angular.module('app', ['components', ...otherModules])
.controller('MainController', ...)
angular.boostrap(document.querySelector('app'), ['app'])
Working example
If there's a better way, please let me know.

My page turns blank when I try to use a controller in angular

I have a problem with angular to integrate a controller to my page. The page becomes blank as soon as I try to integrate it.
<div class="container-fluid" ng-app="ods-widgets" ng-controller="myCtrl" >
<ods-dataset-context context="cont" cont-domain="https://data.rennesmetropole.fr" cont-dataset="{{dataset}}">
</ods-dataset-context>
</div>
<script>
var app = angular.module("ods-widgets", []);
app.controller("myCtrl", function($scope) {
$scope.dataset= "statistiques-de-frequentation-du-site-rennes-metropole-en-acces-libre";
});
</script>
Without the controller:
http://jsfiddle.net/5c0xr8f4/13/
With the controller:
http://jsfiddle.net/8796ueyL/
ods-dataset-context is a component (https://github.com/opendatasoft/ods-widgets).
it's a component that I import via CDN.
I just want to control what is inside the cont-dataset
I looked into the library that you mentioned in your comment. It seems that the issue is that ods-widgets is already an angular module that is being imported via the CDN. If you name your own angular module with the same name, you are effectively overwriting this existing module that you have imported. So what you want to do is declare your own angular module and import ods-widgets as a dependency. You can take a look at the Fiddle for a working sample, but the important part is this one:
angular.module("myApp", ['ods-widgets']);
And in your HTML update the ng-app reference:
<div class="container-fluid" ng-app="myApp" ng-controller="myCtrl" >

AngularJS - Expression not displaying within HTML

I've gone through other questions, but couldn't find any that dynamically loaded controllers/views the way I am. I still fear this may be a duplicate question, but I have done my due diligence and came up empty. Please point me in the right direction if you're better with search terms.
This is how my app works: My index page loads up RequireJS pointing to a main.js file which outlines the initial includes (app.js, routeResolver, and a data service (unused currently). The routeResolver allows me to dynamically load in my views and respective controllers using code such as below within app.js. (Using a consistent naming convention, passing 'home' loads in home.html and associates it with homeController.js from their respective controllers/views locations.) We do not need to use ng-app='appname' because it’s added at runtime by calling angular.bootstrap() within the app.js file.
//Define routes - controllers will be loaded dynamically
var route = routeResolverProvider.route;
$routeProvider
.when('/', route.resolve('home'))
.when('/createnew', route.resolve('createnew'))
In my controller, I'm loading a variable from sessionStorage. (I have confirmed it is there/available. The test alert displays it correctly.) My problem is it is not displayed on the html page, and the console does not produce any errors. I have confirmed the page is accurately associating itself with the controller because if I remove the expression, I get an error that it is not defined... but despite it containing a value, it still doesn't display. All I get is 'Welcome '.
Controller:
'use strict';
define(['app'], function (app) {
var injectParams = ['$location', '$filter', '$window', '$timeout'];
var homeController = function ($location, $filter, $window, $timeout) {
var userTitle = sessionStorage.getItem('userTitle');
alert(userTitle);
};
homeController.$inject = injectParams;
app.register.controller('homeController', homeController);
});
View:
<div class="container-fluid text-center">
<div class="row content">
<div class="col-sm-2 sidenav">
<p>Placeholder</p>
</div>
<div class="col-sm-8 text-left">
<p>Welcome {{ userTitle }}</p>
</div>
<div class="col-sm-2 sidenav">
<div class="well">
<p>Placeholder</p>
</div>
<div class="well"></div>
</div>
</div>
</div>
I'll gladly share more code, but I didn't want to make this too long and I feel like I'm just missing something silly...
At first glance, I noticed you're making a local variable named userTitle when you want to add that variable to $scope.
Inject $scope into homeController and $scope.userTitle = 'test';. This should get you what you want.

Angular.js - Two Concurrent But Exclusive Routes ($routeProvider)

<div id="header">
Header
</div>
<div id="wrapper">
<div id="slider" ng-view>
Slider
</div>
<div id="mainWindow" ng-view>
Main window
</div>
</div>
http://jsfiddle.net/abehnaz/k9Y4f/
I am working with a webapp that has two major views. The first is a main window (standard stuff), and the second is a "slider". The slider is more than a slide out menu (sometimes called a "shelf"). It has routing and secondary information associated with it.
In what seems to be the normal Angular paradigm, I would use a single ng-view in the main window, and that would populate the main window with the results of the $routeProvider's view template.
However, here I want to be able to ALSO have the slider have its own route. Angular does not seem to support this. I was thinking that I could potentially hack the functionality in by making a client side URL of the form:
www.app.com/MAINWINDOW/someView1/someView2/SLIDER/someViewa
Am I on the right track with this, or is there another functionality of Angular that I can use?
I would not recommend two routeParameters. Make your sidebar a service and put a controller on your wrapper, slider, and mainWindow. By having the ng-controller on #wrapper as well as the children, you can share data between the controllers (and in turn, inject the service into those controllers. I have done this and it was very successful. See an example below. In my code, my ng-view and routing returned everything within the <div ng-app="myApp">. You could easily put the ng-view in the MainCtrl and trigger routes from the sidebar.
In your template:
<div ng-app="myApp">
<div ng-view>
//Produced by routing
<div id="wrapper" ng-controller="RootCtrl">
<div id="Sidebar" ng-controller="SidebarCtrl">
...
<div ng-click="setUnit(mod.number, unit.number)">Unit 1</div>
...
</div>
<div id="mainWindow" ng-controller="MainCtrl">
</div>
</div>
</div>
</div>
In your javascript:
A service:
myApp.service('questionsSvc', function(Modules) {
var QuestionsSrc = {};
QuestionsSrc.questions = [];
var getQuestionsForModAndUnitFn = function(mod, unit) {
...bunch of code to have it populate QuestionSrc with questions
};
return {
getQuestionsForModAndUnit: getQuestionsForModAndUnitFn,
Questions: QuestionsSrc
};
});
For Controllers:
function RootCtrl($scope) {
$scope.data = {};
}
function SidebarCtrl($scope, Modules, questionsSvc) {
$scope.setUnit = function (mod, unit) {
questionsSvc.getQuestionsForModAndUnit(mod, unit);
$scope.data.questions = questionsSvc.Questions.questions;
//$scope.data.questions was used in the MainCtrl window to do an ng-repeat on the json array of questions bound to this model.
}
};
function MainCtrl($scope){
...whatever you need to manipulate the code...
}
With this example, I am sharing information across Controllers. The magic is with the $scope.data = {}; from the RootCtrl. It allows me to attach questions to the $scope based on an action in the Sidebar (clicking a label) and use that same $scope in the MainCtrl to display the questions in a pretty format. With what I just showed, I did not have to use $routeParameters to pass variables to go to another page (such as module and quiz) but I could have done so as you asked but would have had the sidebar ng-click change routes.
Hope this helps.
One solution is to use Angular UI router:
AngularUI Router is a routing framework for AngularJS, which allows
you to organize the parts of your interface into a state machine.
Unlike the $route service in Angular core, which is organized around
URL routes, UI-Router is organized around states, which may optionally
have routes, as well as other behavior, attached.
States are bound to named, nested and parallel views, allowing you to
powerfully manage your application's interface.
Just to give a flavor for how you could use AngularUI Router- It supports multiple named views (which you can read more about under "Multiple & Named Views" in their docs). So for instance you can use ui-view with names:
<div id="wrapper">
<div id="slider" ui-view="slider">
Slider
</div>
<div id="mainWindow" ui-view="main">
Main window
</div>
</div>
Then within config you can attach states to various routes and specify what each view should display for that state.
myapp.config(function($stateProvider, $urlRouterProvider){
$stateProvider
.state('index', {
url: "/index",
views: {
"Slider": {
templateUrl:"route1.viewA.html"
},
"Main": {
templateUrl:"main1.viewA.html"
}
}
})
.state('slider2', {
url: "/slider2",
views: {
"Slider": {
templateUrl:"route2.viewA.html"
},
"Main": {
templateUrl:"main1.viewA.html"
}
}
})
There's a variety of other ways you could use AngularUI Router though. For instance, you may be able to get away with just using nested routes- which they recommend trying first.
Here's a couple more good reference material you might check out:
http://joelhooks.com/blog/2013/07/22/the-basics-of-using-ui-router-with-angularjs/
http://www.ng-newsletter.com/posts/angular-ui-router.html

AngularJS dynamically set param of ngInclude based on route

I'm trying to dynamically include a template into my index.html. The general structure of index.html is:
<body>
<header ng-controller="Main">
<section>
<!-- global stuff -->
</section>
<section ng-include="moduleName + '/views/menubar.html'">
<!-- module-based stuff -->
</section>
</header>
<div id="view" ng-view></div>
</body>
Sample URL
example.com/<app_name>/index.html#/<module_name>[/method_name]
I can't figure out how to update $scope.moduleName when the route changes. My trouble is two-fold:
The header's controller is Main, not the controller associated with the view, so I can't? update $scope.moduleName from the view's controller (because Main and the view's controller are siblings).
In Main, I tried setting a $scope.$on('$routeChangeSuccess',…), but apparently it is not notified of route changes.
I've thought of setting up a $rootScope.$on listener (as described in SO#15355346) for the route change and broadcasting down to children, who then emit back up their route, which is broadcasted back down so it is available to Main. But that seems heinous.
And I would really prefer to keep the header outside of ng-view.
EDIT I noticed that $route.current.scope has an object named with module_name (possibly because the name of the controller associated with the route's module_name is the same). I'm wondering if I might be able to somehow use the name of that object…
It's hard to say what's wrong in your code without the full picture. Things you show look fine to me.
Please see this plunk I've created to display the ability to do it. Take note that you also can extend route objects with custom properties, like moduleName here:
$routeProvider.when('/page1', {
template: 'one',
controller: 'one',
moduleName: 'firstModule'
});

Categories

Resources