I am building SPA application and I want to get advice about best practice (organize project structure) for follow issue:
My HOME page have some information and place for authentication or greeting user. In this place I need to show one of two states: first - user not is authorized and I show him form to login, second: - user is authorized and I show him "Hello, man".
I know two bad decision for this. 1. I can use ng-switch (and I think that it's not good). 2. I can use something like ng-include wich will call function for get actual html subview. (I think that it worse then first).
Also I listened about "ui router", but I'am not sure that it best way.
How will better for organize my project and what will better to use?
You can use ng-switch or ng-if controlled by a variable isLogged
I would make a template login.html and inside I would use ng-switch.
But a more advanced option is to use a directive controlled by events like:
.directive('loginDialog', function (AUTH_EVENTS) {
return {
restrict: 'A',
template: '<div ng-if="visible"
ng-include="\'login-form.html\'">',
link: function (scope) {
var showDialog = function () {
scope.visible = true;
};
scope.visible = false;
scope.$on(AUTH_EVENTS.notAuthenticated, showDialog);
scope.$on(AUTH_EVENTS.sessionTimeout, showDialog)
}
};
})
I use events in that case because this funcionality is extended to the whole application and it's very easy to control the component sending events from any place I need.
Related
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
Here's a quick question:
Is it possible to have a template in which you have multiple templates and call the one you need, when you need it?
Let me explain this a bit better:
I have a modal that I call this way:
$scope.showErrorModal = ->
errorModal = $ionicPopup.show(
title: 'Issues list'
scope: $scope
templateUrl: './sections/modal/modal.tpl.html'
buttons: [{text: 'Close',type: 'button-assertive'}
])
errorModal.then (res) ->
console.log 'tapped!', res
return
return
as you can see, i'm using an external template.
The problem is that this way i need to create different templates everytime my modal needs to change.
What i'd like to do (if possible), is being able to create various sub-templates inside modal.tpl.html and call them in the right modal.
Here's some example code:
modal.tpl.html:
<div id="error-template">
// here the error-modal stuff
</div>
<div id="success-template">
// here the success-modal stuff
</div>
and from the controller, call them like this, for example:
$scope.showErrorModal = ->
errorModal = $ionicPopup.show(
title: 'Issues list'
scope: $scope
templateUrl: './sections/modal/modal.tpl.html#error-template' //Just to make it clear that i want to use only one part of that file
buttons: [{text: 'Close',type: 'button-assertive'}
])
errorModal.then (res) ->
console.log 'tapped!', res
return
return
Is this pure fiction, or it is possible? Are there any other solutions to solve this type of problems?
Other than reducing the number of network requests, I don't see any real benefit to doing what you mentioned in the question. You may want to use multiple modal directives (errorModal, successModel, etc..) anyway to better compartmentalize your code.
If you want to reduce network requests, there is a $templateCache service that enables you to preload your templates with the first request, in some way like this:
<script type="text/ng-template" id="templateId.html">
<p>This is the content of the template</p>
</script>
You may also want to look at angular ui router which is an alternative router implementation that more easily allows nested and master templates.
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.
Good day! I am rather new to angular and I need help in creating a directive or, maybe, some other solution. I have got a list of news, which are displayed one after another. It is done by ng-repeat. Inside each new there are a things like creationDate, post.media and etc. I would a new to appear in a modal window, when user clicks directly on the div with a new text. But, i would to make a request to the server after click to get the most recent version of the new.
So, this is what I want briefly:
1) User clicks on new's text.
2) The id is sent to the server.
3) Server responds with all post information
4) Some specified template loads with all information got from server and appears in a modal window.
What i tried to do:
I tried to create a directive and placed it on new's text. I created an isolated scope, which expects to have one more attribute, so I could get a postId.
scope: {
postId: '#'
}
I created a template and specified a link to it as templateURL.
Then i created a link function and inside it i created smth like this :
element.on('click', function() {
scope.postInfo = scope.findPostById(postId);
});
But for now, this directive just replaces its innerHTML :))
Some requirments:
Modal window should appear only after server receives all the information.
Thank you :)
Basically ng-repeat creates scope for every single element. So you just need to create a function i.e.
$scope.fetchNews = function(news){
newsRepository.find(news).then(function(result){
$modal.open({
templateUrl:...
controller:...
resolve:{
news:function(){
return result;
}
}
})
})
}
And in the controller of the model you inject as depenency news and after that you are done :)
I'm working on a single page app that behaves similar to a wizard. The user lands on page 1 and needs to save and continue to get to page 2. What's the best way to share those buttons between all the views? My form names are different so I currently have to duplicate these buttons so I can use logic like:
<div class="mySaveButton" ng-disabled="page1Form.$invalid"></div>
but then on page 2:
<div class="mySaveButton" ng-disabled="page2Form.$invalid"></div>
To further complicate matters, saving on page1 posts the data to a different address than page2. I have a navigation controller which is the parent and that needs to be handled as well.
So to summarize I need my buttons (Back, Save and Save and Continue) to do all of the following without having to duplicate the buttons across all views:
Check if the current form is valid
If it's valid, the data for that form needs to post to the correct endpoint for that form
Navigation needs to be notified so that it can update and/or take action
Essentially you need to re-use a template (/controller), and somehow pass options into it. You could probably do something with ngInclude and nesting controllers, but a way that is more friendly to more complex structure later is to create a custom directive. It depends a bit on your exact use-case, and exactly what you need to pass into it, but a simple version would be something like:
app.directive('formButtons', function() {
return {
restrict: 'E',
scope: {
'localBack':'&back',
'localSave':'&save',
'localSaveAndContinue':'&saveAndContinue'
},
templateUrl: 'buttons.html'
};
});
With a template of
<div>
<button ng-click="localBack()">Back</button>
<button ng-click="localSave()">Save</button>
<button ng-click="localSaveAndContinue()">Save & Continue</button>
</div>
The scope options with & each define a function on the directive's scope, that evaluates contents of the attribute on the <form-buttons> element of that name in the parent scope. You can then use the directive as follows
<form-buttons back="back(2)" save="save(2)" save-and-continue="saveAndContinue(2)"></form-buttons>
which will get replaced by the template of the directive, and the function save, back and saveAndContinue, which are defined on the parent scope, will get called when clicking on the buttons in the template, passing the appropriate step number which can be used to customise behaviour.
You can see a working example at http://plnkr.co/edit/fqXowQNYwjQWIbl6A5Vy?p=preview