Angular 2+ how to bind to child components dynamically - javascript

I haven't found a solution for this yet even looking at SO questions and answers.
I'm trying to build an Angular 2+ (I'm using updated Angular 4.x in particular) "container" component, accepting "child" components, and "2-way" binding data/events to them.
Basically I'm trying to do something like this HTML template, but I don't know (coming from Angular 1.x) the Angular 2+ way of writing the js/ts code of the component:
<!-- example "pseudo-html-template" use of "MyContainerComponent" and "MyChildComponent"s -->
<my-container-component>
<my-child-component name="Child 1">
Hello example 1 <code>HTML</code>!
</my-child-component>
<my-child-component name="Child 2">
Hello example 2 <strong>HTML</strong>!
</my-child-component>
<my-child-component name="Child 3">
Hello example 3 <i>HTML</i>!
</my-child-component>
</my-container-component>
<!-- example "pseudo-html-template" of "MyContainerComponent" -->
<ul>
<li *ngFor="/* [for any child in children] */">
<!-- [the child <my-child-component> with "2-way" data/event binding i.e. <my-child-component [selected]="children[$index].selected" [name]="child.name"> ...] -->
</li>
</ul>
For now I had to put everything inside the containter component and write the bindings with explicit indexes like <-- [...] --><my-child-component [selected]="children[3].selected" name="Child 3"><-- [...] -->
I basically need two things: access to the "ViewChildren" components inside the container component, show them with their inner HTML (and possibly retain their state/functionality as well?), and a way to dynamically add bindings to them on load from the container component code. If possible I'd like to achieve this with a similar template HTML code (ie. in my-app.component.html), instead of specifying the children html in the js/ts code of components
I have no clue where and how to look for solutions and documentation.

Related

ng component not useful

I was learning the ng-content. Why need to use it when we can easily write like this
with ng-content
menu.component.html
<div>
<app-message> <h1>Laptop</h1></app-message>
</div>
message.component.html
<div>
<ng-content></ng-content>
<p>something text to be display</p>
<button > Submit</button>
</div>
without ng-content
menu.component.html
<div>
<h1>Laptop</h1>
<app-message> </app-message>
</div>
message.component.html
<div>
<p>something text to be display</p>
<button > Submit</button>
</div>
Using <ng-content> allows you to create flexible reusable components.
For example, a custom app-modal component:
<div class="modal">
<div class="modal-title">
{{title}}
</div>
<div class="modal-body">
<ng-content></ng-content>
</div>
</div>
This is very powerful, and only the start of what you can achieve with <ng-content>
A core prinpiple of software engineering is code reuse (DRY). With this we can bundle all of the logic relating to a component into one component, and inject the content into it.
This is like asking the question
Why not just declare your CSS styles inline instead of in CSS classes?
It is possible, but unmaintainable, and we have evolved beyond that.
Your example is fairly trivial, but it is still useful if you wanted to change style or behaviour based on some injected logic.
There exist some situations when is necessary, maybe no in simple cases but when the app grows up and you have to insert a custom component inside another is very usefully
As you can see from your own example, without using ng-content, the parent component suddenly is partially responsible for the child's layout. It'll get messy real quick, the quicker the more complex the layout gets.
It's useful for displaying components inside of other components, so the components can be reusable. For example, if you want to display some text on a few pages, you call the component with the text inside of the other components that you want the text to be displayed on. It could also be useful for showing different information but styled in the same format. E.g.
if your ng-content already has the information this is an example of how it would be used:
menu.component.html
<ng-content></ng-content>
message.component.html
<ng-content></ng-content>
This is good because you can copy and paste the exact component into another one without having to rewrite the code
If your ng-content is looking for a data source for information to pass into it
<ng-content [data]='data'></ng-content>
This is good because you can recreate the component inside of another component but with different data inside of it.
If you've ever used react, you pass data into it in a similar way here as you would with react props, but instead of props in angular, it will be an #input field. Here is some example code
test.component.html
<ng-content [data]='THIS IS THE DATA'></ng-content>
This is the actual component, as you can see, it is looking for a data source
ng-content.component.html
<p>The data we are looking for is {{data}} </p>
ng-content.component.ts - this says that when the component is called, it is looking for an input called 'data' and the type has to be a string
#Input() data: string;
We would then see the test.component.html displayed like this:
The data we are looking for is THIS IS THE DATA

Adding single Angular-4 component multiple times inside HTML page

We are developing an application in Adobe AEM using angular 4.
Due to our tool limitation we are not able to add a component inside another component by using ng-selector (parent-child structure).
So currently we are creating the parent Component in HTML and including the Angular components inside by using AEM(tool) feature.
In one of the scenario we need to add a single component multiple times in the HTML page but then we are not able to target the each component to show/hide.
For example.
Template in Component1 :
<div *ngif="dynamicvalue">
<div>show</div>
</div>
My HTMl will look like below.
<div *ngif="dynamicvalue"> <!-- Component1 added for the 1st time-->
<div>show</div>
</div>
<div *ngif="dynamicvalue"> <!-- Component1 added for the 2nd time-->
<div>show</div>
</div>
<div *ngif="dynamicvalue"> <!-- Component1 added for the 3rd time-->
<div>show</div>
</div>
In the above scenario :
1.How can I target only the Component1 added at the 1st position to show/hide?
2.How can I target Component1 added at 1st & 2nd position to show/hide?

