I'm trying to use a template tag inside an Angular component.
Following the MDN example I created a similar stackblitz example.
In the original example, the template tag has no children but it's content has children. On my example it's the other way around. The template has the children and content is empty (open the console and click the button to see it).
As a result, trying to manipulate the cloned template content fails.
What am I doing wrong?
app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
click() {
const template: HTMLTemplateElement = document.querySelector('#t');
console.log(`template.childElementCount: ${template.childElementCount}`);
console.log(`template.content.childElementCount: ${template.content.childElementCount}`);
const content = document.importNode(template.content, true);
console.log(content.querySelector('.c2'));
}
}
app.component.html
<button (click)="click()">test</button>
<template id="t">
<div class="c1">
<div class="c2">{{ name }}</div>
</div>
</template>
Console result
You should only call template and not template.contentas first argument.
const content = document.importNode(template, true);
console.log(content.querySelector('.c2'));
Turns out there is an open bug about this: https://github.com/angular/angular/issues/15557
As a workaround I created the template with createElement.
const name = "answer";
const template = document.createElement('template');
template.innerHTML = `<div class="c1"><div class="c2">${name}</div></div>`;
console.log(`template.childElementCount: ${template.childElementCount}`);
console.log(`template.content.childElementCount: ${template.content.childElementCount}`);
const content = document.importNode(template.content, true);
console.log(content.querySelector('.c2'));
Related
I want to switch between two classes (light and dark) at TAG Body.
What I did? I created a service:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class ThemeService {
body = document.body;
constructor() { }
changeLight() {
this.body.classList.replace('light', 'dark');
}
changeDark() {
this.body.classList.replace('dark', 'light');
}
}
It is working as expected but I know that this code does not use best practices.
What is the correct way to change between these two classes?
Edit: Added a service to the stackblitz, but again, there are many ways to do this. This is just a starting point.
While the "right way" is subjective, you have some options to make it "Angular-y"
Component:
import { Component, Inject } from '#angular/core';
import { DOCUMENT } from '#angular/common';
// Create a type that accepts either the string 'light' or 'dark' only
type Theme = 'light' | 'dark';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
// Default to 'light' theme
currentTheme: Theme = 'light';
// Inject document which is safe when used with server-side rendering
constructor(#Inject(DOCUMENT) private document: Document) {
// Add the current (light) theme as a default
this.document.body.classList.add(this.currentTheme);
}
// Swap them out, and keep track of the new theme
switchTheme(newTheme: Theme): void {
this.document.body.classList.replace(this.currentTheme, newTheme)
this.currentTheme = newTheme;
}
}
HTML:
<p>
Current theme: {{ currentTheme }}
<button (click)="switchTheme('light')">Light mode</button>
<button (click)="switchTheme('dark')">Dark mode</button>
</p>
Many ways to do this, but one benefit of defining the types is if you provide a bad value, such as:
<p>
Current theme: {{ currentTheme }}
<button (click)="switchTheme('light')">Light mode</button>
<button (click)="switchTheme('dark')">Dark mode</button>
<button (click)="switchTheme('noop')">Invalid</button>
</p>
You'll get an error:
Argument of type '"noop"' is not assignable to parameter of type 'Theme'.
StackBlitz
I am using #HostBinding('class') to inject classes into the host element. The classes to be injected are generated based on user-supplied parameters. The problem I ran into and I could not find anyone else experiencing is that the classes are applied in an order different from the way I expected them.
For example, having a component defined below:
import {Component, HostBinding, Input} from '#angular/core';
#Component({
selector: '[icon]',
template: `
<ng-content></ng-content>
`
})
export class SuiIconComponent {
#Input() iconType = '';
#HostBinding('class')
get classes(): string {
return [this.iconType, 'icon'].join((' '));
}
}
When I apply the component like shown below:
<div icon iconType="car"></div>
And inspect, I see <div class="icon car"></div> instead of the appropriately formatted <div class="car icon"></div>.
I have tried reversing the array before joining but that did not help either.
Is there any way I get the classes to get rendered in the proper order?
Edit: I realized the classes are being rearranged in alphabetic order.
I'm not sure why angular changes the order, but you can solve your problem with little bit of change in your template.
#Component({
selector: 'icon',
template: `
<div [ngClass]="iconType + ' icon'">
<ng-content></ng-content>
</div>
`
})
export class SuiIconComponent {
#Input() iconType = '';
}
and use it as follows
<icon iconType="car">
Some content here
</icon>
app.component.ts
<div>
<app-head></app-head>
<app-body></app-body>
</div>
head.component.ts
...
#Component({
selector: 'app-head',
templateUrl: './head.component.html',
styleUrls: ['./head.component.scss'],
providers: []
})
...
body.component.ts
...
#Component({
selector: 'app-body',
templateUrl: './body.component.html',
styleUrls: ['./body.component.scss'],
providers: []
})
...
So the pages loads with content head + body but now I wanted to route to a different page and replace entire existing page with the new page. How do I do that?
In my app.module.ts I have the following...
const routes: Routes = [
{ path: 'newPage', component: NewComponent}
]
I wanted use when clicked a button to be redirected to this page and replace existing <app-head> and <app-body> is this possible?
If I just use below I still see the current <app-head> and <app-body>
<button type="button" (click)="loadNewPage()" >
body.component.ts
loadNewPage() {
this.router.navigate(['/newPage']);
}
The results give me the current page.... and doesnt really apply since I am not concating the contents together. I want to replace the head.html and body.html with newpage.html from the NewComponent.ts
You need to replace the content in AppComponent with a router-outlet component and move that replaced content to a new component such as HomeComponent. Use the HomeComponent in your default route so it will load when you initially visit the site.
It's probably best if you check the documentation for Routing & Navigation since this is a pretty fundamental topic in Angular and there are a lot of details you should learn before you get too far.
App.component.html
<router-outlet></router-outlet>
home.component.html
<div>
<app-head></app-head>
<app-body></app-body>
</div>
app-routing.module.ts
const routes: Routes = [
{ path: '', component: HomeComponent }
{ path: 'newPage', component: NewComponent}
]
You will want to put a <router-outlet></router-outlet> in your app component and move what's in your current app component to a new component. Then update your routes to:
const routes: Routes = [
{ path: '', component: TheStuffYouMovedComponent },
{ path: 'newPage', component: NewComponent }
]
Using Angular5 - I know how it is possible to bind an HTML element's style to a Boolean value, but I can't find an explanation to do this for multiple styles at the same time.
ie. I have found something like this works fine:
[style.background]="r.favourite === true ? '#3f51b5' : 'white'"
However I am also wanting to change the color of my text to white at this point also... And I don't want to clutter my components with lots of [style.xxx] tags.
Is there a way I can dynamically bind to a CSS class to apply when r.favourite === true?
I have seen ways in which this can be done... However this assumes you are binding within the same file as such:
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
template: `
<button class="my-btn" [class.extraclass]="someProperty">Call to Action</button>
`,
styles: [`
.my-btn { font-size:1.7em; }
.extraclass { background: black; color: white; }
`]
})
export class AppComponent {
someProperty = true;
}
However my CSS is being stored in a shared file - such that I have a file structure like:
import { Component } from '#angular/core';
#Component({
selector: 'my-component',
styleUrls: ['./css/shared-styles.css']
template: `
<button class="my-btn" [class.extraclass]="someProperty">Call to Action</button>
`
})
export class MyComponent {
someProperty = true;
}
You can use NgClass.
<div [ngClass]="{'text-success':r.favourite ,'text-danger':!r.favourite}">
Where 'text-success' and 'text-danger' are classes you define.
Please refer to this great article about NgClass and NgStyle:
https://codecraft.tv/courses/angular/built-in-directives/ngstyle-and-ngclass/
Hope this helps
I'm new to Typescript and Angular Material. I want to hide elements inside elements like this.
<div id="abc">
<div id="123">
<p>Hello!</p>
</div>
<p>World</p>
</div>
<div id="def">
<p>Hello World</p>
</div>
I want to hide div block(id:123).I tried in this way.
var formElement = <HTMLFormElement>document.getElementById('123');
formElement.style.display='block';
It gets an error saying Cannot read property 'style' of null.... How can I solve this problem.
This is not the way to hide elements in Angular. Bind your element's style attribute to a boolean, like this:
<form [style.display]="isVisible ? 'block' : 'none'">... contents</form>
And in your component class:
this.isVisible = false; // whenever you need to hide an element
Or you can use *ngIf:
<form *ngIf="isVisible">... contents</form>
Please, note that *ngIf removes the node and its children completely from the DOM tree completely if the conditions turns to false, and fully recreates them from scratch when the condition turns true.
You can access the dom element using ViewChild with #localvariable as shown here,
import {Component, NgModule,ViewChild,ElementRef} from '#angular/core'
import {BrowserModule} from '#angular/platform-browser'
#Component({
selector: 'my-app',
template: `
<div id="abc">
<div #id id="123">
<p>Hide!</p>
</div>
<p>World</p>
</div>
<div id="def">
<p>Hello World</p>
</div>
`,
})
export class App {
name:string;
#ViewChild('id') el:ElementRef;
constructor() {
this.name = `Angular! v${VERSION.full}`
}
ngAfterViewInit()
{
this.el.nativeElement.style.display='none';
console.log(this.el.nativeElement);
}
}
#NgModule({
imports: [ BrowserModule ],
declarations: [ App ],
bootstrap: [ App ]
})
export class AppModule {}
#Component({
selector: 'my-app',
template: `
<div #id>
<p>Hide This!</p>
</div>
<div>
<p>Hello World</p>
</div>
`,
})
export class AppComponent {
#ViewChild('id') id:ElementRef;
constructor() {}
ngOnInit()
{
this.id.nativeElement.hidden=true;
}
}
The simplest way to hide elements using DOM is
document.getElementById('123').hidden = true;
in typescript.