Following scenario:
Tabs can come and go. My current idea which I am trying to figure out is to have a <router-view> for each tab. A named <router-view> to be exact. When a tab needs to be added I want to dynamically add a route with the viewPorts property set to the corresponding <router-view> name.
Depending on the currently opened tab I show or hide <router-view>s.
Will this work? Am I missing something?
Do you have other/better approaches to implement this and/or can provide links to examples?
You can do this in a much cleaner way by using a child router. You can configure the child router like this in your parent component's ts/js file:
configureRouter(config: RouterConfiguration, router: Router){
config.map([
{ route: 'tab-1', name: 'tab-1', moduleId: 'tab-1/tab-1', nav: 'true', title: 'Tab 1' },
{ route: 'tab-2', name: 'tab-1', moduleId: 'tab-2/tab-2', nav: 'true', title: 'Tab 2' },
{ route: 'tab-3', name: 'tab-3', moduleId: 'tab-3/tab-3', nav: 'true', title: 'Tab 3' }
)
}
Make every tab a custom element, in the above code I named them 'tab-*'.
You can then go ahead and create a new component for your tabbed navigation (you can always put the navigation logic in the parent component but I find this approach to be cleaner, mode modular and reusable)
tabnav.ts
import {bindable} from 'aurelia-framework';
import {Router} from 'aurelia-router';
export class TabnavCustomElement{
#bindable router: Router;
}
tabnav.html
<template>
<ul>
<li repeat.for="navItm of router.navigation">
<a href.bind="navItm.href" class="nav-link ${navItm.isActive ? 'active' : ''}">${navItm.title}</a>
</li>
</ul>
The you can go to parent.html and include it like this:
<tabnav router.bind="router"></tabnav>
<router-view></router-view>
The <router-view> will then change according to what the tab that you click on. If you want to share state between the tabs you can create a shared service which you can inject on each of the three custom elements.
Also every time you want to add a new tab you simply create a new custom element for that tab and place it in the router. It's the navigation component's responsibility then, to show it on screen
Related
I'm currently using Angular for a few weeks for a small project and I wanted to add an anchor in my app.
So normally in order to add an anchor without using any framework, you'd create a block with an ID
<div id="top"> then you'd add an anchor tag <a href="#"> with the href attribute that would be equal to the ID of the block of the page we want to redirect the to.
ex:
<div id="top">...</div>
...
When we click on the link, it scrolls up to the page* to the block we defined the ID with.
*if we add in the CSS of the html or body tag scroll-behavior: smooth;
The issue is that when I add that inside my Angular template, it redirects me to the URL with the name of the ID on the href attribute!
If I take the previous example, here's what would happen:
localhost:4200/login → (click to the link) → localhost:4200/#top
Strangely it treats it as if it was a router link attribute
So I'm wondering how we could add an anchor in Angular
So I found a solution! (thanks to #Benjamin Looi for giving me the link to a relevant post)
So actually Angular has an issue with anchor tags when we want the user to another part of the same page
The solution is to use routerOptions of type extra options in the appRoutingModule and add in some options, here's the code:
const routes: Routes = [...];
const routerOptions: ExtraOptions = {
useHash: false,
anchorScrolling: 'enabled',
onSameUrlNavigation: 'reload' //Must have if you want to be able to use the anchor more than once
};
#NgModule({
imports: [RouterModule.forRoot(routes, routerOptions)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Then in the template, we have to add the anchor of the block we want to redirect the user in the attributes fragment with the name of the ID and routerLink with the page you're in (otherwise it will redirect to "/")
ex:
<a routerLink="/login" fragment="top"></a>
Now it will successfully redirect to the top of the page!
May be this might help
HTML code :
<button (click)="scrollToElement(target)"></button>
<div #target>Your target</div>
Ts code :
scrollToElement($element): void {
console.log($element);
$element.scrollIntoView({behavior: "smooth", block: "start", inline: "nearest"});
}
got it from this
Using HTML anchor link #id in Angular 6
Updated Question for more Clarity:
Need to display some texts and links as innerHTML(data from service/DB) in the Angular HTML and when user clicks, it should go to Typescript and programmatically navigates by router.navigate
Also, How to add DomSanitizer from #ViewChild/ElementRef
Added all example in below code
Here is the updated stackblitz code
As shown in screenshot from angular.io some texts and some links
Sorry, I didn't realize you answered my comment. Angular routing is not secondary, if you don't use Angular modules you'll end up with just an HTML/CSS/Typescript application. you need at least the RouterModule for Angular to be able to use routing and hence, do what it's supposed to with the DOM.
First:
You are not importing RouterModule
solution:
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot([]) // this one
]
Second:
You can't bind Angular events through innerHTML property
fix:
Make use of #ViewChild directive to change your innerHTML property and manually bind to the click event, so change in your app.component.html from
<div id="box" [innerHTML]="shouldbedivcontent" ></div>
to
<div #box id="box"></div>
Now, in your app.component.ts, add a property to hold a reference to that "box" element so you can later make some changes to the dom with it:
#ViewChild('box') container: ElementRef;
Implement AfterViewInit, that hook is where you will be able to actually handle your container, if you try using it for example in OnInit you'd get undefined because that component's html is not in the dom yet.
export class AppComponent implements AfterViewInit {
and
ngAfterViewInit() {
this.container.nativeElement.innerHTML = this.shouldbedivcontent;
this.container.nativeElement.addEventListener('click',
() => this.goto('bar')
);
}
change shouldbedivcontent property from:
'1) this is a click
<a (click)="goto("bar")">Click</a><br>
2)this is with routerlink
<a routerLink="" (click)="goto("bar")">Click</a><br>
3)This only works with href
bar and test'
to
'1) this is a click
<a id="link_1">Click</a><br>
2)this is with routerlink
<a [routerLink]="" (click)="goto(\'bar\')">Click</a><br>
3)This only works with href
bar and test'
And even so you'd still not get the default anchor style unless you apply some styling yourself.
Third
You are not HTML sanitizing, which could be dangerous. read more here
MY SUGGESTION:
Seems like a lot to do for you and a lot to read for someone else working alongside you for something you could easily do like in the example below!
Move your html to your app.component.html:
<div id="box">
1) this is a click
<a (click)="goto('bar')">Click</a><br>
2)this is with routerlink
<a routerLink="" (click)="goto('bar')">Click</a><br>
3)This only works with href
bar and test
</div>
<p>Below is actual content</p>
You'll notice that everything works now, except the anchor without routerLink or href, because that's not a link.
EDIT:
Looking at the new stackblitz, i suggest a change of approach, binding to innerHTML is ok when working with plain text or even some simple html but not a great choice to bind events or routing logic.
Angular's Renderer2 provides with a bunch of methods to dyncamically add elements to the DOM. With that on the table, you just need a little effort to take that simple html you get from your backend and turn it into something like (paste this property in your code to test it along the rest of the code provided below):
public jsonHTML = [
{
tagName: '',
text: 'some text with click ',
attributes: {
}
},
{
tagName: 'a',
text: 'bar',
attributes: {
value: 'bar' // goto parameter
}
},
{
tagName: '',
text: ' some more text with click ',
attributes: {
}
},
{
tagName: 'a',
text: 'foo',
attributes: {
value: 'foo' // goto parameter
}
}
]
Once you have it, it's way easier to create all of those elements dynamically:
this is for the code in your Q1:
Inject Renderer2 with private r2: Renderer2
And replace the Q1 related code in AfterViewInit hook to:
const parent = this.r2.createElement('div'); // container div to our stuff
this.jsonHTML.forEach((element) => {
const attributes = Object.keys(element.attributes);
const el = element.tagName && this.r2.createElement(element.tagName);
const text = this.r2.createText(element.text);
if (!el) { // when there's no tag to create we just create text directly into the div.
this.r2.appendChild(
parent,
text
);
} else { // otherwise we create it inside <a></a>
this.r2.appendChild(
el,
text
);
this.r2.appendChild(
parent,
el
);
}
if (attributes.length > 0) {
attributes.forEach((name) => {
if (el) {
this.r2.setAttribute(el, name, element.attributes[name]); // just the value attribute for now
if (name === 'value') {
this.r2.listen(el, 'click', () => {
this.goto(element.attributes[name]); // event binding with property "value" as parameter to navigate to
})
}
} else {
throw new Error('no html tag specified as element...');
}
})
}
})
this.r2.appendChild(this.container.nativeElement, parent); // div added to the DOM
No html sanitizer needed and no need to use routerLink either just inject Router and navigate to the route you want! Make improvements to the code t make it fit your needs, it should be at least a good starting point
Good Luck!
You have a css problem.
looks like a link
<a [routerLink]="something"></a> looks like a link, because if you inspect the HTML it actually gets an href property added because of routerLink
<a (click)="goTo()"></a> does NOT look like a link, because there is no href
Chrome and Safari default user agents css will not style <a> without an href (haven't confirmed Firefox but I'm sure its likely). Same thing for frameworks like bootstrap.
Updated stackblitz with CSS moved to global, not app.css
https://stackblitz.com/edit/angular-ivy-kkgmkc?embed=1&file=src/styles.css
This will style all links as the default blue, or -webkit-link if that browser supports it. It should be in your global.css file if you want it to work through the whole app.
a {
color: rgb(0, 0, 238);
color: -webkit-link;
cursor: pointer;
text-decoration: underline;
}
this works perfectly for me :D
#Directive({
selector: "[linkify]",
})
// * Apply Angular Routing behavior, PreventDefault behavior
export class CustomLinkDirective {
#Input()
appStyle: boolean = true;
constructor(
private router: Router,
private ref: ElementRef,
#Inject(PLATFORM_ID) private platformId: Object
) {}
#HostListener("click", ["$event"])
onClick(e: any) {
e.preventDefault();
const href = e.target.getAttribute("href");
href && this.router.navigate([href]);
}
ngAfterViewInit() {
if (isPlatformBrowser(this.platformId)) {
this.ref.nativeElement.querySelectorAll("a").forEach((a: HTMLElement) => {
const href = a.getAttribute("href");
href &&
this.appStyle &&
a.classList.add("text-indigo-600", "hover:text-indigo-500");
});
}
}
}
HOW I USE IT
<p linkify
class="mt-3 text-lg text-gray-500 include-link"
[innerHtml]="apiSectionText"
></p>
result
I have a component that I use in a page and this component have tabs in it, the tabs works fine they aren't components though, they are just in the component.
This is what I'm trying to do, have a new link when clicking on a tab:
entity/someUid <-- Current behaviour
entity/someUid/messages <-- When clicking on a tab
entity/someUid/languages <-- When clicking on a tab
Page:
<app-entity
(currentTab)="getCurrentTab($event)"
></app-entity>
Page Module:
const routes: Routes = [
{
path: '',
component: EntityPage
}
];
Page Component:
// It does navigate like the example above but instead my 404 page is showing up
getCurrentTab(tab: string) {
this.router.navigate(['entity', this.entity.uid, tab]);
}
The component itself:
<ul>
<li (click)="view = 'messages'; currentTab.emit('messages')">Messages</li>
<li (click)="view = 'languages'; currentTab.emit('languages')">Languages</li>
</ul>
<div *ngIf="view == 'messages'">
Messages Content
</div>
<div *ngIf="view == 'languages'">
Languages Content
</div>
#Output() currentTab: EventEmitter<string> = new EventEmitter();
view: string
How can I make my component have routing while I still can render this component inside a page?
Try this
https://codecraft.tv/courses/angular/routing/nested-routes/
Children routes by adding one more router outlet in app entity and make the routes declared in its module
I'm using Angular Router and I'm trying to link to a sub-component in another route by id. Basically what would usually be done using the <a href="www.url.com/profile/#profile-header-id">.
I'm not sure if there's a built-in way for the Angular router to do this, but if not perhaps I can manually trigger the link at a later point when I know the element has been rendered.
The issue isn't linking to another route which of course is done with the Link from the Angular router. The issue is linking to an element which is found in the rendered HTML of the linked component.
Less Abstract Code Example:
So let's say my router in the app.module.ts file is
`const routes = [
{ path: '', component: HomeComponent},
{ path: '#section3', component: HomeSection3Component},
{ path: 'B', component: BComponent},
];`
Now in component OtherComponent, I want a Link that not only takes me to the home page route ' ', but also scrolls to the element of id #section3, thereby skipping all the irrelevant stuff.
My home component has nested components for each one of the sections of the page. Each section/component has an id.
home.component.html
`<main>
<app-section1></app-section1>
<app-section2></app-section2>
<app-section3></app-section3>
</main>`
However, all I can see is a blank page when clicking the button <button routerLink="#section3">Go to homepage section 3</button> on the B page.
The most elegant solution is just to add a fragment property to add the #section3 to the URL and then make it jump to this section with an anchor tag.
<div [routerLink]="['']" fragment="section3">
Jump to 'Section3' anchor
</div>
Use the routerLink directive combined with its fragment input property.
<a routerLink fragment="section3">Section 3</a>
With your routes, the rendered DOM is
Section 3
Make sure that you have imported RouterModule in the declaring module of the component in which you use the routerLink directive. Example:
import { CommonModule } from '#angular/common';
import { NgModule } from '#angular/core';
import { RouterModule } from '#angular/router';
#NgModule({
declarations: [
HomeComponent,
HomeSection1Component
HomeSection2Component,
HomeSection3Component,
],
imports: [
CommonModule,
RouterModule,
],
})
export class HomeModule {}
I am attempting to create a reusable angular2 component that accepts an array of URLs to html files on my server and creates a content window with tabs to switch between "chapters", effectively swapping out the html and css inside the content window. I have tried all sorts of things including iframes but those don't work, the angular 1 ng-include work-arounds that I can find on StackOverflow but they have all since been deprecated, and the closest I've got is building a component that you can #Input html and it interpolates the content but style won't apply and angular strips out any style or script tags. Here is what I have tried.
In my parent component class:
htmlInput: string = "<h1>Why Does Angular make this so hard?</h1>";
cssInput: string = "h1 { color:red; }"
Parent Component HTML:
<app-html [html]='htmlInput' [css]='cssInput'></app-html>
My HTML Component:
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'app-html',
template: '<div [innerHtml]=html></div>', //This works but no style
//template: '{{html}}', //This displays the actual markup on page
styles: ['{{css}}'] //This does nothing
//styles: ['h1 { color: red; }']//Also nothing
})
export class HtmlComponent implements OnInit {
#Input() html: string = "";
#Input() css: string = "";
ngOnInit() {
}
}
The result of this code is
Why Does Angular make this so hard?
But no red color. Maybe style is applied before the innerHtml is added to DOM? I don't know but just putting {{html}} results in displaying the actual markup with the h1 tags visible.
The reason I want to do it this way is that I have a bunch of HTML pages already created sitting in a folder on my server from before I angularized my site that all share a single style sheet. I'd like to just be able to flip through them like pages in a book without reloading the page and since there are so many and I'm likely to add more all the time, I'd really rather not create routing for every single one. (I already have routing for basic site navigation.)
Does anybody have a better suggestion for how to embed styled HTML into a page dynamically in the most recent version of Angular 2? At the time of this post we are in 2.0.0-beta.17.
OR... I already figured I may be approaching this issue from the entirely wrong angle. There must be a reason Angular is making this so difficult and deprecating all the solutions people have come up with so If anyone has a suggestion about how I could achieve the same results in a more angular friendly way I'd love to hear that too.
Thank you.
Edit:
I was able to fix my issue by creating a pipe which sanatizes the html before adding it to an iframe.
import { Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
#Pipe({ name: 'safe' })
export class SafePipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
transform(url: string) {
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
}
}
And then you can just pass your html into the iframe.
<iframe width="100%" height="1000" frameBorder="0" [src]="url | safe"></iframe>
This is useful to me since I have some old pages that use all sorts of jquery and style etc. This works as a quick fix to have them show up.
Angular2 rewrites the styles added to a component by including the dynamically added attributes like _ngcontent-yle-18 into the CSS selectors.
Angular2 uses this to emulate shadow DOM style encapsulation. These attributes are not added to dynamically added HTML (for example with innerHTML).
Workarounds
add styles to index.html because these styles are not rewritten by Angular2
set ViewEncapsulation.None because then Angular doesn't add the encapsulation emulation attributes
use /deep/ to make Angular2 ignore the encapsulation emulation attributes
See also Angular 2 - innerHTML styling
You should wrap your css into an object and use ngStyle to bind it to your component rather than the styles attribute, because styles does not support data binding.
Example:
htmlInput: string = "<h1>Why Does Angular make this so hard?</h1>";
cssInput: string = "{h1 { color:red; }}"
Parent Component HTML:
<app-html [html]='htmlInput' [css]='cssInput'></app-html>
Your HTML Component:
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'app-html',
template: '<div [innerHtml]="html" [ngStyle]="css"></div>',
styles: []
})
export class HtmlComponent implements OnInit {
#Input() html: string = "";
#Input() css: string = "";
ngOnInit() {
}
}