Angular component with transcluded markup - angularjs

I am trying to create an Angular component and transclude the inner HTML of the component, but the markup of the inner HTML does not seem to be compiling. My use case for this is that the component has an attribute binding that I want to use in multiple ways, so the template will never be exactly the same.
For example, say I have the following simple controller:
class ComponentCtrl {
$onInit() {
this.variable = 'hello world';
}
}
let MyComponent = {
controller: ComponentCtrl
};
app.component('myComponent', MyComponent);
I want the following HTML:
<my-component>
<div style="color: green;">{{ $ctrl.variable }}</div>
</my-component>
<my-component>
<div style="color: red;">{{ $ctrl.variable }}</div>
</my-component>
to render as:
<div style="color: green;">hello world</div>
<div style="color: red;">hello world</div>
However, right now it is only rendering as:
<div style="color: green;"></div>
<div style="color: red;"></div>
without the markup being evaluated.
Is there something I'm doing wrong?

did you write the right name of controller inside your {{ }} in html? you wrote controller: ComponentCtrl and then {{ $ctrl.variable }}. it looks like they must have the same names

I think the problem come from {{ $ctrl.variable }}. In fact $ctrl try to link with a parent controller not with the controller of your component.
If you want interact with the controller of your component you need to use some parameter.

Transclusion is not made by default, you have to especify on your component that it has to be transcluded. Also, you didn't especify on your template where it should be trasncluded. Therefore, your component should look like:
let MyComponent = {
transclude: true, // tell angular to transclude it
template: '<ng-transclude></ng-transclude>', // tell where it will be transcluded
controller: ComponentCtrl
};
app.component('myComponent', MyComponent);
However, how was told on comments, component scopes are always isolated. Therefore, ou won't be able to access {{ $ctrl.variable }} from outside the component.

The transcluded content's scope has a $parent property that always points to the host component's scope.
So you could do something like this -
<my-component>
<div style="color: green;">{{ $parent.$ctrl.variable }}</div>
</my-component>
<my-component>
<div style="color: red;">{{ $parent.$ctrl.variable }}</div>
</my-component>
Plunk link that uses $parent property - http://run.plnkr.co/preview/ckdwiuzlb00073b661a7blt3f/

Related

AngularJS: Use variable inside component

I want to use an input variable and display it inside my component HTML but I can't get it to work.
I'm pretty sure I miss something important here but can't say what.
Here is my component declaration:
app.component('requestSummary', {
templateUrl: "./Template/request-summary",
controller: function RequestSummary() {
var vm = this;
},
bindings: {
request: "="
}
});
The component template:
<div>
<h1>{{ vm.request.Pnr }}</h1>
</div>
(I have also tried without the vm)
The component use:
<md-card ng-repeat="request in vm.requests">
<md-card-content>
<request-summary request="request"></request-summary>
</md-card-content>
</md-card>
When I do a console.log(vm) inside the component controller, I can see my request is there:
But I don't know how to print it inside the HTML.
Any help is appreciated.
Components have an automatic default controllerAs controller with an alias of $ctrl. You need to use:
<div>
<h1>{{ $ctrl.request.Pnr }}</h1>
</div>
And you can get rid of the var vm = this;.

How to avoid `require` and Access the controller of the parent component in transclusion

