Angular 2 navigate to element in *ngFor using interpolation - html

I am using Angular 2 and looking to set-up a page where the contents are displayed through an *ngFor loop, and the body is displayed through a separate div. These use interpolation to display the objects relevant properties. I would like the content to link to the relevant div in the body.
To display the contents in the left hand side:
<div class = "contents">
<div *ngFor = "let disp of content">
{{disp.heading}}
</div>
</div>
To display the article on the right hand side
<div *ngFor="let disp of content>
{{disp.header}}
{{disp.body}}
<div>
This is similar to the query here Angular2 Routing with Hashtag to page anchor
However, as it is in an *ngFor loop dependent on content, abd uses interpolation, I cannot see a way to identify different divs within the loops as these are provided dynamically.

However, as it is in an *ngFor loop dependent on content
Not necessarily, You can get the current index of a *ngFor in angular using the predefined index variable
So, we can set the links to point to href=#content{{i}} which is href=#content0 for the first element, href=#content1 for the second element and so on,
<div *ngFor="let disp of content; let i=index">
{{disp.heading}}
</div>
Then we can create the content divs using the same idea,
<div *ngFor="let disp of content; let i=index" id="content{{i}}">
<h1> {{disp.header}} </h1>
<p> {{disp.body}} </p>
</div>
so the first div will have an id #content0, the second one #content1 and so on,
working example
Hope this helps

Related

Angular - DragDropModule not working inside templates

I would like to use DragDropModule for my angular application so I can move panels that are stored inside templates (for practical purposes as well as for recursive plotting of child elements).
The problem that I have is that cdkDrag can't find the correct cdkDropList to drop into if cdkDrag is hidden within a template and is not directly nested under the HTML element. Example:
<div
cdkDropList
[cdkDropListData]="expanded.activities"
(cdkDropListDropped)="dropActivity($event)">
<div *ngFor="let activity of expanded.activities">
<ng-container [ngTemplateOutlet]="orangeProgramActivity"></ng-container>
</div>
</div>
<ng-template #orangeProgramActivity>
<div cdkDrag>This is just a test</div>
</ng-template>
With this code example, the orangeProgramActivity can be dragged anywhere but doesn't drop into the correct dropList as cdkDrag keyword can't find any droplist within its own template.
In the second example, everything works correctly and the item gets dropped into correct dropList:
<div
cdkDropList
[cdkDropListData]="expanded.activities"
(cdkDropListDropped)="dropActivity($event)">
<div *ngFor="let activity of expanded.activities">
<div cdkDrag>This is just a test</div>
</div>
</div>
I would like to achieve the same functionality as in the second example, but with the use of templates because I really need them for recursion. Unfortunately, I can't reveal the whole code as my employer wouldn't be happy with that.
All I need is some static reference for my cdkDrag that is inside a template, to point onto a correct element with dropList that is outside of the template.
These are the only solutions that I found on the internet and they don't seem to work for me:
Material 7 Drag and Drop ng-template incompatibility
CdkDragDrop and ngTemplateOutlet
This is my first question on Stack Overflow and I'm new to angular, so I'm sorry for any confusion in my post, and thanks for any help in advance!
This might not work for all use cases, but you could just write it like this:
<div
cdkDropList
[cdkDropListData]="expanded.activities"
(cdkDropListDropped)="dropActivity($event)">
<div *ngFor="let activity of expanded.activities" cdkDrag>
<ng-container [ngTemplateOutlet]="orangeProgramActivity"></ng-container>
</div>
</div>
<ng-template #orangeProgramActivity>
<div>This is just a test</div>
</ng-template>
I was lucky that I only need to reorder elements inside an array in which they are and I don't need to transfer them into other containers. So for my "root" activities, I used your simple solution to wrap it all in a draggable element.
<div cdkDropList [cdkDropListData]="expanded.activities" (cdkDropListDropped)="dropActivity($event)">
<!-- For every object in array make a container with template and an icon that will serve as a handle -->
<div *ngFor="let activity of expanded.activities">
<div cdkDrag>
<div cdkDragHandle>
<!-- Handle icon from FontAwesome -->
<i class="fas fa-grip-vertical"></i>
</div>
<!-- Container with a template that we need -->
<ng-container [ngTemplateOutlet]="orangeProgramActivity" [ngTemplateOutletContext]="{act: activity}"></ng-container>
</div>
<!-- Recursive template for children -->
<ng-container [ngTemplateOutlet]="childActivitiesRecursion" [ngTemplateOutletContext]="{act: activity}"></ng-container>
</div>
And for my recursion, I created a droplist inside a template that serves only for reordering these children activities.
(I had problems with pasting template so the ng-template tag is not terminated at the end of the code)
<ng-template let-act="act" #childActivitiesRecursion>
<!-- Droplist inside a template for reordering child activites -->
<div cdkDropList [cdkDropListData]="act.childActivities" (cdkDropListDropped)="dropActivity($event)">
<!-- Iterates through all child activities -->
<div *ngFor="let childActivity of act.childActivities">
<div cdkDrag>
<div cdkDragHandle>
<i class="fas fa-grip-vertical"></i>
</div>
<!-- Different template for child activities -->
<ng-container [ngTemplateOutlet]="blueProgramActivity" [ngTemplateOutletContext]="{act: childActivity}"></ng-container>
</div>
<!-- Recursion for even deeper child activites -->
<ng-container [ngTemplateOutlet]="childActivitiesRecursion" [ngTemplateOutletContext]="{act: childActivity}"></ng-container>
</div>
</div>
I will accept Richard's answer as a solution because if you are able to solve it like this, it's definitely the most elegant solution you can do. But there would be still an issue if you needed to transfer items into different containers after using templates for recursion!

