AngularJS binding template variable - angularjs

I have to renderize the template dynamically depending the layout sended (for now we have original and alternative).
In the beggining I was traking manually in the html. Like this:
<component layout="original"></component>
Component template:
template: ($element, $attrs) => {
let process = 'original';
if ($attrs.layout) {
process = $attrs.layout;
}
return require(`./templates/${process}.html`);
}
But now I have to compile according the variable. For example:
<component layout="{{vm.templateType}}"></component>
But when I acess the $attrs in the template the Angular is not compiled and the result is the string like this:"{{vm.templateType}}".
There is a way to force the template compilation before run the template function?

This is a problem in AngularJS you can check the discuss here: #2895 and #13526.
To solve it I had to use the $compile in the controller like this:
this.$onChanges = function (obj) {
const layout = require(`./templates/${obj.layout.currentValue}.html`);
$element.append($compile(layout)($scope));
};
And remove the template attr of the component.

Related

angularjs component controller not passing initial binding value to directive in template (summernote)

I have this rather simple angularjs component that has an optional height binding:
"use strict";
angular
.module("app")
.component("myComp", getComp());
function getComp() {
return {
bindings: getBindings(),
template: getTemplate(),
require: "ngModel",
controller,
};
}
function getBindings() {
return {
height: "<?",
noExternalFonts: "<?",
ngModel: "=",
}
}
function getTemplate() {
return `<summernote height="$ctrl.height" ng-model="$ctrl.ngModel" config="$ctrl.options"></summernote>`
}
function controller(chSummernoteService) {
const ctrl = this;
ctrl.chSummernoteService = chSummernoteService;
ctrl.$onInit = onInit.bind(ctrl);
}
function onInit() {
const ctrl = this;
ctrl.options = ctrl.chSummernoteService.getOptions({
noExternalFonts: ctrl.noExternalFonts,
});
}
The problem is, when I call it, the height is being ignored by summernote, the directive in the template, even tho the other two attributes, ng-model and options work fine.
<my-comp height="400" ng-model="$ctrl.someOtherCtrl></my-comp>
I also checked replated the $ctrl.height in the template with a hard coded number and that DOES work.
Is that some angularjs quirk I'm unaware of? (I'm new to angularjs, coming from React)
Or is that a bug in the summernote directive maybe?
Thanks in advance for any help!
I checked out the angular-summernote src and it appears to be bugged. Their controller is improperly written such that it reads the raw string value of the height attribute no matter what, so it's literally interpreted as "$ctrl.height". It's also written in such a way that it'll still read the raw value even if you try to force it in between interpolation bindings, i.e. height="{{ $ctrl.height }}" won't work either.
Luckily, the config attribute is being parsed properly, and according to the documentation the height can be specified as a property within that instead. So remove the height attribute from your element and within your onInit function, add the height property to your config object:
ctrl.options.height = ctrl.height;

How to make ngIf work after transclusion?

I have a list component where I want to define custom columns inside. These columns get transcluded into the row of the components template. Unfortunately I can't use ngIf in this context.
Here is my $postLink function of the myList component:
const template = $templateCache.get('myList.tpl.html');
const jqTemplate = angular.element(template);
const row = angular.element(jqTemplate.children()[0]);
$transclude(clone => {
row.append(clone);
$element.html(jqTemplate.html());
});
$compile($element.contents())($scope);
Here is a plnkr of the minimal sample: http://plnkr.co/edit/C9Rvs8NiTYsV3pwoPF6a
Is that because of the terminal property? Can someone entlighten me why ngIf does not work like expect it to?
I think trying to perform the operation in postLink phase is too late, since it is first being applied to child elements.
Compile phase seems to be more appropriate. In there things are simpler, you don't even need to use transclusion or clone-linking function. Scope is applied at a later state.
I provide a solution using directive since in cases like this I find component syntax more confusing.
app.directive('myList', function($templateCache){
return {
bindings: {
list: '='
},
transclude: false,
compile: function(tElement) {
const template = $templateCache.get('myList.tpl.html');
const jqTemplate = angular.element(template);
var elemChildren = tElement.children('div');
jqTemplate.children('.row').append(elemChildren);
tElement.append(jqTemplate);
return {};
}
}
});
http://plnkr.co/edit/MrLJmnMKxO8PVPkzE8KK?p=preview

Using transcludeFn in angular components

