How to reuse Angular directive in an ASP.NET MVC application? - angularjs

I have an ASP.NET MVC application with a lot of Areas and models, views and controllers inside them. I have a small calculator that I want to write in Angular because it will be easier for me, as a developer, and cooler for the user (instead of using jQuery only).
The thing is I want to have this calculator on different views in some of my areas in the MVC app. The best solution that I could came up with is make an Angular directive and then use it in every view that I need it. My question is whether this will work and whether I would have to make a different Angular module for every view that will use the directive.
Any better solutions and proposals are welcome.

Example from Angular documentation: https://docs.angularjs.org/tutorial/step_07
phonecatApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: 'PhoneListCtrl'
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: 'PhoneDetailCtrl'
}).
otherwise({
redirectTo: '/phones'
});
}]);
you can config as many controlles that you like per page(html). and each htm will include your custom calculator directive.
All the controllers can be registered to the same module:
var phonecatControllers = angular.module('phonecatControllers', []);
phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',
function ($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}]);
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams',
function($scope, $routeParams) {
$scope.phoneId = $routeParams.phoneId;
}]);

Here's my take on it: You don't need a separate module per view. If you are only using this to provide your calculator directive then you could just do something like:
angular.module('calculator', [])
.directive('onscreenCalculator', function () { ... });
I don't know if you are using partial views, but if you are it could be problematic if you use the ng-app = "calculator" directive at the view level. Personally, I would put it in your _Layout.cshtml and then you know you're only going to have one instance.
Your biggest challenge (in my opinion) is going to be how to get the result of the calculation back into your view since you're not truly writing an Angular app, but just using a directive. I'm sure it can be done with some playing around, though.

Related

Angular JS problems with Ng-Route, Gulp, and Json

I'm pretty new to the world of front end development and I'm working through my first project with AngularJS. I'm also using Yeoman, Gulp, Bower to set up my project, which is also bran new to me... I've kind of crafted a build from the yo generator Gulp Angular and put my own personal touches to it. I'm sure I did more harm than good :p but I'm learning.
Anyways I've been coding all day and am really stumped why my project is having trouble when I use the ng-route. The home display works correctly but when I try to click on a link to a deeper page it just refreshes back to the home. I'm using Json files rather than a server and the Gulp Angular set up has all my files compiled to another folder when launching a server. Is there any chance the issue could lie within the compiler?
I'm starting to go crazy so I think I'm gonna call it quits for the night but if anyone has the time and the generosity to look over my github repo I would be over joyed :)
Thanks
https://github.com/jleibham/BhamDesigns.git
App Module
(function() {
'use strict';
var bhamDesignsApp = angular.module('bhamDesignsApp', ['ngAnimate', 'ngTouch', 'ngSanitize', 'ngMessages', 'ngAria', 'ngRoute', 'mm.foundation', 'appControllers']);
bhamDesignsApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/projects', {
templateUrl: 'partials/projects.html',
controller: 'ProjectsController'
}).
when('/projects/:projectId', {
templateUrl: 'partials/gallery.html',
controller: 'GalleryController'
}).
otherwise({
redirectTo: '/projects'
});
}]);
})();
App Controller
(function() {
'use strict';
var appControllers = angular.module('appControllers', []);
appControllers.controller('ProjectsController', ['$scope', '$http',
function ($scope, $http) {
$http.get('app/json/projects.json').success(function(data){
$scope.projects = data;
});
$scope.orderProp = '-year';
}]);
appControllers.controller('GalleryController', ['$scope', '$routeParams',
function($scope, $routeParams) {
$scope.projectId = $routeParams.projectId;
}]);
})();
You are calling the wrong url and your routes do not recognize the url you do call with your href, so it redirects you. In you are going to call this:
href="#/json/galleries/(what ever the project.id is)
Then your routing should look similar to this:
when('/json/galleries/:projectId', { /// the rest of your code
You are going to want to use $routeParameters with ngRoute. here is a great example

Prevent $routeParams from Bleeding Through Codebase

I am using the Angular $routeProvider service to wire-up my single-page HTML5 applciation. I am using the following routing configuration:
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/show-order/:orderId', {
templateUrl: 'templates/order.html',
controller: 'ShowOrdersController'
});
}]);
Within the ShowOrdersController I need access to the RESTful URL parameter described above as :orderId. It is suggested that to best achieve this, I should use the $routeParams service in my controller:
app.controller('ShowOrderController', function($scope, $routeParams) {
$scope.order_id = $routeParams.orderId;
});
I have serious concerns about this. My routing logic has now bled through to my controller! If I want to drastically change the routing scheme, I would have to go through all my controller code and correct all the references to $routeParams.
Furthermore, if I want to re-use the ShowOrderController for multiple routes, it's going to enforce all of the routes to use the same token variable :orderId.
This just seems like poor coding to me. It would make more sense to provide some linking mechanism, so the router can specify well-known parameters to the controller.
This would be just like how a modal's resolve method works:
$modal.open({
controller: 'ShowOrderController',
resolve: {
orderId: function () {
return $routeParams.orderId;
}
}
});
app.controller("ShowOrderController", ["orderId", function (orderId, $scope) {
$scope.orderId = orderId;
}]);
Is there any way to achieve this or something similar with the out-of-the-box AngularJS routing services?
As per AngularJS - How to pass up to date $routeParams to resolve? it is possible to reference the current route's parameters in the resolve method of the $routeProvider using $route.current.params:
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/show-order/:orderId', {
templateUrl: 'templates/order.html',
controller: 'ShowOrdersController',
resolve: {
orderId: function( $route ) {
return $route.current.params.orderId;
}
}
});
}]);
This will then honour the suggestion above, that the controller can declaratively specify its parameters:
app.controller("ShowOrderController", ["orderId", function (orderId, $scope) {
$scope.orderId = orderId;
}]);
In conjunction, this effectively decouples the controller from the route's parameters.