How can I generate *ngFor loop-items inside 1 single div instead of every single item having a div container around it

I am creating a text generator that will be used to create a typewriter(something like https://monkeytype.com/). Therefore I have a database with a table with thousands of words. Essentially I want to be able to loop through the table (x amount of times) and then show the words in 1 single div with text inside. Instead a div gets generated for every single word which I do not want.
<div class="d-flex flex-wrap typewriter-text-container justify-content-center">
<div *ngFor="let words of word" class="typewriter-text">
<div class="form-control-plaintext" (keyup)="startTypingSession($event)">{{words.word}}</div>
</div>
</div>
This is my HTML and some TypeScript. I am using Angular.
getWordsAmount(Amount: number): Observable<TextsGenerated[]>{
return this.http.get<TextsGenerated[]>(`${this.ROOT_URL}TextGenerator/words/${Amount}`, httpHeaders);
}
This is the GET request I make in order to get the desired amount of words from my table.
EDIT:
Since I am trying to create something like monkeytype.com, I want to make sure that all the items that get generated are inside an input tag somehow. I need to be able to register/check that the user types the random words generated correctly.
Result displayed:
As I understood, you don't want the "div" after the ngFor. Aren't you?
<div class="d-flex flex-wrap typewriter-text-container justify-content-center">
<div *ngFor="let words of word" class="typewriter-text">
<h1 class="form-control-plaintext" (keyup)="startTypingSession($event)">{{words.word}}</h1>
</div>
</div>
For this you can just replace the div with a text field, they also can implement some styles and schemas.
Hope this is helpful and sorry if not ;)

Angular - Scroll to the bottom of an element

So I have this 'output' element which fills up with messages, the overflow is set on scroll so after a certain amount of messages this element becomes scrollable. It doesn't auto scroll to the bottom though, so I tried doing this using the DOM, document.getElementById('output'), this didn't work and after some reading I learned this should be avoided in angular.
So I want to target this #output element in my output-window component with my typescript and access some scrollTo() method to scroll it to the bottom, how should I do this?
output-window.component.html:
<div id="output">
<p *ngFor="let message of messages">
{{ message }}
</p>
</div>
Project structure
home.component
+-->output-window.component
+-->div#output
Targeting this div#output is what's giving me trouble.
retrieve p tags with view children
#ViewChildren("p") ps: QueryList<any>
ngAfterViewInit() {
this.ps.last.nativeElement.scrollIntoView();
}
then access to native element and then scroll to it.
or better approach is write a directive for p element and set it for last element
Edit : It seems you need to name your p element like below
<div id="output">
<p *ngFor="let message of messages" #p>
{{ message }}
</p>
</div>
in order to make ViewChildren work properly.
I have created a stack blitz for you. Please check here

