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
Related
I am new to angular js.Please suggest which one is better . I have a page where I can select a no of check boxes and navigate to 2 nd page.I want to show the checked values when I press back button in browser.
Which one is better using $rootScope or stateparam or localstorage.Please consider performance also.
Going through your options one by one:
$rootScope: You should try to avoid using this option as it is associated to the global state. Use this option only if you want the data you set to be globally available throughout the app and do not want to reuse the same variable names anywhere else.
$stateParams: These typically go into the state URI as a dynamic part of the whole URL. This has only limited usage, and may not be a good idea for your use case where you need to store data from multiple checkboxes.
localStorage: This is slower than using a JavaScript variable, and hence not a good idea.
Services:
Here is a better solution for you, entirely within the AngularJS paradigm: AngularJS services
Excerpt from the link in point 3:
Data should also be stored in services, except where it is being bound
to the $scope. Services are singletons that persist throughout the
lifetime of the application, while controllers are transient between
application states. If data is stored in the controller then it will
need to be fetched from somewhere when it is reinstantiated. Even if
the data is stored in localStorage, it's an order of magnitude slower
to retrieve than from with a Javascript variable.
You can use the service to set and get your data inside your controller anywhere in the app in a consistent manner.
Here is an indicative example:
app.factory('formDetails', formDetails);
function formDetails() {
var formData = {};
return {
getProperty: function () {
return formData;
},
setProperty: function(values) {
formData = values;
}
};
};
Use it in your controller as:
app.controller('MyController', [ '$scope', 'formDetails' , function($scope, formDetails) {
$scope.checkboxData = {};
// on load call service 'get' function
$scope.checkboxData = formDetails.getProperty();
// on some event/watch function call service 'set' function
formDetails.setProperty($scope.checkboxData);
}]);
I'm working with a web application that allows designers to create pages by writing html in a combination with angularjs directives that are created by myself and other developers. I'm struggling with the best way to populate the directives with data.
Initial attempt was to keep all directives completely self contained. So a product page for example might look like the following (with custom html around all of this - left out for clarity):
<product>
<product-information></product-information>
<product-image></product-image>
<product-quantities></product-quantities>
<product-add-to-cart-button></product-add-to-cart-button>
</product>
If the directives need data (which almost all do) they would use a service to call a web API and get the data they need. There are a few issues that have arisen with this approach.
The directives often need some information from a parent or sibling. In the example below, product-image likely needs the ProductID so it can get the correct image. In this case I have to rely on getting that information from a querystring parameter or store in an angularJS service that is initially populated by the parent directive.
Lots of API calls. With every directive making it's own API calls I'm now ending up with pages that have 15+ API calls to load, and that can be expected to grow over time. Even though a lot of the data may be closely related (even in the same database table). Obviously this is less than ideal.
So I've started changing my approach with the second pass through. Now the directives are set up like a tree structure which each directive expecting it's data requirements to be passed in through an attribute. Here's an example:
<product-image product-url="vm.product.imageUrl" ng-if="vm.product"></product-image>
This solves the problem #2 of too many API and database calls but exposes too many internals to the designer. Now the designer has to know to pass in product-url and must understand that there is a vm.product behind the scenes. He may even need to understand some angularJS (ng-if). I've seen this pattern used a lot even in Angular2 with Inputs. Seems fine for developer usage but not for designer used directives, we want to hide the inner workings and complexity while giving the designer the power of controlling the layout.
Finally, I'm considering using the parent controller to populate everything that might be needed on the page. Then all the child directives will just use a service like they are now but instead of calling an API, the data is already loaded. The directives remain simple and mostly self-contained, but their data load is triggered by a parent. The only issue I have with this is that we may end up loading a lot of data that is unused because of directives not being used by the designer. But I feel like this is a necessary trade off.
Has anyone built something similar, are there any possible approaches I am missing?
You can use a component tree with your service.
Starting with a designer friendly:
<div ng-app="MyApp">
<product id="1">
<product-image></product-image>
</product>
</div>
Something like this sorta works:
angular.module('MyApp', [])
.factory('api', function($q){
return {
loadProduct: function(id){
return $q.when({
id: id,
imageUrl: 'http://i2.cdn.turner.com/cnnnext/dam/assets/160407085910-setsuna-main-overlay-tease.jpg'
})
}
};
})
.component('product', {
transclude: true,
bindings: {
id: '='
},
template: [
'<div ng-transclude></div>'
].join(''),
controller: function(api) {
var self = this;
this.$onInit = function() {
self.data = api.loadProduct(this.id);
};
}
})
.component('productImage', {
require: {
product: '^product'
},
bindings: {
},
template: [
'<pre>{{ $ctrl.url | json }}</pre>'
].join(''),
controller: function() {
var self = this;
this.url = false;
this.$onInit = function() {
this.product.data.then(function(data){
self.url = data.imageUrl;
})
};
}
})
See this CodePen:
http://codepen.io/anon/pen/wGmEKP?editors=1011
I'm building a web application and I have a screen that consists in five sections, each section represents a level, the areas are the higher level of my tree, when I click in any card of the area, the system should return the skills of that area and so on.
I need to change the url and state according what the user is accessing, for example, if the user access some skill, the url must be
example.com/#/curriculum/skill/<skillId>
and if I access this link it should automatically load the capabilities from this skill and his parent which is area in this case.
I have one controller for area, skill, capability, knowledge and criteria, in each controller I have a action to load the next level of the tree, which looks like that
$scope.loadSkills = function (id) {
Area.loadSkills(...)
$state.go('curriculo.skill', {id: this.current.id}, {nofity: false, reload: false});
}
And these are my states
$stateProvider
.state('curriculum', {
url: '/curriculum',
templateUrl: '/templates/curriculo.html',
})
.state('curriculum.are', {
url: '/area/:id',
template: '',
})
.state('curriculum.skill', {
url: '/skill/:id',
template: '',
})
.state('curriculum.capability', {
url: '/capability/:id',
})
.state('curriculum.knowledge', {
url: '/knowledge/:id',
})
.state('curriculum.criteria', {
url: '/criteria/:id',
});
I'm new in Angular and I not sure about what to do, should I created multiple nested views in this case, and if so, how do I load stuff that I need according the url?
I would suggest to use the capability of multiple named views offered by the ui-router. You can read more about it here. Basically the documentation says the following:
You can name your views so that you can have more than one ui-view per
template.
If you check the example in the documentation, you'll notive that there are similarities between your scenario and the example, because you want to dynamically populate a different views (here named views).
Example
I tried to recreate your scenario in this JSFiddle.
First I created an abstract state which provides the different views like areas, skills etc. This is the template for the abstract state:
<div class="curriculum" ui-view="areas"></div>
<div class="curriculum" ui-view="skills"></div>
Next I created a nested state curriculo.main, which declares the different views (areas, skills etc.) you need. Each view has its own template and controller. Notice that the nested state has a resolve which initially loads the areas from a service called curriculo. If you use resolves remember that the resolve keyword MUST be relative to the state not the views (when using multiple views).
Basically the service is responsible for the business logic, means getting the areas, skills etc. In the JSFiddle I have hard-coded the HTTP results. Replace that with HTTP calls and make use of promises. Since each named view has its own controller we need a mechanism to notify about changes, for example to notify the SkillsController that skills have been loaded. Thus, I created a simple event system (subcribe-notify):
.factory('notifier', function($rootScope) {
return {
subscribe: function(scope, callback, eventName) {
var handler = $rootScope.$on(eventName, callback);
scope.$on('$destroy', handler);
},
notify: function(eventName, data) {
$rootScope.$emit(eventName, data);
}
};
});
The SkillsController can then subscribe to a specific event like so:
notifier.subscribe($scope, function(event, data) {
$scope.skills = data;
}, 'onSkillsLoaded');
The curriculo service calls (at the end of the getSkills()) notifyand provides an event. In this case the same event as you subscribed to in the SkillsController.
notifier.notify('onSkillsLoaded', result);
All in all, that's the magic behind my little example. It's worth mentioning that you need to apply best practices to the code, since this is just to recreate your scenario. For best practices I suggest the Angular Style Guide by John Papa.
Update 1
I updated my example to show you deep linking. I simulate the deep link via
$state.go('.', {area: 2, skill: 5});
This way I can activate a certain state. Now each view has its activate function. Inside this function I do all the work that is neseccary for the initialization, e.g. selecting an area if the query param is set. As you know, you can access the params with the $state service. I had to use a $timeout to delay the init of the areas controller because the subscribe wasn't made yet. Please try to find a better solution to this problem. Maybe you can use promises or register each controller in a service which resolves if all controller have been initialized.
If anything has been selected I also use the go with an additional option to set the notify to false.
$state.go('.', {area: area.id, skill: skillId ? skillId : undefined}, {notify: false});
If notify is set to false it will prevent the controllers from being reinitialized. Thus you can only update the URL and no state change will happen.
When I use this it works:`
angular.module('app').service('DataService', function() {
return {theme: "amelia"}
});
But when I use this, there is no update? Can you tell me the difference?
angular.module('app').service('DataService', function() {
return {
theme: function() {
return {theme: "amelia"}
}
};
});
Controller
$scope.settings = DataService.theme();
Jade
select.form-control(ng-model="settings.theme", ng-options="theme for theme in themes")
Is it possible to get the second way working? Because I will share more data then one Object!
Thank you!
The first version of the code calls the function once to instantiate the service. After that, because services are singletons in angular the function isn't called again, but rather the return value (a "static" object) is accessed in every controller that uses the service after that.
The second version, each controller you inject the service into calls the theme function, which instantiates a brand new object each time. You have now effectively mitigated the fact that the service is a singleton. This is why data will not be shared with the second set of code.
If you put a break point on the function call in each case and run your code you should see the first version called once while the second version will be called many times.
"Get It Working"...
You can't really make it work with a function call but if you need to share multiple data objects there isn't any reason not to nest them. You could very easily do something like:
angular.module('app').service('DataService', function() {
return {
dataObjects: [
{"type":"theme", "theme":"amelia"},
{"type":"user", "id":123, "name":"ABC"}
]};
});
In the example I added a second object which is a user object to make shared "dataObjects" array. To find a specific object in the "dataObjects" array, you could loop till you find the correct type ("theme", for example). If necessary, you could even nest one level deeper if you needed the objects to be pristine (without the added type attribute).
Hope that helps!
It should be theme: function().... inside your service. Replace "=" with ":".
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.