Invoke function between two controllers while using routeprovider

I am using route provider as follows,
var appModule = angular.module('ngLogin', ['ngRoute','restangular','btford.socket-io','ngSanitize','xeditable']);
appModule.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/home', {
templateUrl: 'sample/homepage.html',
controller: 'ngHomeControl'
}).
when('/contacts', {
templateUrl: 'sample/homepage.html',
controller: 'ngContactControl'
});
}]);
Here I need to call function from ngHomeControl to ngContactControl.
I tried as follows, but the function didn't invoked.
appModule.controller('ngHomeControl', function($scope,$routeParams,socket,Restangular,$http) {
$rootScope.$broadcast('getFriendList',{"userName":userName});
});
appModule.controller('ngContactControl', function($scope,$routeParams,$rootScope,socket,sharedProperties,Restangular,$http,$timeout) {
$scope.$on("getFriendList",function(event,data)
{
console.log('getFriendList');
});
});
Can anyone help me to resolve?
This will not work as only one controller is instantiated at a time (in your case).
A proper way would be to use a service. There is a nice article that wil help you with this.
See also this answer on how to create a service.
Based on those two resources you should came up with something similar to this:
var appModule = angular.module('appModule', ['ngRoute']);
appModule.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/home', {
templateUrl: 'home.html',
controller: 'ngHomeControl'
}).
when('/contacts', {
templateUrl: 'contacts.html',
controller: 'ngContactControl'
});
}]);
appModule.service('friendsService', function(){
this.getFriendList = function () {
return ['John', 'James', 'Jake'];
}
});
appModule.controller('ngHomeControl', function($scope, friendsService) {
$scope.homeFriends = friendsService.getFriendList();
});
appModule.controller('ngContactControl', function($scope, friendsService) {
$scope.contactFriends = friendsService.getFriendList();
});
There is a complete working JSFiddle so you can test it out.
Please also checkout the console output to see when used components are instantiated.
You will see that controllers are instantiated each time the route changes - they are instantiated implicitly via the ngController directive used inside templates. The service is instantiated only once and this is at the time when it is needed/injected for the first time.
The reason your event listener isn't fired is because there isn't a ngContactControl instance alive when your at /home. You can create a parent controller, which handles the scope events but a better way is to use a service that is shared among the controllers that need this functionality.
See this plunker for an example how to share data and/or functions via a service.

Setting templateUrl in an AngularJS Module to increase modularity

