Angular - use template as container for multiple templates - angularjs

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.

Related

AngularJS self contained directives for designer use

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

Mechanism for change parts of view (angular js)

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.

AngularJS: How to scale repetitive but slightly-dynamic content

I need to implement a browser-like banner warning system so that the user can only access certain features after they've acknowledged certain warnings. The warning messages will depend on data sent from the server and I have to implement different styles and links.
Edit: There could be any number of warnings displayed at the same time. One for each feature. The user must individually acknowledge each warning before the corresponding feature is enabled. Some of the warning texts are static, some are dynamic. Some can have different acknowledge links instead of the standard "okay".
Traditionally, I would package each kind of warning into a class (in the OO sense) and push them to screen through a centralized method, e.g.
displayWarning(new InfoBanner("You must ... before you can modify " + data.name, function onclick() { ... }));
Here the InfoBanner class will have a method that creates the banner elements and attach the event handler.
The Angular way of doing this, on the other hand, seems to be you write the banner entirely in HTML with ng-if and ng-click, etc. E.g.:
<p style="info banner" ng-if="...">You must ... before you can modify {{...}}. <a href ng-click="...">Okay</a></p>
However, this seems quite unfocused and messy because there will now be a large blob of banner code dwarfing the functional part of the page. (There are hundreds of error types defined!)
Is there any way to resolve this without reverting to the fully imperative code?
(Note: a custom directive is probably not the answer as <p style="info banner" is almost like a directive and there's little sharable code among these warnings beyond this.)
(Edit: One can see this question in another way: in the imperative world, the warning-adding logic are scattered in the code but close to the feature they're protecting, so they're easy to understand and maintain. In the declarative world, they must be centralized to the place where they're displayed. I would like a solution where they're declared close to the component they're protecting but displayed centrally.)
What I understand from your question your problem is that since you are in an Angular application you need / should include your banners as markup in your HTML view, since however whether each banner is displayed or not depends on the data you are getting from the server you only know if a specific banner should be displayed in your controller (hence the ng-if you have included in your banner HTML example).
What I would propose in this case would be to create a BannerService which would hold a list of all the banners that should be displayed at any given time. In your controller you can use the functions exposed by the service to add banners to the list when the data you got from the server indicates that you should do so. Each banner in the list would be an object containing all the information that might be different between different banners (ex. banner text, type, etc.) meaning that your HTML view doesn't really need to "know" anything about specific banner details and can just display all the banners available in the BannerService using an ng-repeat.
You can see below a quick-and-dirty example to better understand how this would work.
var app = angular.module('TestApp', [])
app.service('BannerService', [function(){
var banners = [];
this.getBanners = function() {
return banners;
}
this.addBanner = function(banner) {
banners.push(banner);
}
}])
app.controller('TestCtrl', ['BannerService', function(BannerService) {
// Add banners to the banner service depending on your data etc.
BannerService.addBanner({text: "This is a banner", type: "info"});
}])
app.controller('BannerCtrl', ['$scope', 'BannerService', function($scope, BannerService) {
$scope.banners = []
$scope.$watch('BannerService.getBanners', function (newVal, oldVal, scope) {
$scope.banners = BannerService.getBanners();
});
}])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="TestApp">
<div ng-controller="BannerCtrl">
<div ng-repeat="banner in banners"><p class="banner {{banner.type}}">{{banner.text}}</p></div>
</div>
<div ng-controller="TestCtrl">Page content</div>
</div>
 In my opinion what you are looking to implement fits perfectly with Aspect Oriented Programming. If you've never used AOP before be prepared for some light reading. The concept is simple and works very well with Angular's patterns. There is an AngularAOP project, but before you dive into it I suggest running through this article first:
http://www.bennadel.com/blog/2425-decorating-scope-methods-in-the-angularjs-prototype-chain.htm

How to implement routing based on the $rootScope variable value

I have a requirement to implement a data collection "wizard" app, using AngularJS.
As the user progresses onto the next wizard page, I'm faced with a choice: a) to have each page partial have its own "Next" button, with hg-click explicitly calling the pre-defined next partial page or b) to have just one pair of "Next/Previous" buttons and implement navigation in a more dynamic way, by storing the curtest wizard step in a $rootScope.
My question: If I chose option "b", how what would be the way to implement dynamic routing based on the $rootScope.currentWizardStep value?
Or, maybe, there is a better way to do all this. If You know of such as way, please share :)
You're looking to implement $routParams.
This allows you to name segments of a URL, which then turn into a key/value pair.
from the docs:
// Given:
// URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
// Route: /Chapter/:chapterId/Section/:sectionId
//
// Then
$routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
Then within your controller, you just have to check the URL to determine which page they are on. Using the example given from the docs, if you wanted to identify which chapter they are on, you would do:
var chapter = $routeParams.chapterId;
This would still allow you to keep just one set of 'next/previous' buttons. I don't know how you're incrementing pages (different names, page numbers, etc). But it should be fairly minor to keep track of what pages are next/previous. Especially if you had a master array of all the pages & the order that you expect them to be in.
var wizardPages = {
one: {previous: 'start.html', current: 'one.html', next: 'two.html'},
two: {previous: 'one.html', current: 'two.html', next: 'three.html'},
three: {previous: 'two.html', current: 'three.html', next: 'end.html'}
};
$scope.next = function(){
var current = $routeParams.page;
$scope.template = wizardPages[current].next;
};
$scope.previous = function(){
var current = $routeParams.page;
$scope.template = wizardPages[current].previous;
};
And then your HTML
<div class="" ng-include="template"></div>
And wherever you configure your routes:
$routeProvider
.when('/wizard', {templateURL: 'view/wizard.html'})
.when('/wizard/:page', {templateURL: 'view/wizard.html'});
Your URL would look something like: www.example.com/wizard/one
This would allow you to continue using partials (which would be loaded into your ng-includes)
Populating the $rootScope with statefull data is always a bad idea. Depending on how big your page will be (or to be more precisely: how many subpages you will have) I would even recommend to not use routing at all, but work with the ng-show-Directive and a single Controller, incrementing a value representing the current subpage.
Something like this:
<div ng-show="pageNum == 1">
<h1>First Page</h1>
</div>
<div ng-show="pageNum == 2">
<h1> Second Page</h1>
</div>
<input type="button" ng-click=pageNum++" value="Next Page"/>
Of course this is only possible if you dont need render the Subpages serverside.

AngularJS select2 - how create directive?

(sorry for my english :)
I'm using Select2 in my forms with AngularUI ui-select2 directive like this:
<input ng-model="city" type="text" ui-select2="setupCitySelect" />
where setupCitySelect - object with select2 options. I'm setting him up in appropriate scope
$scope.setupCitySelect = {
allowClear: true,
minimumInputLength: 2
...etc, about 50 SLOC
}
All works fine. But when we have, let say, five select2 elements on page (or part of page) - CitySelect, UserSelect, ConditionSelect etc. we get a tons of code, most of them is the same. AngularUI provides "Global Defaults". So we can move repeating code (in directives):
var dirs = angular.module('vipc.directives', ['ui']);
// defaults setting for UI
dirs.value('ui.config', {
select2: {
allowClear: true,
minimumInputLength: 2,
formatInputTooShort: function(term, minLenght) {
var rest = minLenght - term.length;
return "minimum: "+rest;
},
...etc.
But we still need some work in controllers: to set unique properties, such as ajax-url...
And it's come on several pages, several controller. Ough...
Yes, I can put this into one file, say common.js. But I think - it's not best way. Angulas say: "use directive, Luke!". But how? Too complicated docs. I read docs. Three times.
Without success. I wrote some simple dirs, but this...
It should had 'isolated' scope - can be 2 CitySelect on page - in search form and modal form above. compile function? link function?
All i need is just
<myapp-city-select id="city"></myapp-city-select>
<myapp-user-select></myapp-city-select>
...later, same html file
<myapp-city-select id="city2"></myapp-city-select>
Somebody can help?
It should be in the controller. Here is the plunker. showing you that all directives share the same data.
So you can have a directive with common options in the controller and pass specific options into it.
Another option is have a service that defines common options and inject the service into wherever you want.

Resources