AngularJS - Load Controller asynchronously From AJAX Without Changing Route - javascript

I want to dynamically load an angular controller upon an ajax call that renders a new view(HTML).
Here is what i have:
example of a view. HTML Snippet From AJAX
<!-- CVS Pharmacy Extracare - Add View -->
<div ng-controller="cvsViewCtrl" class="container-fluid">
<div class="col-md-8 col-md-offset-2">
<h3 id="asset-title" class=""></h3>
<br>
<p>Member ID</p>
<input class="input-s1 block-elm transition" type="text" placeholder=""/>
<br>
<input class="add-asset-btn btn btn-success block-elm transition" type="button" value="Add Asset!" ng-click="prepareCVS();"/>
</div>
</div>
the separate script that pertains to the view
App.controller('cvsViewCtrl', ['$scope', '$http', function($scope, $http){
console.log('cvs view loaded');
$scope.prepareCVS = function() {
console.log('admit one');
}
}]);
and the function that loads them
$scope.setAddAssetView = function(a) {
console.log(a);
if($scope.currentAddView == a) {
console.log('view already set');
return;
}
$scope.currentAddView = a;
$('#main-panel').html('');
$http({
method: 'POST',
url: '/action/setaddassetview',
headers: {
'Content-Type': 'application/json',
},
data: {
asset: a,
}
}).then(function(resp){
// Success Callback
// console.log(resp);
var index = resp.data.view.indexOf('<s');
var script = resp.data.view.slice(index);
var html = resp.data.view.replace(script, '');
$('#main-panel').html( html );
$('#asset-title').text(a.name);
var indexTwo = a.view.indexOf('/add');
var scriptLink = insertString(a.view, indexTwo, '/scripts').replace('.html', '.js').replace('.', '');
console.log( scriptLink );
window.asset = a;
$.getScript(scriptLink, function(data, textStatus, jqxhr){
console.log('loaded...');
})
},
function(resp){
// Error Callback
console.log(resp);
});
}
when $.getScript runs, the script gets loaded successfully but it doesn't initialize the controller. i even tried:
var s = document.createElement('script');
s.type = 'text/javascript';
s.src = scriptLink;
s.innerHTML = null;
s.id = 'widget';
document.getElementById('switch-script').innerHTML = '';
document.getElementById('switch-script').appendChild( s );
this appends the script with the right link but still doesn't get initialized. How can i work around this?

Make this changes in your App, and be sure to load the controller file before the html with ng-controller rendered.
var App = angular.module("app", [...]);
App.config(["$controllerProvider", function($controllerProvider) {
App.register = {
controller: $controllerProvider.register,
}
}]);
App.register.controller('cvsViewCtrl', ['$scope', '$http', function($scope, $http){
console.log('cvs view loaded');
$scope.prepareCVS = function() {
console.log('admit one');
}
}]);

That would actually not be the right way to handle things.
If you use ui-router for routing, you can load the controller in the resolve function. A good service for that is ocLazyLoad.
That can be done as follows:
var app = angular.module('myApp', [
'ui.router',
'oc.lazyLoad',
]);
angular.module('myApp').config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/login');
$stateProvider
.state('main', {
url: '/main',
templateUrl: 'app/views/main.view.html',
controller: 'mainCtrl',
resolve: {
loadCtrl: ['$ocLazyLoad', function($ocLazyLoad) {
return $ocLazyLoad.load('app/ctrls/main.ctrl.js');
}],
}
})
.state('second', {
url: '/second',
templateUrl: 'app/views/second.view.html',
controller: 'secondCtrl',
resolve: {
loadCtrl: ['$ocLazyLoad', function($ocLazyLoad) {
return $ocLazyLoad.load('app/ctrls/controller_TWO.ctrl.js');
}],
}
})
});
When you're changing from one route to another, resolve will be triggered before the html is loaded and the controller will be registered properly.

Related

Data for listing page and detail page without 2 API calls