Get recursive nested template call to work in Angular 2?

I hope the title isn't misleading too much as I have no idea how else to call it, but here's the problem:
I am developing an app with Angular 2 and use the nested templates (hope that's the right name for them) in several instances.
Now the issue I have is that my app consists of several "widgets" which can contain other widgets. This can create a sort of circle in the template calls.
Example:
home.html:
<div> some html stuff </div>
<widget1 *ng-for="#widget of widgetList" [widget]="widget"></widget1>
widget1.html:
<div> some html stuff unique to widget1 </div>
<div *ng-if="widget.widgetSubList">
<div *ng-for="#widget of widget.widgetSubList">
<div [ng-switch]="widget.type">
<p *ng-switch-when="2"><widget2 [widget]="widget"></></widget2></p>
<p *ng-switch-when="3"><widget3 [widget]="widget"></></widget3></p>
</div>
</div>
</div>
widget2.html
<div> some html stuff unique to widget2 </div>
<div *ng-if="widget.widgetSubList">
<div *ng-for="#widget of widget.widgetSubList">
<div [ng-switch]="widget.type">
<p *ng-switch-when="1"><widget1 [widget]="widget"></></widget1></p>
<p *ng-switch-when="3"><widget3 [widget]="widget"></></widget3></p>
</div>
</div>
</div>
widgetSubList is a property of the widget that is filled if it has sub-widgets, the ng-if does work in this case and doesn't crash the code if there are no sub-widgets.
That's the point where the whole thing crashes since it creates the mentioned "circle" of widgets containing widgets that have been in the above part of the tree already since widget1 can call widget2 which can call widget1 again.
I can't change that structure since it's predetermined by the API I use in this case.
So now the question: Is there a way to have this work?
Since all widgets require different implementations, I can't really work around it without creating one giant html file filled with ng-ifs, which I would like to avoid.
PS: I edited the example a bit further to represent the code better.
the #Input part is present in the .ts files.
Right now, I only read out one additional level of sub-widgets from the API for test purposes.
What is a bit strange is that you don't have a loop of widgets within the widget1.html and widget2.html files. With your implement, you always remain on the same widget metadata, so it's normal that you have an infinite loop...
As a matter of fact, you need to have a recursive structure that would allow to define the structure of your component:
widgetList
widget
children
widget
children
widget
children
widget
children
widget
children
(...)
So when the current widget won't have children, the recursive loop will end.
So I would refactor your templates like this (for example for widget1.html):
<div> some html stuff unique to widget1 </div>
<div *ng-for="#subWidget of widget.children">
<div [ng-switch]="subWidget.type">
<p *ng-switch-when="2"><widget2 [widget]="subWidget"></widget2></p>
<p *ng-switch-when="3"><widget3 [widget]="subWidget"></widget3</p>
</div>
</div>
Each widget would have an input corresponding to the widget metadata:
#Component({
selector: 'widget1',
(...)
})
export class Widget1Component {
#Input()
widget: any;
}

How to add a custom directive div after nth ng-repeat item

In my code i have a bunch of items rendered by ng-repeat.
These items use the bootstrap class col-md-3
and i'd like to add a feature which allows the user to see a large detail panel by clicking on one of these items.
However i'd like to accomplish it by having only one detail panel in my DOM and moving it around.
The html is like this:
<div ng-repeat="thing in thingsCtrl.things">
<thing-widget class="col-md-3"></thing-widget>
</div>
<thing-detail ng-show="thingsCtrl.showDetail" class="col-md-12"> </thing-detail>
any chance to accomplish?
Thanks in advance.
You could try moving the thing-detail inside the ng-repeat and use $index to add it or not.
<div ng-repeat="thing in thingsCtrl.things">
<thing-widget class="col-md-3"></thing-widget>
<thing-detail ng-if="$index === nth" ng-show="thingsCtrl.showDetail" class="col-md-12"> </thing-detail>
</div>