I have nested angularJS modules EventList and EventPage.
The directory structure looks like this:
app.js
EventForm
|_ EventList.js
eventListView.html
EventPage
|_EventPage.js
eventPageView.html
Eventlist.js:
angular.module('EventList', ['EventPage'])
.config([ '$routeProvider', function config($routeProvider){
$routeProvider.when(Urls.EVENT_LIST, {
templateUrl: 'app/EventList/event-list.html',
controller: 'EventListCtrl'
});
}])
.controller('EventListCtrl', ['$scope', '$location', '$http', function EventListController($scope, $location, $http) {
}]);
EventPage.js:
angular.module('EventPage', [])
.config([ '$routeProvider', function config($routeProvider){
$routeProvider.when(Urls.EVENT_PAGE + '/:id', {
templateUrl: 'app/EventList/EventPage/event-page.html',
controller: 'EventPageCtrl'
})
}])
.controller('EventPageCtrl', ['$scope', '$routeParams', '$http', function EventPageController($scope, $routeParams, $http) {
}]);
Obviously the templateUrl is hardcoded right now. My question is what is the best way to set the templateUrl in the routeProvider so the modules aren't dependent on the hard coded directory structure and can be taken out and reused in other AngularJS projects without breaking? Is there a way to just get insertViewNameHere.html in the current folder?
If you're using Grunt, this is easy. Not because modularizing AngularJS is trivial, but because all the hard work has already been done for you by the good folk doing angular-bootstrap...
When I faced this problem I simply downloaded their Gruntfile and changed every ui.bootstrap to my-angular-module. Since I mimicked their basic directory structure, it worked right out of the box.
Also, you might want to update the Angular and Bootstrap versions. E.g., change
ngversion: '1.0.8',
bsversion: '2.3.1',
to
ngversion: '1.2.3',
bsversion: '3.0.2',

Injection of angularFireCollection into angular.js config for usage in resolve

I've been experimenting a little with Angular.js lately. As part of this I created a very simple set of controllers with an ng-view and templates to trigger depending on the route requested. I'm using angularFireCollection just to grab an array from Firebase. This works fine in the thumbnailController which does not form part of the ng-view.
My problem is that in addition to the data flowing into the thumbnailController, I also need the two other controllers to be able to access the data. I initially simply set the data to either be part of $rootScope or to have the ng-view as a child of the div in which the thumbnailController is set.
However, the issue from that perspective is that each sub-controller presumably attempts to set the data in the template before it is actually available from Firebase.
The solution appears to be using resolve as per the answer to this question angularFire route resolution. However, using the below code (and also referencing angularFireCollection) I get an error message of angularFire being an unknown provider. My understanding is the code below should be sufficient, and I would also add that the usage of angularFireCollection in thumbnailController works fine as I say.
I also experimented with injecting angularFire/aFCollection directly into the controllers using .$inject however a similar issue arose in terms of it being considered an unknown provider.
If possible could someone advise on what the issue may be here?
var galleryModule = angular.module('galleryModule', ['firebase']);
galleryModule.config(['$routeProvider', 'angularFire', function($routeProvider, angularFire){
$routeProvider.
when('/', {
controller: initialController,
templateUrl: 'largeimagetemplate.html',
resolve: {images: angularFire('https://mbg.firebaseio.com/images')}
}).
when('/view/:id', {
controller: mainimageController,
templateUrl: 'largeimagetemplate.html',
resolve: {images: angularFire('https://mbg.firebaseio.com/images')}
}).
otherwise({
redirectTo: '/'
});
}]);
galleryModule.controller('thumbnailController', ['$scope', 'angularFireCollection', function($scope, angularFireCollection){
var url = 'https://mbg.firebaseio.com/images';
$scope.images = angularFireCollection(url);
}]);
function initialController($scope,images){
$scope.largeurl = images[0].largeurl;
}
function mainimageController($scope, images, $routeParams){
$scope.largeurl = images[$routeParams.id].largeurl;
}
I got the chance to dig into this a little bit - it seems like regular services cannot be used in .config sections. I'd instantiate angularFire in the controller instead of using resolve, for example:
galleryModule
.value("url", "https://mbg.firebaseio.com/images")
.controller('thumbnailController', ['$scope', 'angularFireCollection', 'url',
function($scope, angularFireCollection, url) {
$scope.images = angularFireCollection(url);
}])
.controller('initialController', ['$scope', 'angularFire', 'url',
function($scope, angularFire, url) {
angularFire(url, $scope, 'images').then(function() {
$scope.largeurl = $scope.images[0].largeurl;
});
}])
.controller('mainimageController', ['$scope', 'angularFire', '$routeParams', 'url',
function($scope, angularFire, $routeParams, url){
angularFire(url, $scope, 'images').then(function() {
$scope.largeurl = $scope.images[$routeParams.id].largeurl;
});
}]);
This is not ineffecient, since the data is only loaded once from the URL by Firebase, and all subsequent promises will be resolved almost immediately with data already at hand.
I would like to see angularFire work with resolve in the $routeProvider, however. You can use this method as a workaround until we figure out a more elegant solution.

Resources