When is transclusion appropriate?

Case in point, I am developing a multi-select control in Angular2, similar in functionality to Select2 or a multitude of other controls.
I started by defining what I want the user interface to look like in terms of defining what's included in the dropdown, and came up with two options.
One is to use #Input()s for the options:
<my-multi-select [options]="options"></my-multi-select>
...and then within the template for my-multi-select:
<div class=option" *ngFor="let option of options">{{option.display}}</div>
Ahother is to use transclusion, which is how material2 appears to do it:
<my-multi-select>
<my-option *ngFor="let option of options" [value]="option"></my-option>
</my-multi-select>
...and then within the template for my-multi-select:
<div class=select-container">
<ng-content select="my-option"></ng-content>
</div>
I was content with the transclusion option, but then when I started to actually implement it rand in to difficulty binding the events coming from my-option to my-multi-select. I could try to figure out a way to notify my-select of things that are happening in my-option, like using an Observable, or digging deeper in to using an #Output event -- but that feels like trying to jam a square peg in to a round hole when #Input variables might just be simpler.
This led me to the question, is transclusion even appropriate here? And the bigger question, when is transclusion appropriate, and when is using transclusion jamming a square peg in to a round hole?
For your example, it's simple to compare the two aproaches as you are only including one text item as part of the transclusion. This makes using #Input() trivial and may well be the best solution.
However, imagine a scenario where you have multiple elements you want to include in your child component, each with custom HTML tags along for the ride. Using trasnclusion this is trivial, but using #Input() it requires a few "hacks" to get right and isn't very maintainable or extendable.
Explanation
Building on from this Todd Motto blog about transclusion as a reference, we can utilise transclusion to have more complex HTML for our title and content sections without issue.
Transculsion Parent
<my-component>
<my-title>
<h1>This is the Component title!</h1>
</my-title>
<my-content>
And here's some awesome content.
<ul>
<li>First</li>
<li>Second</li>
</ul>
</my-content>
</my-component>
Transclusion Child
<div class="my-component">
<div>
Title:
<ng-content select="my-title"></ng-content>
</div>
<div>
Content:
<ng-content select="my-content"></ng-content>
</div>
</div>
Now imagine this same scenario using only #Input() to declare our elements.
Our binding from the parent isn't very friendly, we definitely don't want to do this for more complex scenarios.
In our child component we have to use [innerHTML] to get around interpolation encoding in Angular. Again, this isn't very easy to extend or maintain. This is where in my opinion transclusion really excels.
Input Parent
<my-component
[my-title]="<h1>This is the Component title!</h1>"
[my-content]="And here's some awesome content.<ul><li>First</li><li>Second</li></ul>">
</my-component>
Input Child
<div class="my-component">
<div [innerhtml]="'Title:' + my-title"></div>
<div [innerhtml]="'Content:' + my-content"></div>
</div>
This led me to the question, is transclusion even appropriate here?
If want html to look like:
<my-multi-select>
<my-option *ngFor="let option of options" [value]="option"></my-option>
</my-multi-select>
Where my-multi-select and my-option are components, then transclusion is the way to do it.

Angular 1.5 nesting components

I have a container component that has 2 other controllers nested in it. I have them set up like so
container -> component1 and container -> component2
My train of thought here was that I could transclude the HTML from my page into my container component.
<container> <component1></component1> <component2></component2></container>
and then in the container HTML <div ng-init="vm.init()" ng-transclude></div>
Problem is, my container isn't running at all, I added a console log to its init function and no code is running. While component1 and component2 run their init's.
Seems to be a large amount of documentation covering older angular versions. Which tell me that they need to be nested like so <div ng-controller="parentController"> <div ng-controller="childController"></div> </div
How do you nest components into each other in 1.5?
Looks like the issue was that I was attempting to put a ng-init and ng-transclude in the same element. My guess is that ng-transclude overrides everything in the element it is on.
so I moved the ng-init to another element and its working fine
<div ng-transclude ng-init="vm.init()"></div>
changed to
<div ng-init="vm.init()"> <section ng-transclude ></section> </div>

Initiating same angularjs app in two places

I have a html template that is being used for multiple projects. In that template I have two places where I can set the content.
The layout is something similar to this:
<body>
<div>Navbar from TEMPLATE</div>
<div>
<div>other TEMPLATE stuff</div>
<div id="content1">CONTENT can be set here</div>
</div>
<div id="content2">CONTENT can also be set here</div>
</body>
My question is is it possible to use the same angular app in both #content1 and #content2? Can the ng-app="main" be used twice?
If something like this isn't possible to do I can add an ng-app attribute to the template but would rather not have to do this as, as I said, the template is used for multiple projects a lot of which won't be using angular.
Edit:
Just to clarify, I'm using a tal template from zope. In that template I have two fill slots, I would like to use the same angular app in both fill slots without having to change the original tal template if possible.

Categories