I'm trying to build a form component that receives an object as input and use the template defined into the object to ng-include the right template to render the form defined in the model.
The problem I have is the object might be defined in the above component. For example this:
<somecomponent>
<formx object="$ctrl.settings"></formx>
</somecomponent>
Unfortunately, it doesn't seem to work. From what I read the transcluded block should be using the scope of the above controller. Is there a way to access the scope of the component somecomponent?
By the way, what I'm looking for is to do the same as:
<div ng-controller="SomeController as ctrl">
<formx object="ctrl.settings"></formx>
</div>
But instead of using a plain controller I'd like to use a component without using an explicit require as the parent component might be different from time to time.
With components the ng-include directive adds a child scope to the isolate scope. Transcluded components need to reference $parent:
<somecomponent settings="'ss'">
̶<̶f̶o̶r̶m̶x̶ ̶o̶b̶j̶e̶c̶t̶=̶"̶$̶c̶t̶r̶l̶.̶s̶e̶t̶t̶i̶n̶g̶s̶"̶>̶<̶/̶f̶o̶r̶m̶x̶>̶
<formx object="$parent.$ctrl.settings"></formx>
</somecomponent>
The DEMO
angular.module("app",[])
.component("somecomponent",{
transclude: true,
bindings: {settings:"<"},
template: `
<fieldset>
somecomponent scope-{{$id}}
<ng-transclude>
</ng-transclude>
</fieldset>
`
})
.component("formx",{
bindings: {object:"<"},
template: `
<fieldset>
formx scope-{{$id}}<br>
object={{$ctrl.object}}
</fieldset>
`
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app">
<somecomponent settings="'ss'">
<formx object="$parent.$ctrl.settings"></formx>
</somecomponent>
</body>

Ng-if inside ng-template does not work

I created a component and I am trying to make the component's template dynamic, that is, for some condition the parent tag should be a div, otherwise it should be an anchor tag.
I have been trying to use ng-if but somehow it wont work. Here is a code snippet. For some reason, even if the ng-if is true, the nested div (.testDiv .testThumbnail) will be undefined and this will break my component.
I cannot understand why it doesn't find the component even if the ng-if is true. I am new to Angular JS, so maybe I am missing something here? Or there is a better way to dynamically create the component's parent tags according to some condition.
function myCardController($window) {
var element = angular.element(document.querySelector('.testDiv .testThumbnail'));//is undefined
}
angular.module('myApp').component('myCard', {
templateUrl: 'testTemplate', ,
controller: ["$window", myCardController],
});
<script type="text/ng-template" id="testTemplate">
<div ng-if="true"
class="testDiv">
<div role="img" class="testThumbnail"></div>
</div>
<a ng-if="false" class="tesstDiv">same content</a>
</script>
You probably miss to add ng-app or ng-controller directive. Use following HTML as your template:
<div ng-controller = "myCardController">
<div ng-if="show"
class="testDiv">
<div role="img" class="testThumbnail"></div>
</div>
<a ng-if="!show" class="tesstDiv">same content</a>
</div>
Update your JS code as like:
function myCardController($window) {
var element = angular.element(document.querySelector('.testDiv .testThumbnail'));//is undefined
$scope.show = true;
}
angular.module('myApp').component('myCard', {
templateUrl: 'testTemplate.html', ,
controller: ["$window", myCardController],
});
You also can use ng-app="myApp" in your body tag.

Angularjs compile a directive inside ng-repeat with isolated scope

I have a directive in the form of a dropdown, pretty simple. The user can click a button to add as many as they need to in a ul, make their selections, and save it off. This is all inside of several ng-repeats.
I'm having trouble mastering the scope. As I expected, this works:
<div ng-repeat="group in groups" question-group="group" class="question-group">
<div ng-repeat="question in questions">
<ul>
<li ng-repeat="case in question.cases"></li>
<li><new-case group='group'></new-case></li>
</ul>
</div>
</div>
When I say "works", I mean that group is properly scoped (the data of the entire group is necessary for the resulting input).
When I switch it to "click to add":
<div ng-repeat="group in groups" question-group="group" class="question-group">
<div ng-repeat="question in questions">
<ul>
<li ng-repeat="case in question.cases"></li>
<li>add case</li>
</ul>
</div>
</div>
group is undefined in the scope. Here is my createNewCase function:
function createNewCase($event) {
var thisLi = angular.element($event.target).closest('li');
var listItem = $compile('<li><new-case group=\'group\'></new-case></li>');
var html = listItem($scope);
thisLi.before(html);
}
$scope.createNewCase = createNewCase;
And the newCase directive:
angular.module('groups.directives.newCaseDirective', [])
.directive('newCase', ['$window', function() {
return {
restrict: 'EA',
scope: { group: '=' },
templateUrl: 'groups/views/newcase.tpl.html'
};
}]);
I've been reading for days and I've tried a few other derivatives but I'm ultimately just not getting it. Help is greatly appreciated.
Thanks!
The issue is that group is created by ng-repeat and is only available in child scopes of ng-repeat.
Each repeated element is in it's own child scope. So your directive version works but your other one doesn't because the controller doesn't see those child scopes.
You would have to pass group as argument of the function if you want to access it in controller
<a href="#" ng-click="createNewCase($event, group)">

how to have unique scope in ng-repeat in angularjs

Is it possible for me to have multiple array for $scope?
i have a list of div with child scopes that is generated from a parent scope in ng-repeat. How can i have the scope variable individually unique?
I am generating a list of ng-repeat in another ng-repeat.
<div ng-repeat="" ng-init="hide=true" ng-click="hide=!hide">
<div ng-hide="hide" ng-init="childhide=true" ng-click="childhide=!childhide">
<div ng-repeat="" ng-init="childhide" ng-hide="childhide">
<div>{{ variable }}</div>
</div>
</div>
</div>
How can i have the variable unique? Coz each time when i click on either one div, all div with childhide variable will show. Anyway to make them behave individually?
Thanks.
To get a new $scope for each div, the first ting that comes to mind is to create another directive and specify which type of scope you want.
<div class="container">
<div ng-repeat="item in items"></div>
</div>
will become:
<div class="container">
<inner-directive ng-repeat="item in items"></inner-directive>
</div>
then in inner-directive:
app.directive('innerDirective', function () {
return {
restrict: 'E',
template: '<div></div>', // this replaces what you had before
scope: {}
};
});
This will create an isolate scope, which does not inherit properties from it's parent.
There are a couple of other scope options but i cant remember off the top of my head what each one does. Easy to read in the docs though.

Resources