I have set up a service to return a listing of clients from my API. Using UI-router, I can successfully pass a client's id to the details state - however, it seems unnecessary here to make another API call to retrieve a single client when I have all the necessary data in my controller.
What is the best way to use the ID in my detail state URL to show data for that client? Also - if a user browses directly to a client detail URL - I'll need to then make a call to the API to get just that client data - or is there a better way?
EDIT: I am not looking to load the two views on the same 'page', but completely switch views here, from a listing page to a detail page.
Routes in App.js
$stateProvider
.state('root', {
abstract: true,
url: '',
views: {
'#': {
templateUrl: '../partials/icp_index.html',
controller: 'AppController as AppCtrl'
},
'left-nav#root': {
templateUrl: '../partials/left-nav.html'
},
'right-nav#root': {
templateUrl: '../partials/right-nav.html'
},
'top-toolbar#root': {
templateUrl: '../partials/toolbar.html'
}
/*'footer': {
templateUrl: '../partials/agency-dashboard.html',
controller: 'AppController as AppCtrl'
}*/
}
})
.state('root.clients', {
url: '/clients',
views: {
'content#root': {
templateUrl: '../partials/clients-index.html',
controller: 'ClientsController as ClientsCtrl'
}
}
})
.state('root.clients.detail', {
url: '/:clientId',
views: {
'content#root': {
templateUrl: '../partials/client-dashboard.html',
//controller: 'ClientsController as ClientsCtrl'
}
}
})
// ...other routes
Service, also in app.js
.service('ClientsService', function($http, $q) {
this.index = function() {
var deferred = $q.defer();
$http.get('http://api.icp.sic.com/clients')
.then(function successCallback(response) {
console.log(response.data);
deferred.resolve(response.data);
},
function errorCallback(response) {
// will handle error here
});
return deferred.promise;
}
})
And my controller code in ClientsController.js
.controller('ClientsController', function(ClientsService) {
var vm = this;
ClientsService.index().then(function(clients) {
vm.clients = clients.data;
});
});
And finally, my listing page clients-index.html
<md-list-item ng-repeat="client in ClientsCtrl.clients" ui-sref="clients-detail({clientId : client.id })">
<div class="list-item-with-md-menu" layout-gt-xs="row">
<div flex="100" flex-gt-xs="66">
<p ng-bind="client.name"></p>
</div>
<div hide-xs flex="100" flex-gt-xs="33">
<p ng-bind="client.account_manager"></p>
</div>
</div>
</md-list-item>
You can use inherited states like suggested here.
$stateProvider
// States
.state("main", {
controller:'mainController',
url:"/main",
templateUrl: "main_init.html"
})
.state("main.details", {
controller:'detailController',
parent: 'main',
url:"/:id",
templateUrl: 'form_details.html'
})
Your service does not change.
Your controllers check if the Model has been retrieved:
app.controller('mainController', function ($scope, ClientsService) {
var promise = $scope.Model ? $q.when($scope.Model) : ClientsService.index();
promise.then(function(data){
$scope.Model = data;
});
})
app.controller('detailController', function ($q, $scope, ClientsService, $stateParams) {
var promise = $scope.Model ? $q.when($scope.Model) : ClientsService.index();
promise.then(function(data){
$scope.Model = data;
$scope.Item = data[$stateParams.id];
});
})
See
http://plnkr.co/edit/I4YMopuTat3ggiqCoWbN?p=preview
[UPDATE]
You can also, if you must, combine both controllers:
app.controller('mainController', function ($q, $scope, ClientsService, $stateParams) {
var promise = $scope.Model ? $q.when($scope.Model) : ClientsService.index();
promise.then(function(data){
$scope.Model = data;
$scope.Item = data[$stateParams.id];
});
})
I would change the service to cache the data. With $q.when() you can return a promise from a variable. So you save your response in a variable, and before doing the API call you check if the cache has been set. If there is any cache, you return the data itself. Otherwise, you do the usual promise call.
.service('ClientsService', function($http, $q) {
var clients = null;
this.getClient = function(id) {
if (clients !== null) {
return $q.when(id ? clients[id] : clients);
}
var deferred = $q.defer();
$http.get('http://api.icp.sic.com/clients').then(function(response) {
clients = response.data;
deferred.resolve(id ? clients[id] : clients);
}, function (response) {
// will handle error here
});
return deferred.promise;
}
})

Passing return of function to another state

