I have a page and a model on my scope $scope.model = { ... };
I bind values on my page with values on the model which is nice. I have an address nested somewhere in that model, for the sake of this example lets say
$scope.model = { visits : [{date : ..., addresses : [{ ... }, ...]},
...], ...};
I need to write all the information of the first visits first address somewhere in a div. I know i can type all the fields like this model.visits[0].addresses[0].Zip etc. but there are like 5 fields in model.visits[0].addresses[0] that i need to output. I figured, there most be an easier way? I considered putting a
<div ng-init="a = model.visits[0].addresses[0]">{{a.Zip}}...</div>
and then just access all properties like that. What i really want though, it not to create a new property on the scope called a unless i can narrow the scope for a to just that one div.
Is that possible somehow?
Clarification: I know i can probably redo my model or move the data up on model itself, but this is just something I've run into multiple times and i would just really want to know if there is a solution to a problem like this.
You could create a tiny directive that creates a new scope on the elements you want:
...
angular.module('yourApp').directive('newScope', function () {
return {scope: true};
});
Then you can use it like this:
<div ng-init="tmp=model.visits[0].addresses[0]" new-scope>{{tmp.Zip}}...</div>
Now, the tmp property will be attached to the new scope created by the newScope directive and will not affect the parent scope.
See, also, this short demo.
Related
I have 4 controllers in my Angular 1.x app:
houseController
apartmentController
addressController
contactInfoController
With addressController & contactInfoController I update the address and contact info of a house or an apartment.
I work in the following way:
<div ng-controller="houseController" ng-init="getHouseInformation()>
{{house.contact_info.email}}
{{house.contact_info.mobile_phone_number}}
<a ng-controller="contactInfoController" ng-click="showContactInfoEdit(house.contact_info.id)">Edit</a>
</div>
When the update of the contact information is successfull within the contactInfoController, I want to update the house information getHouseInformation() in the houseController (read: make a new call to the API to get the updated information).
Since I have more than one (and in the future even more) house/apartment/... controllers, I need to have a solid way on how to 'refresh' the scope of these apartments, houses, ... on the fly.
What would be the best solution in my case?
Edit:
My scopes look like this:
// in houseController
$scope.house = {
id : 1,
title : "House title",
contact_info : {
email: '',
mobile_phone_number : ''
}
}
// in apartmentController
$scope.apartment = {
id : 1,
title : "Apartment title",
contact_info : {
email: '',
mobile_phone_number : ''
}
}
// in contactInfoController
$scope.contact_info : {
email: '',
mobile_phone_number : ''
}
So when updating the contact information scope, I'm not directly changing the house scope... Hope this helps.
I disagree with the other advice you got here - directives help you intelligently manipulate the DOM with your data but not necessarily share it between scopes, which is what you're asking about. As I wrote to you on Slack, what you seem to be looking for is a Service which will contain all your data (either declared in it or linked to an external file or API), and then injected into every controller that needs access to that data. That's one of the main uses for services!
For posterity, here's what I wrote to you on Slack:
"...You’re currently creating unrelated objects that don’t communicate - why should they? They’re in different scopes. You’re actually not getting any use out of Angular and could use vanilla Javascript for that:)
The idea is to use persistent data that is shared across your web app. $scope.anything will never communicate outside its scope unless you bind it to something outside the scope, like a service. Whether the service draws data from an external API or really just an object model that lies outside of Angular, (on your server/file structure, for example), or even defined within the service itself doesn’t matter (although the latter is far from a best practice) - but you need something persistent outside of the local scopes that's shared between your controllers/views.
Then there are a few ways to connect the dots. A very common (and probably the best) design pattern is to create a service (NOT directive! I don’t know why they gave you that advice) that encapsulates that data. So, for example,
myApp.service(‘dataModel', function(){
$scope.houses = {}; //an object containing ALL the houses, each one containing ALL the apartments, each apt.containing ALL the contact info, etc. etc.
});
Then in your controllers you pass the dataModel service, and then declare and link the local scope 'reference' of the same object to it, for example:
myApp.controller(‘buildingsView’, [dataModel, function(dataModel){
//and link to that service’s object locally:
$scope.houses = dataModel.houses;
}]);
Then, once you affect that model in one view/controller, the data in the other views/controllers will magically change too!
And that is the angular way:)
Hope this makes sense!
(To clarify, there are two issues here: getting your data INTO angular and then SHARING that same object by injecting the same service to your various controllers. That’s sort of the exact idea of services - they’re singletons, which means only one copy of them exists at any time and if they’re referred to more than once, it’ll always be to the same object, which is why manipulating it in one place will change it in another.)"
Put your data within a $scope variable, and make your controllers watch this varaible from scope. When the event is triggered, you can then do what you want.
$scope.houses = [];
$scope.$watch('houses', function(newValue, oldValue) {
// This is triggered when $scope.houses changes directly
// Add true as param to this function if you want to watch properties nested within the houses object
});
If houses is within a controller, use the following:
(in controller)
var self = this;
self.houses = [];
// This tells angular to watch the controller property houses instead of a scope variable
$scope.$watch(function(){return self.houses}, function(newValue, oldValue) {
// This is triggered when $scope.houses changes directly
// Add true as param to this function if you want to watch properties nested within the houses object
});
I suggest to use directive, then it's easier to exchange data. And that is the reason why directive exists. I try to explain how to build your use case with directives. Assume that you have one controller (houseController) and for every sub requirements you make a directive. One for the contact, one for the appartment and one for the address. Then you define your whole object inside houseController. You pass all necessary data as a parameter to the directive and you can access them from the houseController or from inside the directive, because of the two way data binding. In this way you don't have to exchange something or to call update functions. You adjuste just the object.
your html
<div ng-controller="houseController">
<contact data="house.contact"></contact>
<apartment data="house.apartment"></apartment>
...
</div>
house controller
$scope.house = {
apartment:{
floor: 1,
number:34,
...
},
contact:{
id:2,
email:"peter#test.com",
mobile_phone_number:"123456789",
...
},
...
}
contact directive
.directive('contact', function() {
return {
restrict: 'E',
scope: {
data: '='
},
link: function(scope, element, attrs) {
scope.showContactInfoEdit = function(id){
// do your stuff here
scope.data.name = "Peter";
}
},
templateUrl: 'contact.html'
};
});
contact.html
{{data.email}}
{{data.mobile_phone_number}}
<a ng-click="showContactInfoEdit(data.id)">Edit</a>
I made a plunkr: http://plnkr.co/edit/8tRMtgztaXRC3EKyQhcH?p=preview
I'm pretty new to AngularJS, and I have a jQuery background so that could influence my way of thinking the problem.
In order to do DOM manipulation through transclude directive (i.e. adding a specific class) I need to know how many children (or maybe siblings) has a generic element.
What I mean is I would like to set a class on all children, based on an algorithm that counts the number of children themselves.
This is what I tried so far
var main = angular.module("Main",[]);
function utilities(){
this.consoleScope = function($scope){
return $scope.children().length;
};
}
main.service("utilities",[utilities]);
main.controller("Prova",["$scope","utilities",function($scope,utilities){
var self = this;
self.consoleScope = function(){
return utilities.consoleScope($scope);
};
}]);
But even if it runs without errors, it doesn't retrieve the information I wanted. I can comprehend this is not the right way to do this, but I can't see any other way. What could I try?
So you've mixed up your application logic with your DOM logic. Ideally when talking about children you'd be assigning or creating these based upon a data set or collection.
e.g.
//In your controller
$scope.data = someDataSet;
from there you would implement your algorithm based upon your data set.
//still in your controller
$scope.algorithm = function(data){
... implement your logic ...
// e.g.
return data.length > 5;
}
Now in your UI mark up you would use ng-class and an expression to assign the class on any element that needs the class. Your controller shouldn't know about your classes.
<div ng-class="(algorithm(data)) ? 'trueClass' : 'falseClass'" ></div>
This is a really simple implementation but you can extend it pretty easily.
https://docs.angularjs.org/api/ng/directive/ngClass
I have an angular-rails resource with a property that consists of irregular data that is potentially quite complicated-- something like:
{ foo: [ { bar: 'baz', lol: [ { 'omg': ... etc
I built a directive which takes this data and drills down into it, dynamically rendering form fields for each object... I've got the data displaying perfectly, however the piece of the puzzle that's missing is, how can I take advantage of Angular's binding so that changing the value on the form input will actually update that attribute in the model?
Originally I was thinking this should be simple, as my code drills through the data structure, it can just be maintaining a path, so I'd end up with something like: 'myObject.foo.bar'
Then I could just pass that to the form input's ng-model attribute...... however, I could not get angular to recognize ng-model="path" where $scope.path = "myObject.foo.bar"... ng-model="{{path}}" did not work either.
My directive is using angular.forEach to drill down into this datastructure, and someone had mentioned to me that I should perhaps be using ng-repeat instead, but I wasn't sure if this is the correct way to go or not? I still feel like there should just be a way to do ng-model="path" and have that work...
Any guidance would be greatly appreciated.
To use dynamic property names, use array notation. I.e. myObject["foo"]["bar"]. Plunkr: http://plnkr.co/edit/W60F75?p=preview
Can you try setting an property on the scope with the value of the object itself and then refer it in the form element? Like below:
// In your script
$scope.obj = myObject;
// In your form
<input ng-model="obj.foo.bar" type="text" />
I have an array of items bound to <li> elements in a <ul> with AngularJS. I want to be able to click "remove item" next to each of them and have the item removed.
This answer on StackOverflow allows us to do exactly that, but because the name of the array which the elements are being deleted from is hardcoded it is not usable across lists.
You can see an example here on JSfiddle set up, if you try clicking "remove" next to a Game, then the student is removed, not the game.
Passing this back from the button gives me access to the Angular $scope at that point, but I don't know how to cleanly remove that item from the parent array.
I could have the button defined with ng-click="remove('games',this)" and have the function look like this:
$scope.remove = function (arrayName, scope) {
scope.$parent[arrayName].splice(scope.$index,1);
}
(Like this JSFiddle) but naming the parent array while I'm inside it seems like a very good way to break functionality when I edit my code in a year.
Any ideas?
I did not get why you were trying to pass this .. You almost never need to deal with this in angular. ( And I think that is one of its strengths! ).
Here is a fiddle that solves the problem in a slightly different way.
http://jsfiddle.net/WJ226/5/
The controller is now simplified to
function VariousThingsCtrl($scope) {
$scope.students = students;
$scope.games = games;
$scope.remove = function (arrayName,$index) {
$scope[arrayName].splice($index,1);
}
}
Instead of passing the whole scope, why not just pass the $index ? Since you are already in the scope where the arrays are located, it should be pretty easy from then.
Of course the answer is "Use it when you want to show the app". Ok that is fair enough. But what about subviews? I am using Ted's example: https://github.com/t2k/backbone.marionette-RequireJS. That example only has one controller setup. I have six controllers which I copied the libraryController in Ted's example. Each example has the following code:
var _initializeLayout = function() {
console.log('initialize Start Layout...');
Controller.layout = new Layout();
Controller.layout.on("show", function() {
vent.trigger("startLayout:rendered");
});
vent.trigger('app:show', Controller.layout); <!-- is this needed for each?
};
So I have that code in each of my controllers. The StartLayout has two regions that have their own views that are the entry points to the InspectorController and the PlayerController.
Each of those controllers has:
vent.trigger('app:show', Controller.layout);
So it would seem to me that I may be calling 'app:show' more than needed. Once for every Controller that needs initializing.
Is this necessary? I can understand perhaps calling that when I'm dealing with direct child views of the app but if I'm deep into PlayerController and the app view isn't visible it seems like overkill.
Thanks,
Andrew
Try not to think of "calling" app:show. It's not a function, it's an event. An event can have an arbitrary number of subscriptions listening for it. In the case of this application, there is only one listener on that event:
vent.on('app:show', function(appView) {
app.content.show(appView);
});
In this case, it's telling the content region to display whatever view is included in the event as appView. So, if you want to replace the content region with your own view, you should trigger app:show with a parameter of whatever view you want the content region to display.
content is bound to a DOM element, and whenever you call content.show(someView), the contents of that DOM element will be replaced by whatever is generated by someView.render().el.
I would suggest reading up on Layouts, Regions, and Events.
Hope this helps.