Is it true, that I cannot customize transclusion in angular components (angular 1.5)? The task I want to solve is passing a template to a component using transclusion and make it able to use "in-the-component" variables. Like this:
<my-items-component items="$ctrl.items">
<div>{{::item.description}}</div>
</my-items-component>
Where item supposed to be put into my-items-component documentation, and used to customize the item presentation inside the component.
I was able to do this with directives, using transcludeFn function, but it seems there are no arguments passed to $postLink component hook.
So, should I use a directive for this or there's another approach?
To use tansclusion in AngularJS 1.5 components you need first to enable tarnsclusion in your component by using transclude: true, then use <ng-transclude></ng-transclude> in your component template.
I have created a sample pen as an example http://codepen.io/fadihania/pen/bwpdPq
I've found an answer.
Access $scope of Component within Transclusion in AngularJS 1.5
This worked with my problem. My example:
<my-custom-component>
<input model="$parent.$ctrl.name">
</my-custom-component>
Then in my component now I have "name". I hope this helped you.
there are 2 ways of solving your problem
to place all our html inside component template definition
app.component('myItemComponent', new myItemComponentConfig());
function myItemComponentConfig() {
this.controller = componentController;
this.template = '<div>{{::item.description}}</div>',
this.bindings = {
this.bindings = {
items:'<'
}
};
this.require = {};
}
use it like this :
<my-items-component items="$ctrl.items"></my-items-component>
2.use Ng-transclude to load child HTML of a component
app.component('myItemComponent', new myItemComponentConfig());
function myItemComponentConfig() {
this.controller = componentController;
this.template = '<div></div>',
this.bindings = {
items:'<'
};
this.require = {};
this.transclude:true;
}
use it like this :
<my-items-component items="$ctrl.items">
<div>{{::item.description}}</div>
</my-items-component>

resulting HTML from $compile(custom-directive) doesn't bind {{values}}

I want to dynamically add Angular custom Directives, but the directive resulting from $compile(directive) doesn't have the 2-ways binding.
Here's my simplified problem: I am using MapBox, and I want to use Directives for the the markers' popup to show, for example, the markers' title. MapBox wants HTML as a String to put inside the popup, so my idea was to pass a $compiled directive, something like $compile('<myDirective></myDirective>')($scope).html().
It replace the directive with its template, but {{values}} are not solved.
I have something like this to create the popup
map.featureLayer.on('layeradd', function(e)
{
var marker = e.layer;
var popupContent = ctrl.createPopup(marker);
// popupContent should be HTML as String
marker.bindPopup(popupContent);
});
ctrl.createPopup(marker) call a function of the controller, that does:
this.createPopup = function(marker)
{
var popup = "<mapbox-marker-popup"
+" title = "+marker.feature.properties.title
+"</mapbox-marker-popup>";
// should return HTML as String
return ($compile(popup)($scope).html());
}
where mapbox-marker-popup is a directive specified as follow:
/* ===== MARKER POPUP DIRECTIVE=========== */
.directive('mapboxMarkerPopup', function() {
return {
restrict: 'E',
template: [
"<p>{{title}}</p>",
].join(""),
scope:
{
title: '#'
}
}
})
Anyway... mapboxMarkerPopup is not working. title is shown as {{title}}
[UPDATE2 - {{title}} not solved]
Here's the JSFiddle
You need to return the compile angular element instead of returning html of that element. Only returning the html will never carry the angular two way binding. By using compiled object you can keep your binding working.
Code
this.createPopup = function(marker) {
var popup = "<mapbox-marker-popup" +
"title = '" + marker.feature.properties.title + "'"
+ "</mapbox-marker-popup>";
return ($compile(popup)($scope)[0]);
};
Working Fiddle
Update
$compile
Compiles an HTML string or DOM into a template and produces a template
function, which can then be used to link scope and the template
together.
Take a look at this link will give you more idea

Angular directive embed in kendo grid does not execute

I'm using kendo ui in my MVC project. So, I have this simple directive that executes when it's not rendered by kendo.
.directive('okok', ['$log', function($log){
return {
link: function (scope, elm) {
$log.log('directive okok!!');
}
};
}])
The directive executes in this line:
<h2 okok>Hello??</h2>
But it does not execute when razor generates the html. Here
#(Html.Kendo().Grid(Model.CoolModel)
.Name("CoolGrid")
.Columns(cols => {
cols.Bound(c => c.StatusDescription).Title("This is my test")
.ClientTemplate("<div okok></div>");
})
/* Mode code :) */
)
Please note the line: .ClientTemplate("<div okok></div>");
Not sure if the output is handle as a string an I have to do something else. Help is appreciated!
I manage this scenario by compiling the element that wraps the grid when the content is bound.
Kendo has an example in its Q&A page where they show how to embed a menu into a grid.cell and the script that enables the submenu has to be executed after the content is render. It kinda lame.
So, I have a function in my controller that compiles an element in the scope.
$scope.rebindElm = function(elm){
$compile(elm)($scope);
}
And in the view a script is executed when kendo.grid.OnContentBound (or something like that)
function bindContent(e){
var elm = angular.element('gridName');
var scope = elm.scope();
if (scope != null) {
scope.rebindElm(elm);
}
}
Yes, it feels like a hack but that's the only way I found to get directives to execute when they are generated with kendo.mvc

Resources