I'm developing a new proyect using angular and I have separated the: App (main module), controller and services in diferent files:
The responsabilities are:
indexApp.js
And them code is:
(function(indexApp) {
indexApp.App = {};
indexApp.Init = function() {
indexApp.App = angular.module("MainAppModule", ["MainControllerModule", "MainServiceModule"]);
};
}(window.indexApp = window.indexApp || {}));
indexController.js
And them code is:
(function (indexController) {
indexController.App = {};
indexController.MainController = function (service) {
var self = this;
var dataRetrieved = service.Login();
self.movie = {
title: dataRetrieved.Id,
director: dataRetrieved.Name,
date: dataRetrieved.LastName,
mpaa: "PG-13",
id: 0,
clickCommand: function () {
alert(self.movie.director);
},
loadData: function (id) {
console.log(id);
if (id !== 0) {
self.movie.title = "Titulo";
self.movie.director = "Director";
self.movie.date = "Mayo 16 de 2015";
self.movie.mpaa = "PG-25";
self.movie.id = id;
}
}
}
};
indexController.SetUrl = function (data) {
indexController.Redirect = data.Redirect;
};
indexController.Init = function () {
indexController.App = angular.module("MainControllerModule", []);
indexController.App.controller("MainController", indexController.MainController);
indexController.MainController.$inject = ["MainService"];
};
}(window.indexController = window.indexController || {}));
indexService.js
Them code is:
(function (indexService) {
indexService.App = {};
indexService.MainService = function () {
var self = this;
self.Login = function () {
return {
Id: 1,
Name: "Freddy",
LastName: "Castelblanco"
};
};
};
indexService.SetUrl = function (data) {
indexService.Login = data.Login;
};
indexService.Init = function () {
indexService.App = angular.module("MainServiceModule", []);
indexService.App.service("MainService", indexService.MainService);
};
}(window.indexService = window.indexService || {}));
At the end in my view I call the follow methods:
#using System.Web.Optimization
#{
Layout = "~/Views/Shared/_Layout.cshtml";
var id = 20;
}
<div ng-app="MainAppModule">
<div ng-controller="MainController as vm">
<div ng-init="vm.movie.loadData(#id)">
<div class="row">
<div class="col-md-12">{{vm.movie.title}}</div>
<input type="text" ng-model="vm.movie.title"><br>
</div>
<div class="row">
<div class="col-md-12">{{vm.movie.director}}</div>
</div>
<div class="row">
<div class="col-md-12">{{vm.movie.date}}</div>
</div>
<div class="row">
<div class="col-md-12">{{vm.movie.mpaa}}</div>
</div>
<div class="row">
<div class="col-md-12">
<button type="button" ng-click="vm.movie.clickCommand()">Click me !!</button>
</div>
</div>
</div>
</div>
</div>
#section scripts
{
#Scripts.Render("~/bundles/index")
<script type="text/javascript">
indexApp.Init();
indexService.Init();
indexController.Init();
</script>
}
Is a correct way to use angular ?? Im using dependency injection.
How you define an angular app is up to you but angular provides modules to deal with code organization, prevent global scope pollution, dependency injection among other things
Angular apps don't have a main method. Instead modules declaratively specify how an application should be bootstrapped
You are using a common method found in other frameworks of using var self = this to add functionality to your app but angular comes with a nice gift scopes. Scopes are extremely useful because all angular apps have one and only one $rootScope wich you can use to store commonly used functionality all across your application. Also scope are organized in a hierarchy wich give you the abitity to nest scopes and make some logic work only on specific DOM elements.
Scopes are arranged in hierarchical structure which mimic the DOM structure of the application. Scopes can watch expressions and propagate events.
To glue your application you should use $watch on the scope to be notified of changes but usually you use any of the predefined directives that do this automatically for simple task like binding and changing attributes eg. ngBind, ngClick, etc.
Scope is the glue between application controller and the view. During the template linking phase the directives set up $watch expressions on the scope. The $watch allows the directives to be notified of property changes, which allows the directive to render the updated value to the DOM.
I personally don't use IIFE when I'm using angular but this is a personal choice. The iife allows you to prevent global scope pollution by wrapping variables inside a function scope so you don't have name collisions but angular introduces providers which can help you to create functionality using factories and services so basically you wrap all your functionality in one of them (read which is the most suitable for your task) and you have already included dependency injection in the mix for free.
Finally there are three ways to use dependency injection (or ways to anotate it).
Inline Array Annotation
mymodule.controller('MyController', ['$scope', function($scope) {
// your code
}]);
$inject Property Annotation
var MyController = function($scope) {
// ...
}
MyController.$inject = ['$scope'];
someModule.controller('MyController', MyController);
Implicit Annotation
someModule.controller('MyController', function($scope) {
// ...
});
You are free to use the way that you feel more confortable with but you should be aware that the last alternative is dangerous if you plan to minify your code because angular rely in variable names to find the dependencies and those will get renamed in the minification process. Personaly I use the first and it seems the most popular since you don't need the extra variable used in the second alternative.
Your code can be rewritten as follows
angular.module('services', []).service('MainService', function () {
return {
Login: function () {
return {
Id: 1,
Name: "Freddy",
LastName: "Castelblanco"
};
}
};
});
angular.module('controllers', []).controller('MainController', ['$scope', 'MainService', function ($scope, service) {
var dataRetrieved = service.Login();
$scope.movie = {
title: dataRetrieved.Id,
director: dataRetrieved.Name,
date: dataRetrieved.LastName,
mpaa: "PG-13",
id: 0
};
$scope.clickCommand = function () {
alert($scope.movie.director);
};
$scope.loadData = function (id) {
if (id !== 0) {
$scope.movie.title = "Titulo";
$scope.movie.director = "Director";
$scope.movie.date = "Mayo 16 de 2015";
$scope.movie.mpaa = "PG-25";
$scope.movie.id = id;
}
}
}]);
angular.module('MainAppModule', ['controllers', 'services']);
And your html
<div ng-app="MainAppModule">
<div ng-controller="MainController">
<div ng-init="loadData(#id)">
<div class="row">
<div class="col-md-12">{{movie.title}}</div>
<input type="text" ng-model="movie.title">
<br>
</div>
<div class="row">
<div class="col-md-12">{{movie.director}}</div>
</div>
<div class="row">
<div class="col-md-12">{{movie.date}}</div>
</div>
<div class="row">
<div class="col-md-12">{{movie.mpaa}}</div>
</div>
<div class="row">
<div class="col-md-12">
<button type="button" ng-click="clickCommand()">Click me !!</button>
</div>
</div>
</div>
</div>
</div>
{Update}
You can also check AngularJS: Understanding design pattern for guidelines on how you should structure your angular app
Related
HTML
chech out the below sample html, I require something like this.
<div id="parentApp" ng-app="parentApp" ng-cloak="" ng-controller="mainController">
<div id="someContent">
{{$scope.parentName}}
***some parent app actions***
</div>
<div id="childApp" ng-app="childApp">
<div id="someContent" ng-controller="secondController">
{{$scope.childName}}
***some child app actions***
</div>
</div>
</div>
Script
2 simple app and controllers for better understanding purpose.
var parentApp = angular.module('parentApp', []);
parentApp.controller('mainController', function($scope, $http) {
$scope.parentName = 'Parent!!'
});
var childApp = angular.module('childApp', []);
childApp.controller('secondController', function($scope, $http) {
$scope.childName = 'Child!!'
});
You cannot use 2 ng-app attribute on a page.
However, looking at your requirement, I think you need to use 2 controllers and do some stuff with it while maintaining the appearance of parent-child in the HTML structure. To, do that you can make use of the angular.bootstrap method.
So, you can modify the html as below:
<div id="parentApp" ng-cloak="" ng-controller="mainController">
<div id="someContent">
{{$scope.parentName}}
***some parent app actions***
</div>
<div id="childApp" ng-app="childApp">
<div id="someContent" ng-controller="secondController">
{{$scope.childName}}
***some child app actions***
</div>
</div>
</div>
And in your code, you can initialize it as below:
var parentAppDivId = document.getElementById('parentApp');
angular.bootstrap(parentAppDivId, ['parentApp']);
var parentApp = angular.module('parentApp', []);
parentApp.controller('mainController', function($scope, $http) {
$scope.parentName = 'Parent!!'
});
var childAppDivId = document.getElementById('childApp');
angular.bootstrap(childAppDivId, ['childApp']);
var childApp = angular.module('childApp', []);
childApp.controller('secondController', function($scope, $http) {
$scope.childName = 'Child!!'
});
If you already have two separate apps and are trying to leverage parts of one in another app, you can inject the module from one app into another. It's the the same structure as you've outlined above, but it will make everything from the first app available in the second app.
angular.module('childApp', ['parentApp'])...
UPDATE 8:
CODE:
<% include ../partials/header %>
<script src="https://www.gstatic.com/firebasejs/3.5.2/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/firebase-util/0.2.5/firebase-util.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.2/angular.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/1.1.4/angularfire.min.js"></script>
<script>
var config = {
info
};
firebase.initializeApp(config);
var fb = firebase.database().ref("posts/fun");
var app = angular.module('app', ['firebase']);
app.controller('ctrl', function ($scope, $firebaseArray, $timeout) {
$scope.data = [];
var _start = 0;
var _end = 4;
var _n = 5;
$scope.getDataset = function() {
fb.orderByChild('id').startAt(_start).endAt(_end).limitToLast(_n).on("child_added", function(dataSnapshot) {
$scope.data.push(dataSnapshot.val());
console.log("THE VALUE:"+$scope.data);
});
_start = _start + _n;
_end = _end + _n;
};
$scope.getDataset()
});
// Compile the whole <body> with the angular module named "app"
angular.bootstrap(document.body, ['app']);
</script>
<div class ="containerMarginsIndex">
<div ng-controller="ctrl">
<div class="fun" ng-repeat="d in data">
<h3 class="text-left">{{d.title}}</h3>
<div class = "postImgIndex">
<a href="details/{{d.id}}" target="_blank">
<img class= "imgIndex" ng-src="/images/uploads/{{d.image}}" >
</a>
</div>
<div class="postScore">{{d.upvotes - d.downvotes}} HP</div>
</div>
</div>
</div>
<% include ../partials/footer %>
SITUATION:
Ok, I have reworked my Firebase database architecture and changed the Firebase rules.
I am now certain the Firebase function returns a value (it is logged in the console).
But I still get the following error:
This HTML:
<div class="fun" ng-repeat="d in data">
<h3 class="text-left">{{d.title}}</h3>
<div class = "postImgIndex">
<a href="details/{{d.id}}" target="_blank">
<img class= "imgIndex" ng-src="/images/uploads/{{d.image}}" >
</a>
</div>
<div class="postScore">{{d.upvotes - d.downvotes}} HP</div>
</div>
gets REPLACED by this once RENDERED:
<!-- ngRepeat: d in data --> == $0
What have I done wrong ?
It's not displaying in your view because you have nothing on the $scope and you're not using {{}} to interpolate your data. See the following changes:
Assign data to a $scope variable to be used in the view:
$scope.data = [];
var _start = 0;
var _end = 4;
var _n = 5;
var getDataset = function() {
fb.orderByChild('time').startAt(_start).endAt(_end).limitToLast(_n).on("child_added", function(dataSnapshot) {
$scope.data.push(dataSnapshot.val());
});
_start = _start + _n;
_end = _end + _n;
And your view, use ngRepeat and {{}} to interpolate:
<div class ="containerMarginsIndex">
<div class="fun" ng-repeat="d in data">
<h3 class="text-left">{{d.title}}</h3>
<div class = "postImgIndex">
<a href="details/{{post.id}}" target="_blank">
<img class= "imgIndex" src="/images/uploads/{{post.image}}" >
</a>
</div>
<div class="postScore">({{d.upvotes - d.downvotes}}) HP</div>
</div>
</div>
Add your scroll listener within your controller. The function more does not exist indeed, however you do have a $scope.more method.
app.controller('ctrl', function ($scope, $firebaseArray, $timeout) {
// ORDERED BY TIME:
var ref = firebase.database().ref("posts/fun");
var scrollRef = new Firebase.util.Scroll(ref, "time");
$scope.posts = $firebaseArray(scrollRef);
scrollRef.scroll.next(5);
// AS DATA APPEARS IN DATABASE ORDERED BY TIME:
ref.once('value', function(snap) {
$scope.rawdata = snap.val();
$scope.$apply();
});
$scope.more = function() {
scrollRef.scroll.next(5);
};
// Add scroll listener
window.addEventListener('scroll', function() {
if (window.scrollY === document.body.scrollHeight - window.innerHeight) {
$scope.$apply($scope.more);
}
});
});
Note that I am calling $scope.more within $scope.$apply so that the scope is digested at the end of the call. Indeed a JS listener on a window scroll event is out of the Angular lifecycle so we need to manually $digest the scope for Angular to update all its watchers and update the HTML. Search online about $scope.$apply if you want to learn more about it.
About your first problem
Your angular application is not started because angular is never initialized. For that you need either to load it synchronously and use the ng-app directive, or if you don't want to change anything with your code you can simply add these lines after your module and controller definition:
// Compile the whole <body> with the angular module named "app"
angular.bootstrap(document.body, ['app']);
You need to include $scope.$apply() because the the scroll event executes outside Angular's context.
Also the event listener should be inside your controller so that the scoped more function is accessible.
Here's an updated fiddle:
https://jsfiddle.net/xue8odfc/2/
I'd say the problem with Angular not resolving the {{post.image}} etc. is due to incompatibilities among the libraries you are referencing. I suggest testing using the versions from the working jsfiddle:
<script src="https://cdn.firebase.com/js/client/2.0.3/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/firebase-util/0.2.5/firebase-util.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.min.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/1.1.4/angularfire.min.js"></script>
Consider two div areas as follows, in html file
<div class="divArea1" ng-controller="myController">
<input ng-click="updateName()" type="button" value="button"/>
</div>
<div class="divArea1" ng-controller="myController">
<p>{{name}}</p>
</div>
Following is the angular js example
productApp.controller("myController", [ '$scope', function($scope) {
$scope.name= "XYZ";
$scope.updateName= function() {
$scope.name = "ABC";
};
} ]);
problem is when I am trying to update the name, upon click on update button it is not visible in the second in the div area. Is there any mistake i am doing.
What you have is two different controllers (with two separate scopes) with the same name.
You need to put the controller in the parent controller to keep the name in the same scope as the button:
<div id="container" ng-controller="myController">
<div class="divArea1">
<input ng-click="updateName()" type="button" value="button"/>
</div>
<div class="divArea1">
<p>{{name}}</p>
</div>
</div>
Controllers are not singletons. Each time you have a new controller, you're having a new instance of this controller, with a new isolated scope.
If your need is to share data between controllers, you should use a factory (which is a singleton).
angular.module('app').factory('mySharedData', function(){
var service = {
object : objectToShare
};
var objectToShare = {};
return service;
});
And from your controller :
angular.module('app').controller('myController',
['$scope','mySharedData',
function($scope, mySharedData){
$scope.someObject = mySharedData.object;
$scope.updateName= function() {
$scope.someObject.name = "ABC";
};
}
]);
And from your view :
<div class="divArea1" ng-controller="myController">
<input ng-click="updateName()" type="button" value="button"/>
</div>
<div class="divArea1" ng-controller="myController">
<p>{{someObject.name}}</p>
</div>
Note : I've encapsulated the name property into an object because objects are passed by reference, and strings by value. This allows you to make it easier to share your values and to have it automatically updated into the service and other controllers, without having to access your property through accessors.
here is demo http://jsfiddle.net/wg7pb1yu/3/
inject $rootScope so that it will do from global scope
productApp.controller("myController", [ '$scope','$rootScope', function($scope, $rootScope) {
$rootScope.name= "XYZ";
$scope.updateName= function() {
$rootScope.name = "ABC";
};} ]);
Hope this will help you
So I have a bootstrap list:
<div class="ajax_company_list" ng-app="app">
<div class='list-group' ng-controller="PolicyController as policyCtrl">
<a href="#" class='list-group-item' ng-repeat="company in policyCtrl.companies">{{company.primary_name}}
</a>
<div id="loadingIcon" class='list-group-item'>
Loading...
</div>
</div>
</div>
Here is my Angular Javascript:
var app = angular.module('app', []);
app.controller('PolicyController', ['$scope', 'CompanyService', function($scope, CompanyService) {
$scope.companies = [
{
policy_number: 12345,
primary_name: "test"
}
];
$scope.getCompanies = function() {
CompanyService.fetchCompanies()
.success(function(data) {
$scope.companies = data.companies;
})
}
}]);
app.factory('CompanyService', ['$http', function($http) {
return {
fetchCompanies: function() {
return $http.get('http://spoonerinc:8886//json/glmod_Spooner-Inc?pagenum=1');
}
}
}]);
I basically have 2 questions. If I set $scope.companies equal to an array of objects, it does not show up but if I change $scope.companies to this.companies, it starts working again. Why is this?
2nd question, I can see the service call running in my net tab and can console.log the data and it reads fine. But it is not updating my actual list at all and I'm not sure what I'm doing wrong.
I am fairly new to Angular so if there is any advice on how I can do my code better, please let me know.
Thanks!
Because you are using the "Controller As" syntax, which effectively publishes the entire controller object to the scope.
What happens under the hood looks something like this:
function myCtrl($scope){
$scope['someAlias'] = this;
}
If you are going to use the controller as syntax, it's best to use a more object based approach instead of pushing things onto the $scope
Either on the prototype:
function myCtrl(companiesService){
this.companiesService = companiesService;
this.init();
}
myCtrl.prototype = {
init:function(){
var _this = this;
_this.companiesService.get()
.then(function(result){
_this.companies = result.data;
});
}
};
Or as closure style object:
function myCtrl(comapniesService){
var ctrl = {};
function init(){
companiesService.get()
.then(function(result){
ctrl.companies = result.data;
});
}
return ctrl;
}
For your second question, I think your problem is here:
ng-repeat="company in policyCtrl.companies"
You don't need to specify the controller as a prefix, since you've already declared it with ng-controller. It should be:
ng-repeat="company in companies"
And ng-controller to be:
ng-controller="PolicyController"
My guess is that the first problem will go away once you correct this.
I have some trouble identifying a view update that is not happening neither when I set a property on the $scope nor when I set a property on the controller prototype.
I have create a quick example of what I am doing to illustrate the issue.
I have, for instance the following view:
<html data-ng-app="test">
<head>
<script data-require="angular.js#1.2.18" data-semver="1.2.18" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<h1>Hello Plunker!</h1>
<div data-catalog="{ 'type': 'Category1', 'key': '252' }">
<span class="text-danger">{{query | json}}</span>
<span class="text-danger">{{catalog.model | json}}</span>
</div>
</body>
</html>
And a simple module with a directive and a controller:
angular
.module("test", [])
.controller("CatalogCtrl", ["$scope", "$parse", "$location", "$q", function ($scope, $parse, $location, $q) {
var catalog = this,
listener = function () {
var query = {},
params = $location.search();
try
{
query = $parse($scope.query)();
}
catch (exception)
{
console.error(exception);
}
if (_.isEmpty(query))
{
return;
}
$q
.all([
scroll({ top: 0 }),
Catalog.searchProducts(_.assign(query, params))
])
.then(function (response) {
catalog.model = response[1];
console.log(catalog.model, response[1]);
})
.catch(function () {
catalog.model = {};
})
.finally(function () {});
};
catalog.model = {test:""};
//$scope.listen("$locationChangeSuccess", listener);
}])
.directive("catalog", function () {
return {
controller: "CatalogCtrl as catalog",
scope: {
query: "#catalog"
},
restrict: "A",
replace: false
};
});
The issue is that neither {{query | json}} nor {{catalog.model | json}} are rendered in the view and I am not sure what is the cause of it. It might be something I am missing or doing wrong, but I could use some help with it if anyone spots my mistake :)
There are a few issues with your code...
1.
Your directive has it controlelr attached and you place some values on its scope (query, catalog etc). Then you try to access those values from an element that is outside of the directive (and thus has a different scope that knows nothing about query, catalog etc. E.g.
<!-- Let's say somehow DIV#1 has scope $001 -->
<div id="1">
<!-- Somehow DIV#1.1 creates a new scope ($002) -->
<div id="1.1" some-directive>
<!-- DIV#1.1.1 will have access to scope $002 -->
<!-- (under certain cirsumstances) -->
<div id="1.1.1"></div>
</div>
<!-- DIV#1.2 (which is outside of DIV#1.1) -->
<!-- will have access to scope $001 (but not $002) -->
<div id="1.2"></div>
</div>
2.
To make things even more complicated, your directive creates an isolate scope, which means that any content it has will not see your directive's scope, but its parent scope. I.e. in the example above, DIV#1.1.1 will have access to scope $001, not $002.
What you can do about it (which basically mean, explicitly state that your directive's content should be included (transcluded) into its template. This gives you greater control on what's going on and allows you to bind the content of your directive to the scope you want (i.e. your directive's isolate scope).
The resulting code should look like this:
<div data-pj-catalog="{ 'type': 'Category1', 'key': '252' }">
...
<div class="col-12">
...
<span class="text-danger">{{query | json}}</span>
<span class="text-danger">{{catalog.model | json}}</span>
...
<div>
</div>
.directive('pjCatalog', function () {
return {
restrict: 'A',
transclude: true,
template: '<div ng-transclude></div>',
controller: 'CatalogCtrl',
scope: {
query: '#pjCatalog'
},
link: function (scope, elem, attrs, ctrls, transFn) {
transFn(scope, function (cloned) {
elem.empty().append(cloned);
});
}
};
})
See, also, this short demo.
Note:
This is considered "advanced" directive stuff (so it sounds (and is) much more complicated than most directive stuff) and should be required in rae cases only.
I am pretty sure there is a much easier way to achieve what you want (e.g. using a non-isolate scope) with slight modifications (but I am not sure what you want in order to help further.