I have been trying to send data from one controller to another. A little background this is code being used in an ionic application if that helps any. I want the to send the data from send() function to the SubCtrl. The send function is being called in MainCtrl. I have created a service for this but the data is still not being shared. What am I missing to complete this action?
var app = angular.module('testapp', []);
app.config(function($stateProvider, $urlRouterProvider) {
"use strict";
/* Set up the states for the application's different sections. */
$stateProvider
.state('page2', {
name: 'page2',
url: '/page2',
templateUrl: 'page2.html',
controller: 'MainCtrl'
})
.state('page3', {
name: 'page3',
url: '/page3',
templateUrl: 'page3.html',
controller: 'SubCtrl'
});
$urlRouterProvider.otherwise('/page2');
});
app.factory('dataShare', function($rootScope) {
var service = {};
service.data = false;
service.sendData = function(data) {
this.data = data;
$rootScope.$broadcast('data_shared');
console.log(data);
};
service.getData = function() {
return this.data;
};
return service;
});
app.controller('MainCtrl', function($scope, $state, $http, dataShare) {
$scope.text = 'food';
$scope.send = function() {
dataShare.sendData(this.text);
};
});
app.controller('SubCtrl', function($scope, $state, dataShare) {
"use strict";
var sc = this;
$scope.text = '';
$scope.$on('data_shared', function() {
var text = dataShare.getData();
sc.text = dataShare.data;
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script id="page2.html" type="text/ng-template">
<div>{text}}</div>
<input type='text' ng-model='text' />
<button class="button button-outline button-royal" ng-click="send();">add</button>
</script>
<script id="page3.html" type="text/ng-template">
<div>text: {{text}}</div>
</script>
I was able to figure this issue out after reading this page. If anyone is having a similar issue I would encourage this reading. Also the video link on this post was really helpful.

Angular module adding a Service injection error

First time doing an angular application, combining different tutorials but this is the first time I am trying to inject a service.
I have one of my View's controllers like:
angular.module("myApp.Pages").controller('signupController', ['$scope', '$location', '$timeout', 'authService', function ($scope, $location, $timeout, authService) {
}
however am seeing an error when I look at the Console in Developer Tools:
angular.js:12793 Error: [$injector:unpr] Unknown provider:
authServiceProvider <- authService <- signupController
http://errors.angularjs.org/1.5.0-beta.2/$injector/unpr?p0=authServiceProvider%20%3C-%20authService%20%3C-ignupController
My project structure is:
-Client
-App
-Components
-Services
-authService.js
-myAppCore.js
-Views
-app.js
-appRouting.js
-Scripts (References)
-Theme (Css)
-Index.html
My index.html scripts I add:
<!-- Angular References-->
<script src="References/Angular/angular.js"></script>
<script src="References/Angular/angular-route.js"></script>
<script src="References/Angular/angular-ui-router.min.js"></script>
<!-- End Angular References-->
<!-- my app and dependent modules -->
<script src="App/app.js"></script>
<script src="App/appRouting.js"></script>
<!-- Services -->
<script src="App/Components/Services/authService.js"></script>
<!-- END services-->
<!-- Controllers for your pages-->
<script src="App/Pages/Home/homeController.js"></script>
<script src="App/Pages/ContactUs/contactusController.js"></script>
<script src="App/Pages/Entry/entryController.js"></script>
<script src="App/Pages/Signup/signupController.js"></script>
<!-- End Controllers for the page-->
My app.js
angular.module("myApp", [
// User defined modules
'myApp.Templates', // templates
'myApp.Pages', // Pages
'myApp.Core', // Core
// Angular modules
'ui.router', // state routing
'ngRoute', // angular routing
'angular-loading-bar', //loading bar
'LocalStorageModule', //local browser storage
])
and appRouting.js
angular.module("myApp")
.config(["$stateProvider", function ($stateProvider) {
$stateProvider.state('Home', {
url: '/Home',
templateUrl: 'App/Pages/Home/home.html',
controller: 'homeController'
})
.state('Entry', {
url: '/Entry',
templateUrl: 'App/Pages/Entry/entry.html',
controller: 'entryController'
})
.state('Signup', {
url: '/Signup',
templateUrl: 'App/Pages/Signup/signup.html',
controller: 'signupController'
})
.state('Contactus', {
url: '/Contactus',
templateUrl: 'App/Pages/ContactUs/contactus.html',
controller: 'contactusController'
})
.state("otherwise", {
url: "*path",
templateUrl: "App/Pages/NotFound/notFound.html"
});
}])
.run(["$location", function ($location) {
// Go to state dashboard
$location.url('/Home');
}]);
authService which handles login/register:
app.factory('authService', ['$http', '$q', 'localStorageService', function ($http, $q, localStorageService) {
var serviceBase = '<location>';
var authServiceFactory = {};
var _authentication = {
isAuth: false,
userName: ""
};
var _saveRegistration = function (registration) {
_logOut();
return $http.post(serviceBase + 'api/account/register', registration).then(function (response) {
return response;
});
};
var _login = function (loginData) {
var data = "grant_type=password&username=" + loginData.userName + "&password=" + loginData.password;
var deferred = $q.defer();
$http.post(serviceBase + 'token', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).success(function (response) {
localStorageService.set('authorizationData', { token: response.access_token, userName: loginData.userName });
_authentication.isAuth = true;
_authentication.userName = loginData.userName;
deferred.resolve(response);
}).error(function (err, status) {
_logOut();
deferred.reject(err);
});
return deferred.promise;
};
var _logOut = function () {
localStorageService.remove('authorizationData');
_authentication.isAuth = false;
_authentication.userName = "";
};
var _fillAuthData = function () {
var authData = localStorageService.get('authorizationData');
if (authData) {
_authentication.isAuth = true;
_authentication.userName = authData.userName;
}
}
authServiceFactory.saveRegistration = _saveRegistration;
authServiceFactory.login = _login;
authServiceFactory.logOut = _logOut;
authServiceFactory.fillAuthData = _fillAuthData;
authServiceFactory.authentication = _authentication;
return authServiceFactory;
}]);
myAppPages.js and myAppCore.js are the same just their respective names :
angular.module("myApp.Pages", []);
Edit: Seeing a "app is not defined" reference error in authService
You don't defined var app, so use angular.module("myApp") to define your factory
angular.module("myApp").factory('authService', ['$http', '$q', 'localStorageService', function ($http, $q, localStorageService)
Also you can declare var app = angular.module("myApp") and use app
I simply did not declare:
var app = angular.module(...)
And my service was referencing app when that did not exist.

AngularJS ng-controller directive does not accept variable (scope function) from javascript, does not give any error either

I am relatively new to angularJS, I am trying to set up a page where inturn multiple pages are called depending upon the selection made previously.
All the pages have their own controller, so I am trying to set the controller and view src through the javascript and using them in HTML tags.
Following is what I am doing:
HTML page:
<div ng-if="sidebarName=='sidebar-device-wire'">
<div ng-controller="getSidebarCtlr">
<div ng-include src="sidebarSrc"></div>
</div>
</div>
javascript:
$scope.sidebarSrc="views/sidebars/sidebar-device.html";
$scope.sidebarCtlr="SidebarDeviceCtrl";
$scope.getSidebarCtlr = function(){return $scope.sidebarCtlr;}
For some reason though, this does not work. i can get the HTML page but the controller is not being called. Can anyone please tell me what I am doing wrong?
I would also recommend to use ngRoute or ui.router because there are many features that aren't easy to implement from scratch (like named views, nested views / nested states or resolves) and these modules are well tested.
Not sure why your controller isn't running but I guess that the expression of the controller is evaluated before your controller that is setting the name is running. So it will be always undefined at compile time.
But if you really like to implement a very basic router you could do it like in the following demo (or in this fiddle).
Update 21.12.2015
Here are some router examples that I wrote for other SO questions:
simple ui.router example - jsfiddle
more complex nested state example ui.router - jsfiddle
dynamic link list with ngRoute - jsfiddle
Please also have a look at ui.router github pages to learn more about it.
angular.module('simpleRouter', [])
.directive('simpleView', simpleViewDirective)
.provider('simpleRoutes', SimpleRoutesProvider)
.controller('MainController', MainController)
.controller('HomeController', HomeController)
.config(function(simpleRoutesProvider) {
simpleRoutesProvider.state([{
url: '/',
templateUrl: 'home.html',
controller: 'HomeController'
}, {
url: '/view1',
templateUrl: 'view1.html'
}, {
url: '/view2',
templateUrl: 'view2.html',
controller: function($scope) {
$scope.test = 'hello from controller'
}
}]);
simpleRoutesProvider.otherwise('/');
})
function HomeController($scope) {
$scope.hello = 'hello from home controller!!';
console.log('home controller started')
}
function MainController($scope) {
$scope.hello = 'Main controller test';
}
function simpleViewDirective() {
return {
restrict: 'EA',
scope: {},
template: '<div ng-include="templateUrl"></div>',
controller: function($scope, $location, $controller, simpleRoutes) {
var childControllerInst;
$scope.templateUrl = simpleRoutes.currentRoute.templateUrl || simpleRoutes.otherwise.templateUrl;
$scope.$watch(function() {
return $location.path();
}, function(newUrl) {
//console.log(newUrl)
$scope.templateUrl = simpleRoutes.changeRoute(newUrl);
childControllerInst = $controller(simpleRoutes.currentRoute.controller || function() {}, {$scope: $scope});
});
$scope.$on('$destroy', function() {
childControllerInst = undefined;
})
},
link: function(scope, element, attrs) {
}
}
}
function SimpleRoutesProvider() {
var router = {
currentRoute: {
templateUrl: ''
},
states: [],
otherwise: {},
changeRoute: function(url) {
var found = false;
angular.forEach(router.states, function(state) {
//console.log('state', state);
if (state.url == url) {
router.currentRoute = state;
found = true;
}
});
if (!found) router.currentRoute = router.otherwise;
//console.log(router.currentRoute);
return router.currentRoute.templateUrl;
}
};
this.state = function(stateObj) {
router.states = stateObj;
};
this.otherwise = function(route) {
angular.forEach(router.states, function(state) {
if (route === state.url ) {
router.otherwise = state;
}
});
//console.log(router.otherwise);
};
this.$get = function simpleRoutesFactory() {
return router;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="simpleRouter" ng-controller="MainController">
<script type="text/ng-template" id="home.html">home route {{hello}}</script>
<script type="text/ng-template" id="view1.html">view1</script>
<script type="text/ng-template" id="view2.html">view2 {{test}}</script>
<div simple-view="">
</div>
home
view1
view2
<br/>
{{hello}}
</div>
What's that code means? $scope.getSidebarCtlr = function(){return $scope.sidebarCtlr;}
the ng-directive requires a Controller name, its argument type is string and you cannot pass a simple function, you need to register a valid controller associating it to a module via the controller recipe.
https://docs.angularjs.org/guide/controller
angular.module('test', []).controller('TestCtrl', function($scope) {
$scope.greetings = "Hello World";
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<section ng-app="test">
<article ng-controller="TestCtrl">{{ greetings }}</article>
</section>

Angular-JS routing get data from MVC controller

I have implemented code to retrieve data from MVC Controller using Angular-JS using ngRoute. What I have implemented is, I have two action methods in MVC controller and at client side I have two menu buttons and by clicking on each button, it retrieves data from respective action method. I have used Angular-Route, Angular Factory Method and ng-view
Till now it is fine. But what I can see is AngularJS doesn't go to Action Method every time when i click on the button. Once data have been retrieved, it only shows the respective view. And in this situation I cannot retrieve fresh data from database. So If I have to call action method every time how can I achieve this ?
This is what I have implemented:
AccountController:
public ActionResult GetAccounts()
{
var repo = new AccountRepository();
var accounts = repo.GetAccounts();
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
var jsonResult = new ContentResult
{
Content = JsonConvert.SerializeObject(accounts, settings),
ContentType = "application/json"
};
return jsonResult;
}
public ActionResult GetNumbers()
{
var repo = new AccountRepository();
var numbers = repo.GetNumbers();
var setting = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
var jsonResult = new ContentResult()
{
Content = JsonConvert.SerializeObject(numbers, setting),
ContentType = "application/json"
};
return jsonResult;
}
Account.JS:
'use strict';
var account = angular.module('accountModule', ['ngRoute']);
account.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/Employees', { templateUrl: '../Templates/Employees.html', controller: 'accountController' })
.when('/Numbers', { templateUrl: '../Templates/Numbers.html', controller: 'numberController' })
.otherwise({
redirectTo: '/phones'
});;
}]);
account.controller('accountController', [
'$scope',
'accountService',
function ($scope, accountService) {
accountService.getEmployees().then(function(talks) {
$scope.employees = talks;
}, function() {
alert('Some error');
});
}
]);
account.controller('numberController', [
'$scope',
'accountService',
function ($scope, accountService) {
accountService.getNumbers().then(function (retrievedNumbers) {
$scope.telephoneNumbers = retrievedNumbers;
}, function () {
alert('error while retrieving numbers');
});
}
]);
AccountService.JS:
account.factory('accountService', ['$http', '$q', function ($http, $q) {
var factory = {};
factory.getEmployees = function () {
var deferred = $q.defer();
$http({
method: 'GET',
url: 'GetAccounts'
}).success(deferred.resolve).error(deferred.reject);
return deferred.promise;
};
factory.getNumbers = function () {
var deferred = $q.defer();
$http({
method: 'GET',
url: 'GetNumbers'
}).success(deferred.resolve).error(deferred.reject);
return deferred.promise;
};
return factory;
}]);
and my view is something like this:
#{
ViewBag.Title = "Index";
Layout = "~/Views/shared/_Layout.cshtml";
}
<script type="text/javascript" src="../../Scripts/Modules/Account/Account.js"></script>
<script type="text/javascript" src="../../Scripts/Modules/Account/AccountService.js"></script>
<div ng-app="accountModule">
<div>
<div class="container">
<div class="navbar navbar-default">
<div class="navbar-header">
<ul class="nav navbar-nav">
<li class="navbar-brand">Employees</li>
<li class="navbar-brand">Numbers</li>
</ul>
</div>
</div>
<div ng-view>
</div>
</div>
</div>
</div>

Categories

Resources