I have an Array of Elements of the type ActionInstruction.
Some get added in the constructor of the component, others are added dynamically by the user via a dialog form field.
actionInstructions: ActionInstruction[];
constructor(private _formBuilder: FormBuilder, public dialog: MatDialog) {
this.actionInstructions = [{post_date_start: new Date()}];
}
someMethod(){
// get result of Type ActionInstruction from Dialog -> newInstruction
this.actionInstructions.push(newInstruction);
}
The content of the array gets displayed in the view like follows:
<div *ngFor="let actionInstruction of actionInstructions">
<div *ngIf="actionInstruction.post_date_start" (click)="openActionCreation(actionInstruction)" class="action-instruction">
<div class=" fl ml-50">
Am {{actionInstruction.post_date_start.toLocaleDateString()}}
</div>
</div>
</div>
At start the elements that are added in the constructor, are getting with the localized date without any problem. But after adding an element dynamically via the form, the following error gets thrown:
At first I thought it could be caused by calling it during runtime in the view, but the problem actually occures in another totally independent component, where I prepare the variable to be shown in the view, in the constructor with the same function.
constructor(){
if(this.user.birthday != null){
this.localizedBirthday = this.user.birthday.toLocaleDateString();
}
}
Here it seems allmost patternless when the error occures and when not.
The only thing that both components have in common, that they use some *ngIf pattern in the view component, where the date get´s displayed.
Not a direct solution but you could use the datepipe in your components.
https://angular.io/api/common/DatePipe
it would look something like this:
<div class=" fl ml-50">
Am {{actionInstruction.post_date_start | date:'medium' }}
</div>
Related
So basically I have a modal component with an input field that tells it which modal should be opened (coz I didn't want to make a component for each modal):
#Input() type!:string
ngOnChanges(changes: SimpleChanges): void {
this.type = changes["type"].currentValue;
this.openModal();
}
that field is binded to one in the app component:
modalType = "auth";
HTML:
<app-modal [type] = modalType></app-modal>
In the beginning it's got the type "auth" (to login or register), but when I click on an icon I want to open a different modal, I do it like so:
<h1 id="options-route"
(click) ="modalType = 'settings'"
>⚙</h1>
but this only works the first time, when modalType already has the value "settings" the event doesn't trigger even though the value has technically changed
I think the problem is that it's the same value because i tried putting a button that does the exact same thing but with the value "auth" again and with that it was clear that the settings button only worked when tha last modal opened was auth and viceversa
any ideas? I want to be able to open the settings modal more than once consecutively possibly keeping onChange because ngDoCheck gets called a whole lot of times and it slows down the app
You need to include the changeDetectorRef, in order to continue in this way.
More about it https://angular.io/api/core/ChangeDetectorRef
Although, a better and a faster alternative is the use of a behavior Subject.
All you have to do is create a service that makes use of a behavior subject to cycle through each and every value exposed and then retrieve that value in as many components as you want. To do that just check for data changes in the ngOnInit of target component.
You may modify this for implementation,
private headerData = new BehaviorSubject(new HeaderData());
headerDataCurrent = this.headerData.asObservable();
changeHeaderData(headerDataNext : HeaderData) {
this.headerData.next(headerDataNext)
console.log("subscription - changeUserData - "+headerDataNext);
}
Explanation:
HeaderData is a class that includes the various values that can be shared with respective data types.
changeHeaderData({obj: value}), is used to update the subject with multiple values.
headerDataCurrent, an observable has to be subscribed to in the target component and data can be retrieved easily.
I mean i'm too l-a-z-y to use your slightly-not-so-much-tbh complicated answers so I just did this:
I added a counter that tops to 9 then gets resetted to 0 and I add it to the value
screwYouOnChangesImTheMasterAndYouShallDoMyBidding = 0;
//gets called onClick
openSettings(){
if(this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding === 9){
this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding = 0;
}
this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding = this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding + 1;
this.modalType = "settings"+this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding;
}
then in the child component I just cut that last character out:
ngOnChanges(changes: SimpleChanges): void {
let change = changes["type"].currentValue as string;
change = change.substring(0, change.length - 1);
this.type = change;
this.openModal();
}
works like a charm 😂
I have an Album component which has a series of images displayed. When the user clicks on an image, a method is called which emits a BehaviorSubject (using next) and routes to another component:
Album component html:
<ul class="container">
<li *ngFor="let album of albums">
<img class="album-cover" [src]="album.albumCoverPhotoUrl" alt="album.name" (click)="onAlbumSelect(album)">
<p class="album-name">{{ album.name }}</p>
</li>
</ul>
Album component.ts:
onAlbumSelect(album: Album) {
this.router.navigate(['gallery-list']);
this.albumsService.fromAlbum.next(album);
setTimeout(() => {
this.albumsService.fromAlbum.next(null);
}, 100);
}
In my gallery list component (the component I navigate to above), I subscribe to the BehaviorSubject and filter an array of images using the album name that I get from the emitted data:
ngOnInit() {
this.albumSub = this.albumsService.fromAlbum.subscribe(album => {
this.selectedAlbum = album;
if(this.selectedAlbum) {
this.galleryList = this.images.filter(e => e.album === this.selectedAlbum.name);
}
}
I am using a behavior subject as previously I used a subject instead but this didn't work as I believe the subject was being emitted before the subscription was initialized on the gallery-list component (therefore it was missing the data emitted from the subject).
As you can see from the onAlbumSelect method from within the Album component, I am using a setTimeout method to emit a null value to reset the behviorSubject value so that it doesn't affect other parts of my code (the gallery-list component is also used to display a full list of images rather than just images from 1 album, I have left this part of the code out to as I didn't feel it was necessary to include it).
This feels somewhat 'hacky' and I am just wondering if anyone can think of a better way to approach this?
I would assume that emitting a subject, routing to a component and subscribing to the subject from within the component you are routing to is something that is done frequently in Angular apps so is there a way of doing this using just a subject (rather than a behaviorSubject) but ensuring the subscription is setup before the value is emitted? Sorry for the long post!
I'm trying to allow for hiding of certain sections of the project I'm working on via user toggle. It saves in the database and gets pulled when the page is loaded in the constructor using the following code
this.http.get(`api/section/get/${this.id}`, this.id).subscribe(res => {
this.section = res.json()[0];
this.sect = res.json();
console.log(this.section);
this.hideIntro = this.sect[0].hideIntro;
this.hideMainVideo = this.sect[0].hideMainVideo;
this.hideHandout = this.sect[0].hideHandout;
this.hideQuiz = this.sect[0].hideQuiz;
console.log("Hide Intro = " + this.hideIntro);
console.log("Hide Main = " + this.hideMainVideo);
console.log("Hide Handout = " + this.hideHandout);
console.log("Hide Quiz = " + this.hideQuiz);
});
The HTML is as follows...
<div class="row classMainBackground col-md-12" *ngIf="!hideIntro">
...content...
</div>
For some reason, no matter what I do, whether I change it to *ngIf="hideIntro == false" or even use [hidden]="hideIntro", it is not working.
Even the console logs in the .ts file show up correctly. Is there a reason why this is not working for me? I've used it in other positions and it works fine there...
Does it have something to do with assigning it in the constructor or something?
Thanks in advance!
Angular change detection runs in response to use interaction with the component. If values are updated outside of that event handling (such as after an HTTP request), you need to manually tell the component that it has changed.
constructor(private changeDetector: ChangeDetectorRef){}
this.http.get(`api/section/get/${this.id}`, this.id).subscribe(res => {
[...]
this.changeDetector.markForCheck();
})
More in depth reading: https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html
I ended up solving the problem by using {{!section.hideIntro}} in the HTML instead of trying to define a new variable to pass that boolean to.
I believe the answer was a combination of what #Vlad274 and #ConnorsFan were mentioning.
the HTML was returning an [object object] for {{hideIntro}} and it seems like there's a delay between the assigning the new value data from the GET response before the DOM actually loads.
Grabbing the data right from the GET respone variable ended up doing the trick.
My function isn't called when I click the <a... tag.
I have the following code in my component:
public htmlstr: string;
public idUser:number;
this.idUser = 1;
this.htmlstr = `<a (click)="delete(idUser)">${idUser}</a>`;
public delete(idUser){
alert("id " + idUser);
}
My html
<div [innerHTML]="htmlstr"></div>
but the function delete isn't called and does not show the alert.
The <div... is created dynamically
If anyone face same issue and above all answer not working then try my trick :
In HTML :
<button onclick="Window.myComponent.test()"> test </button>
In component :
class
constructor(){
Window["myComponent"] = this;
}
test(){
console.log("testing");
}
Your main issue here, on-top of the things pointed out by #Matt Clyde and #Marciej21592, is that you're trying to dynamically add HTML code that needs to be compiled before it can be used (you're trying to bind to a method and variable).
Some ways of doing this can be seen here.
From the code you have supplied, however, there are much easier ways to accomplish what you are after. For starters, I would have that code in the HTML to begin with and hide/show it as needed with ngIf.
i use this method and its work
public htmlstr: string;
public idUser:number;
this.idUser = 1;
this.htmlstr = `<a id='innerHtmlClick'>${idUser}</a>`
this.htmlstr.querySelector(`innerHtmlClick`).addEventListener('click', () => {
this.delete(idUser);
});
public delete(idUser){
alert("id " + idUser);
}
EventListener listen the event bye using id of innerHtml
I assume that it is not a bug but rather Angular's security measure against XSS attacks - for more information I would suggest taking a look here https://angular.io/guide/security#sanitization-example
I somewhat also fail to understand why you insist on passing the event via string literal instead of just simply using:
<div>
<a (click)="delete(idUser)">${this.idUser}</a>
</div>
Your component has inner Html.
Angular will not allow events inside inner Html portions for security reasons. You can use Child components. to make events from inside of inner Html portions. Create a child component and put your html inside the child component and pass the data by using any angular events between parent and child using Input, Output features in Angular
I don't often use [innerHTML], but it looks like the template string you're using <a (click)="delete(idUser)">${idUser}</a> is referencing ${idUser} when you might have meant ${this.idUser}?
Below code snippet worked for me:-
In component :
ngAfterViewChecked () {
if (this.elementRef.nativeElement.querySelector('ID or Class of the Html element')) {
this.elementRef.nativeElement.querySelector('ID or Class of the Html element').addEventListener('click', this.editToken.bind(this));
}
}
inside constructor parameter:-
constructor( private readonly elementRef: ElementRef) {}
import { ElementRef } from '#angular/core';---> at the top of the file
implement 'AfterViewChecked'
I need to know if there is a way to create HTML local variables programmatically.
I am developing a web app where I have an NgFor loop and I want to be able to assign a local variable to each sub element created by the NgFor.
ie :
<div *ngFor="#elt of eltList" >
<span #setLocalVariable(elt.title)></span>
</div>
setLocalVariable(_title : string){
let var = do some stuff to _title;
return var;
}
The exemple above shows you what I am trying to accomplish and obviously does not work.
Is there a way to achieve this ?
Thank you in advance.
Edit:
After seeing the answers I got (and i thank everyone who took the time to read my question and tried to answer it) i'll explain a bit more why i want it that way.
I will be using : loadIntoLocation() from the DynamicComponentLoader.
That function got as a 3rd parameter a string that refers to an anchors (ie : #test in an html element). Thats why i need to create those local variables with a name equal to the one of my elt.title.
I think local variables (defined with the # character) don't apply for your use case.
In fact, when you define a local variable on an HTML element it corresponds to the component if any. When there is no component on the element, the variable refers to the element itself.
Specifying a value for a local variable allows you to select a specific directive associated with the current element. For example:
<input #name="ngForm" ngControl="name" [(ngModel)]="company.name"/>
will set the instance of the ngForm directive associated with the current in the name variable.
So local variables don't target what you want, i.e. setting a value created for the current element of a loop.
If you try to do something like that:
<div *ngFor="#elt of eltList" >
<span #localVariable="elt.title"></span>
{{localVariable}}
</div>
You will have this following error:
Error: Template parse errors:
There is no directive with "exportAs" set to "elt.title" ("
<div *ngFor="#elt of eltList" >
<span [ERROR ->]#localVariable="elt.title"></span>
{{localVariable}}
</div>
"): AppComponent#2:10
Angular2 actually looks for a directive matching the provided name elt.title here)... See this plunkr to reproduce the error: https://plnkr.co/edit/qcMGr9FS7yQD8LbX18uY?p=preview
See this link: http://victorsavkin.com/post/119943127151/angular-2-template-syntax, section "Local variables" for more details.
In addition to the current element of the iteration, ngForm only provides a set of exported values that can be aliased to local variables: index, last, even and odd.
See this link: https://angular.io/docs/ts/latest/api/common/NgFor-directive.html
What you could do is to create a sub component to display elements in the loop. It will accept the current element as parameter and create your "local variable" as attribute of the component. You will be able then to use this attribute in the template of the component so it will be created once per element in the loop. Here is a sample:
#Component({
selector: 'elt',
template: `
<div>{{attr}}</div>
`
})
export class ElementComponent {
#Input() element;
constructor() {
// Your old "localVariable"
this.attr = createAttribute(element.title);
}
createAttribute(_title:string) {
// Do some processing
return somethingFromTitle;
}
}
and the way to use it:
<div *ngFor="#elt of eltList" >
<elt [element]="elt"></elt>
</div>
Edit
After your comment, I think that you try the approach described in this answer. Here are more details: create dynamic anchorName/Components with ComponentResolver and ngFor in Angular2.
Hope it helps you,
Thierry
You could stick it into the template interpolation since it handles expressions.
<div *ngFor="#elt of eltList" >
<span>{{setLocalVariable(#elt)}}</span>
</div>
setLocalVariable(_title : string){
let var = do some stuff to _title;
return var;
}