I am wanting to change the colour of my text with a service and subscription, however I cannot seem to get it to work properly?
I am using [ngClass] to dynamically set characters that are true for 'isActive', but I don't know why I cannot get this to work?
At present, only the first letter is being changed.
Also... I am hoping to change the colour of the highlighted letters changed by the [ngClass].
Stackblitz
html
<div class="container">
<div class="keys" *ngFor="let item of dataKeys">
<div #text class="chars" *ngFor="let char of data[item]" [ngClass]="{'active': char.isActive}">
{{char.name}}
</div>
</div>
</div>
component
#ViewChild('text') private text: ElementRef;
constructor(private service: SettingsService) {
}
ngOnInit() {
this.subscribeToColour();
}
subscribeToColour() {
this.service.getColour.subscribe(res => {
if (this.text) {
this.text.nativeElement.style.color = res;
}
});
}
get dataKeys() {
return Object.keys(this.data);
}
service
export class SettingsService {
default = 'green'
colour = new BehaviorSubject<any>(this.default);
setColour(colour) {
this.colour.next(colour);
}
get getColour(): Observable<any> {
return this.colour;
}
}
As Peter said the view child reference you captured to "text" is only a reference to the first of the divs that you repeated.
As an alternative, you could consider adding the color to the data object:
Eg:
{
"name": "a",
"isActive": true,
"color": "red",
}
And binding to this in your html file - you shouldn't really be manipulating the nativeElement property yourself
You would then update the property in your data object in your service subscriber as appropriate
The #text is only pointing at the div for a. Therefore, this.text.nativeElement.style.color = res; only updates the color for a.
I modified your code a little bit:
my-comp.component.html
<div class="container">
<div class="keys" *ngFor="let item of dataKeys">
<div class="chars" *ngFor="let char of data[item]" [style.color]="char.isActive ? color : 'white'">
{{char.name}}
</div>
</div>
</div>
my-comp.component.ts
color: string = 'white';
subscribeToColour() {
this.service.getColour.subscribe(res => {
this.color = res;
});
}
Note that I got rid of #text in your html file as well as the conditional block in your ts file.
You don't seem to be setting the .isActive variable of any character in your code. That #text is not working how you want it to.
I think you may be overcomplicating this. You can simply change colors by setting them in your service without subscribing to a value.
Try something like:
<div class="container">
<div class="keys" *ngFor="let item of dataKeys">
<div class="chars" *ngFor="let char of data[item]" [ngClass]="{'active': getColour(char.isActive)}">
{{char.name}}
</div>
</div>
</div>
export class SettingsService {
getColour(boolean): string {
if (boolean == true) {
return 'green'; // whatever colour you want.
} else {
return 'red' // whatever you want
}
}
Related
I am new to angular and I have a p tag with a list of paragraphs and data(nested array of objects) for p tag will be coming from the backend. I need to truncate the text after some character limit and show....show more. when a user clicks on the p tag it should show reveal the rest of the text. I have figure out the way to truncate the text and display ...show more.clicking should reveal that specific paragraph text but in my case, all other paragraphs texts which are truncated are also showing full texts and since the data is nested array of objects it is tricky for me and i am not able to figure out the solution. I am providing the stackblitz link below. any help will be appreciated.
stackblitz link
data = [
{
comments:[
{text:'this is comment',id:'1'},
{text:'this is comment',id:'2'}
]
},
{
comments:[
{text:'this is comment',id:'3'},
{text:'this is comment',id:'4'}
]
},
{
comments:[
{text:'this is comment',id:'5'},
{text:'this is comment',id:'6'}
]
}
]
showrest:boolean = false
<div *ngFor="let c of data">
<div *ngFor="let comment of c['comments']">
<p (click)="showrest=true">{{showrest?comment.text:(comment.text | slice:0:10)+'...Click to Read More'}}</p>
</div>
</div>
Here is how you could do, see this repro on stackblitz. You need a context for each comment so just create a component for it. Here is the code :
ts:
import { Component, Input } from "#angular/core";
#Component({
selector: "app-comment",
templateUrl: "./comment.component.html",
styleUrls: ["./comment.component.css"]
})
export class CommentComponent {
#Input() comment;
showrest = false;
}
html:
<p (click)="showrest=!showrest">{{showrest?comment.text:(comment.text | slice:0:10)+'...Click to Read More'}}</p>
and you call it as follow :
app.html
<div *ngFor="let c of data">
<div *ngFor="let comment of c['comments']">
<app-comment [comment]="comment"></app-comment>
</div>
</div>
Your problem was that each of your comment were bound to the same variable (Of the same component), so when yu click on a paragraph it update your variable and therefore all the binded element (here your paragraphs).
Another solution could be to add a property to your comment model, something like :
{text:'this is comment',id:'1', isFullyVisible: false},
This way your could handle the display of your comment with this variable. The best solution depends on what you intend to do with your comment. If it's just a display and nothing else then just adding a property to your model is good enough.
I am a beginner with angular material. I have written this code so far:
Answer {
id: string;
content: string;
}
<section *ngFor="let opt of step.answers">
<mat-checkbox [checked]="opt.id" (change)="check($event)">
<p [innerHTML]="opt.content"> </p>
</mat-checkbox>
</section>
I still can't figure out how to retrieve the id for every option selected in every stepper and collect them in array
so this is the solution I've found, it's close to #Çağrı proposition but I changed few things since "event" has an attribute checked that I can use without needing to use a variable to save the value of "isChecked"
check(event, opt, userResponse) {
var rep = [];
if (event.checked === true) {
userResponse.push(opt);
}
if (event.checked === false) {
var index: number = userResponse.indexOf(opt);
userResponse.splice(index, 1);
}
}
<section *ngFor="let opt of step.answers" >
<mat-checkbox (change)="check($event,opt,step.userResponse)">
<p [innerHTML]="opt.content"> </p>
</mat-checkbox>
</section>
in [checked] condition u can't put id. It is true false condition. U should add one attribute more of step.answers model which calls ischecked whose initial value is false. and create global array called choosenanswers ,and write
Answer {
id: string;
content: string;
ischecked:boolean=false;
}
then
<section *ngFor="let opt of step.answers">
<mat-checkbox [checked]="opt.ischecked" (change)="check($event)">
<p [innerHTML]="opt.content"> </p>
</mat-checkbox>
</section>
then in check event in components
check(event){
this.choosenanswers=[];
this.step.answers.foreach(x=>{if(x.ischecked){ this.choosenanswers.push(x.id);}})
}
In angular 6 I want to access *ngFor last value as I want to operation if last value is set
eg
<li [ngClass]="list.mydata==1?'replies a':'sent a'" *ngFor="let list of chatlist; let last=last;">
<span [last]="last"></span>
<img src="{{list.profile_img}}" alt="" />
<div *ngIf="list.sender_type==0">
<p>{{list.message}}{{last}}</p>
</div>
<div *ngIf="list.sender_type==1">
<p style="background-color: burlywood;">{{list.message}}</p>
</div>
</li>
I want to do is [(myvar)]=last in place of let last=last
I want to bind the last variable so, I can access it is set or not in its component.
you can create a custom directive:
import { Directive, Output, EventEmitter, Input } from '#angular/core';
#Directive({
selector: '[onCreate]'
})
export class OnCreate {
#Output() onCreate: EventEmitter<any> = new EventEmitter<any>();
constructor() {}
ngOnInit() {
this.onCreate.emit('dummy');
}
}
and then you can use it in your *ngFor to call the method in your component:
<li [ngClass]="list.mydata==1?'replies a':'sent a'" *ngFor="let list of chatlist; let last=last;">
<span (onCreate)="onCreate(last)"></span>
<img src="{{list.profile_img}}" alt="" />
<div *ngIf="list.sender_type==0">
<p>{{list.message}}{{last}}</p>
</div>
<div *ngIf="list.sender_type==1">
<p style="background-color: burlywood;">{{list.message}}</p>
</div>
</li>
then in your component:
myvar: boolean = false;
onCreate(last) {
this.myvar = last;
}
checkout this DEMO.
Angular provides certain local variables when using *ngFor, one is for example last, which will (not as you expect currently) be a boolean value, being true if it is the last item. This is meant for adding specific stylings for the example to the last element of a list.
If you want that boolean you already use it correctly, but obviously the element using it should be a component. So instead of
<span [last]="last"></span>
it should be something like
<my-component [last]="last"></my-component>
where in my-component you define
#Input last: boolean;
and thus have access to it.
for example
<li [ngClass]="list.mydata==1?'replies a':'sent a'" *ngFor="let list of chatlist; let index=i;">
</li>
now you excess last elemnt like
<anyTag *ngIf="i === chatlist.length-1"></anyTag>
I'm making a pop up in angular with primeng. The content of the pop up depends on two flags controlled by radio buttons. If one is true, a particular HTML has to be rendered and if other is true, other part has to be rendered. But if one is true, other part in not getting cleared form the pop up. My code is something like this:
In .ts file:
part1: boolean = false;
part2: boolean = false;
makeP1True() {
this.part1=true;
this.part2=false;
}
makeP2True() {
this.part2=true;
this.part1=false;
}
in HTML file:
<p-radioButton name="groupname" value="Part1" (onClick)="makeP1True()"></p-radioButton>
<p-radioButton name="groupname" value="Part2" (onClick)="makeP2True()"></p-radioButton>
<div *ngIf="part1">
Show Part 1
</div>
<div *ngIf="part2">
Show Part 2
</div>
You don't have part2 property as in you example
part1: boolean = false;
part1: boolean = false; // just name it to part2
template
<p-radioButton name="groupname" [value]="Part1" label="Part1" (onClick)="makeP1True()"></p-radioButton>
<p-radioButton name="groupname" [value]="Part2" label="Part2" (onClick)="makeP2True()"></p-radioButton>
<div *ngIf="part1">
Show Part 1
</div>
<div *ngIf="part2">
Show Part 2
</div>
component
export class AppComponent {
part1: boolean = false;
part2: boolean = false;
makeP1True() {
this.part1 = true;
this.part2 = false;
}
makeP2True() {
this.part2 = true;
this.part1 = false;
}
}
stackblitz example
As a general note you can solve it with single propert and relay on
p-radioButton to toggle the value
component
export class AppComponent {
part1: boolean ;
}
template
<p-radioButton name="groupname" label="part1" [value]="true" [(ngModel)]="part1"></p-radioButton>
<p-radioButton name="groupname" label="part2" [value]="false" [(ngModel)]="part1"></p-radioButton>
<div *ngIf="part1 === true">
Show Part 1
</div>
<div *ngIf="part1 === false">
Show Part 2
</div>
stackblitz example with single variable
By doing *ngIf="part1" you are actually just checking whether 'part1' is truthy, which means whether it exists or not. However, to see which item is selected, you would need to set a state variable such as selected.
I would suggest you simplify this by using a single function and ngSwitch. Here is a tutorial: https://www.tektutorialshub.com/angular-2-ngswitch-directive/
With the [ngSwitch] directive you can specify an expression to be evaluated. With *ngSwitchCase you can specify the matcher. The syntax would be the closest to your code.
It would look like this:
HTML
<p-radioButton name="groupname"
value="Part1"
(onClick)="setPart('part1')">
</p-radioButton>
<p-radioButton name="groupname"
value="Part2"
(onClick)="setPart('part2')">
</p-radioButton>
<ng-container [ngSwitch]="selected">
<div *ngSwitchCase="'part1'">
Show Part 1
</div>
<div *ngSwitchCase="'part2'">
Show Part 2
</div>
</ng-container>
TS
public selected: string;
public setPart(id: string): void {
this.selected = id;
}
In this case both divs do something like this: *ngIf = "selected === 'partX'.
BTW: <ng-container> lets you use directives such as *ngIf,*ngSwitch and so on without creating a new DOM elements. Great for building conditionals.
You can read up on all of this in the official docs
As the code provided bellow. I tried to select a dynamic element generated by ngIf but failed.
I used two ways in total.
ElementRef and querySelector
component template:
`<div class="test" *ngIf="expr">
<a id="button">Value 1</a>
</div>
<div class="test" *ngIf="!expr">
<a id="button">Value 2</a>
</div>`
component class:
expr: boolean;
constructor(
private elementRef: ElementRef,
) {
}
ngOnInit(): void{
//Call Ajax and set the value of this.expr based on the callback;
//if expr == true, then show text Value 1;
//if expr == false, then show text Value 2;
}
ngAfterViewInit(): void{
console.log(this.elementRef.nativeElement.querySelector('#button'));
}
The output result is null.
#ViewChild
component template:
`<div class="test" *ngIf="expr">
<a #button>Value 1</a>
</div>
<div class="test" *ngIf="!expr">
<a #button>Value 2</a>
</div>`
component class:
#ViewChild('button') button: elementRef;
expr: boolean;
ngOnInit(): void{
//Call Ajax and set the value of this.expr based on the callback;
//if expr == true, then show text Value 1;
//if expr == false, then show text Value 2;
}
ngAfterViewInit(): void{
console.log(this.button);
}
The out put result is undefined;
Is there a way to get dynamic dom generated by *ngIf?
Finally the problem has been solved through #ViewChildren.
And to log the updated result, it is necessary to use a separate function.
For example:
Wrong Code:
#ViewChildren('button') buttons: ElementRef;
function(): void{
this.expr = true; // Change expression then the DOM will be changed;
console.log(this.buttons.toArray()); // This is wrong because you will still get the old result;
}
Right Code:
#ViewChildren('button') buttons: ElementRef;
function(): void{
this.expr = true; // Change expression then the DOM will be changed;
}
ngAfterViewInit(): void{
this.buttons.changes.subscribe( e => console.log(this.buttons.toArray()) ); // This is right and you will get the latest result;
}
You can't get the element when the *ngIf="expr" expression is false because then the element doesn't exist.
The value is not yet set in ngOnInit(), only when ngAfterViewInit() is called.
Plunker example
#Component({
selector: 'my-app',
template: `
<div class="test" *ngIf="prop">
<a #button id="button1">button1</a>
</div>
<div class="test" *ngIf="!boolean">
<a id="button2">button2</a>
</div>`
,
})
export class App {
#ViewChild('button') button: ElementRef;
prop:boolean = true;
ngAfterViewInit() {
console.log(this.button);
}
}
Plunker example with ViewChildren
#Component({
selector: 'my-app',
template: `
<button (click)="prop = !prop">toggle</button>
<div class="test" *ngIf="prop">
<a #button id="button1">button1</a>
</div>
<div class="test" *ngIf="!boolean">
<a #button id="button2">button2</a>
</div>`
,
})
export class App {
#ViewChildren('button') button: QueryList<ElementRef>;
prop:boolean = true;
ngAfterViewInit() {
console.log(this.button.toArray());
this.button.changes.subscribe(val => {
console.log(this.button.toArray());
});
}
}
Is not exactly the best but I deal with a similar situation using [ngClass] instead *ngIf.
Just create a class with "display: none" and hide the elements when needed. The elements selected with #ViewChild can be accessed without problem.
For now you can use this little "hack" while search for a better solution or more elegant one.
ex.
.hide-element{
display: none;
}
<div class="test" [ngClass]="expr === 'yourTest' ? 'hide-element' : null">
<a #b id="button1" (click)="onButton1(b)">button1</a>
</div>
if you are handling some async return you can just use async pipe
<div class="test" [ngClass]="(expr | async) === 'yourTest' ? 'hide-element' : null">
<a #b id="button1" (click)="onButton1(b)">button1</a>
</div>
Hope it helps.
If you need the element only in the moment that someone interacts with it (e.g. clicks on it) or with a sibling or child element of it you can pass it's reference with the event.
Template:
<div class="test" *ngIf="expr">
<a #b id="button1" (click)="onButton1(b)">button1</a>
</div>
Code:
onButton1(button: HTMLButtonElement) {
}
If you need the element without interaction with it you might also look at the ViewChildren query instead of ViewChild.
You can set it up with
#ViewChildren('button') buttons: QueryList<ElementRef>;
You can then subscribe to changes of elements that match a selector through this.buttons.changes.subscribe(...). If the element get's created or deleted you will get notified through the subscription. However my first way involves far less boilerplate code.
Alternativly you can also only access the QueryList synchronously in moments where you are sure that the element exists (through some preconditions). In your example you should be able to retrieve the button with
let button: ElementRef = this.buttons.first;
in these cases.
<div class="test" *ngIf="boolean">
<a #button id="button1">button1</a>
</div>
#ViewChild('button') button: elementRef;
console.log(this.button);
the ID = 'button1', not 'button'?
var target = document.getElementById('Button1');