I'm using the #ng-bootstrap/ng-bootstrap which adds Bootstrap components to Angular. You can inject components into an opened Modal, so the injected component makes up the body/content of the opened modal.
I have a list of Player. When I click a player, I want to open a Modal with the clicked player's data inside of it. I can open a Modal with the injected PlayerComponent like this.
constructor(
private modalService: NgbModal
) { }
openPlayerModal() {
const modalRef = this.modalService.open(PlayerComponent)
}
The question is... How do I inject additional data to the component to let it know what player's data to fetch? E.g. I might have an OnInit interface on the PlayerComponent that fetches data from an API based on an ID supplied by the modalService.open() call.
It seems like #ng-angular/ng-angular allow you to touch the component instance after it has been instantiated. So you can then modify the values.
So solution would be:
constructor(
private modalService: NgbModal
) { }
openPlayerModal() {
const modalRef = this.modalService.open(PlayerComponent)
modalRef.componentInstance.player_id = 1;
}
And then make the component use an #Input() decorator on player_id.
export class PlayerComponent {
#Input() player_id;
...
}
Related
I'm using Angular 11, Angular material and Bootstrap for a project, my problem is I want to show a pop up with and ad the first time a user loads the home page, the modal dialog is made in angular material, I have the modal dialog in ads component and then call it in the home component on the ngOnInit so the dialog will show when the user loads the home page, I found some solutions using JS but didn't make them work for me, any help on how can I solve this?
My ads component html, I'm only showing the image, no button to close the modal but if I need to add a button for a solution, I can add the button.
<mat-dialog-content id="myModal" class="gradient-border">
<img style="max-width: 100%" src="../../../assets/img/modal-ad.jpg" />
</mat-dialog-content>
ads component ts
import { Component, OnInit } from '#angular/core';
import { MatDialogRef } from '#angular/material/dialog';
#Component({
selector: 'app-anuncios',
templateUrl: './anuncios.component.html',
styleUrls: ['./anuncios.component.css'],
})
export class AnunciosComponent implements OnInit {
constructor(public dialogRef: MatDialogRef<AnunciosComponent>) {}
ngOnInit(): void {}
}
Home component ts
import { Component, OnInit } from '#angular/core';
import { MatDialog } from '#angular/material/dialog';
import { AnunciosComponent } from '../anuncios/anuncios.component';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css'],
})
export class HomeComponent implements OnInit {
constructor(public dialog: MatDialog) {}
ngOnInit(): void {
this.showDialog();
}
showDialog() {
const dialogRef = this.dialog.open(AnunciosComponent, {
maxWidth: '100vw',
maxHeight: '150vw',
panelClass: ['animate__animated', 'animate__bounceInDown'],
});
}
}
So this code makes the modal dialog always show when the home page is load, I need to show it the first time the user load the home page, I saw some solutions using cookies or JS functions but didn't make them work for me, as I said, I'm new to this and I think I didn't use those solutions properly for my project, any suggestions will be appreciated.
If in order to achieve what you mean you need to "remember" if the user has already seen the dialog.
Now I have another question for you:
Does the user need to see this every time the page loads?
Once and never again?
Every time he initiates a session on your site? (opens the browser to browse your site)
If your use-case is like option 1, then you might just have an internal variable. You can either use a static variable with a boolean or use a singleton Service from Angular.
#Component({...})
export class MyComponent {
public static hasAdvertBeenShown = false
ngOnInit() {
if(!MyComponent.hasAdvertBeenShown) {
this.matRef.showDialog()
MyComponent.hasAdvertBeenShown = true
}
}
}
If your use-case is to show the advertisement the first time the user browses your site then your variable would be stored inside localStorage and it would survive opening and closing the browser.
#Component({...})
export class MyComponent {
public static hasAdvertBeenShown = MyComponent.hasAdvertBeenShownBefore()
ngOnInit() {
if(!MyComponent.hasAdvertBeenShown) {
this.matRef.showDialog()
MyComponent.markAsSeen()
}
}
public static boolean hasAdvertBeenShownBefore() {
return JSON.parse(localStorage.getItem('advert'))
}
public static boolean markAsSeen() {
localStorage.setItem('advert', true)
}
}
If your use-case is the latter then use sessionStorage is similar to localStorage but it's shorter lived (per session)
#Component({...})
export class MyComponent {
public static hasAdvertBeenShown = MyComponent.hasAdvertBeenShownBefore()
ngOnInit() {
if(!MyComponent.hasAdvertBeenShown) {
this.matRef.showDialog()
MyComponent.markAsSeen()
}
}
public static boolean hasAdvertBeenShownBefore() {
return JSON.parse(sessionStorage.getItem('advert'))
}
public static boolean markAsSeen() {
sessionStorage.setItem('advert', true)
}
}
If you'd like to know more about local and session storages you can take a read over here
Here's what I usually do in my apps
Whenever I need to store something into localStorage, let that be preferences (user settings, dark-mode, etc.) and I want that to survive browser restarts I need to use localStorage.
Since using raw localStorage can get messy quite easily what I do is just create a singleton "UserSettingsService" that wraps the "low-level" local storage logic so I can share it across the codebase:
#Inject({providedIn: 'root'})
export class SettingsService {
private storage: Storage = localStorage // Change this based on your use-case
public markAdvertisementAsShown() {
this.storage.setItem('advert', true)
}
public boolean hasAdvertisementBeenShown() {
const safeBoolean = this.storage.getItem('advert') ?? 'false' // defaulting to false in case it didnt exist
return JSON.parse(safeBoolean)
}
}
And then on my other classes:
#Component({...})
export class SomeComponent {
hasAdvertBeenShown = this.adverts.hasAdvertisementBeenShown()
constructor(private matRef: Mat..., private adverts: AdvertService){}
ngOnInit() {
if(!this.hasAdvertBeenShown) {
// do something like showing the advert
this.adverts.markAdverisementAsShown()
}
}
}
It might seem a little overkill for a boolean but apps tend to get more and more complex. Like you'd like later you want to show 3 diferent advertisements and you need to be sure which one you've shown. Now you wouldn't be serializing the boolean but rather the advertisement object that has been shown. Logic gets more complex but because it's in a service you would only change it there and boom it's working again throughout your app!
I want to load datas only if the user see the specific component which load the datas. So don't load before the component is visible.
I have this template:
<button (click)="showMyCompontente()">Click me</button>
<app-other-component *ngIf="show"></app-other-component>
With this TypeScript code:
export class MyComponent implements OnInit {
show = false;
ngOnInit() {
}
showMyCompontente() {
this.show = !this.show;
}
}
And the point is here:
#Component({
selector: 'app-other-component',
})
export class OtherComponent implements OnInit {
ngOnInit() {
this.load();
}
load() {
// needs to load datas only if the user see the component
}
}
How to achive in the OtherComponent to start the this.load() only if the component is visible to the user? I want to reload datas again if the user hide the component and show it agian.
I need a solution inside the component to detect itself is became visible or disappear. That's because I have many compontents, calling eachothers in many variations.
Which Angular lifecycle hooks fires only when the user shows the component?
I would try:
Adding an input property with type of boolean to OtherComponent component.
Passing value of show to the input property, then inside the OtherComponent component, use ngOnChanges to detect any change in the input property and call load() accordingly.
I have a footer component with a "Save" button that is always visible in my Angular 6 app - it is a core component that never destroys unless the app is destroyed. Each page of the app is a different section in saving a Product, so for example a "General Info" section, "Pricing" section, "Quantity" section, and so forth. When I progress through my app, if I click the Save button, at any time, it should save the current state of the Product object.
I have a Product object that looks like this:
export interface Product {
id: number;
name: string;
price: number;
qty: number;
}
My "General Info" feature page looks like this:
constructor(private store: Store<fromProduct.State>) {}
ngOnInit() {
this.store.dispatch(new LoadAction());
this.product$ = this.store.pipe(select(fromProducts.getProduct));
}
This loads the product just fine, I see all of the values from the product$ observable in my view. However, I don't know how to "pass" the loaded product object to the footer component to save the state.
My footer component looks like this:
// Markup
<button (click)="saveProduct($event)">Save</button>
// Component
import * as productActions from '../../product/state/actions';
...
saveProduct(product: Product) {
this.store.dispatch(new productActions.SaveProductAction(product));
}
I know $event is linked to nothing - that is what my question is. How can I get the Product object from the "General Info" component into the footer component via ngrx?
Thanks!
I think that your footer should not hold the save logic, since it's just a unique trigger for multiple actions.
I'd advise you to propagate the Save button click event to where your forms are.
If the form is a direct parent or sibling, then you could do it simply with #Input() and #Output(), if it's not then you can use a service to share an Observable between your forms and your button as follows:
#Injectable()
export class EventService{
private subject= new Subject<any>();
saveButtonClicked = this.subject.asObservable();
constructor() { }
saveButtonClick() {
this.subject.next();
}
}
Footer template:
<button (click)="onSaveClick()">Save</button>
Footer TypeScript:
onSaveClick() {
this.eventService.saveButtonClick();
}
Your different forms:
this.eventService.saveButtonClicked.subscribe(res => {
// Your save logic
});
<side-nav [navTitle]="navTitle"></side-nav>
<router-outlet>
</router-outlet>
I have navigation bar at the root component. I created [navTitle] with #Input Decorator inside the side-nav component. side-nav component is placed in another component(root-component). However I want access [navTitle] and change from component which loaded inside the router-outlet acording to which component is loaded. How do I achieve that?
You can't pass any data to router-outlet as to regular component (at the current version of Angular it's not possible, may be it will be added in the future), so the following syntax is invalid:
<router-outlet [dataToPass]="'something'"></router-outlet>
In provided case, you can use services to share data between your components, and I think, that using observable is the best way, because you will get the updated version of data realtime:
data.service.ts
// Other service stuff
#Injectable()
export class DataService {
private navTitle$: BehaviorSubject<string> = new BehaviorSubject<string>('Default nav title');
public setNavTitle(newNavTitle: string): void {
// Sets new value, every entity, which is subscribed to changes (`getNavTitle().subscribe(...)`) will get new value every time it changes
this.navTitle$.next(newNavTitle);
}
public getNavTitle(): Observable<string> {
// Allow to `subscribe` on changes and get the value every time it changes
return this.navTitle$.asObservable();
}
}
side-nav.component.ts
// Other component stuff
export class SideNavComponent implements OnInit, OnDestroy {
public navTitle: string = '';
private getNavTitleSubscription: Subscription;
constructor(private _dataService: DataService) { }
ngOnInit() {
// Will update the value of `this.navTitle` every time, when you will call `setNavTitle('data')` in data service
this.getNavTitleSubscription = this._dataService.getNavTitle()
.subscribe((navTitle: string) => this.navTitle = navTitle);
}
ngOnDestroy() {
// You have to `unsubscribe()` from subscription on destroy to avoid some kind of errors
this.getNavTitleSubscription.unsubscribe();
}
}
And any component, which is loaded in that router-outlet:
any.component.ts
// Other component stuff
export class SideNavComponent implements OnInit {
private navTitleToSet: string = 'Any title';
constructor(private _dataService: DataService) { }
ngOnInit() {
// Set title from current component
this._dataService.setNavTitle(this.navTitleToSet);
}
}
In such case you don't really need to pass the value from root component to side-nav, because you already have a subscription in side-nav component and you will have access to the latest value. If you need navTitle in both root and side-nav components, you can just move the logic with subscription to root.
And here is the working STACKBLITZ.
You can use a service to communicate between components. I have created a short example which would give you a glimpse of how it can be done.
The service being a singleton, has only one instance and hence the properties remain the same.
Hope it helps.
https://stackblitz.com/edit/angular-paziug?file=src%2Fapp%2Fapp.component.ts
I have a small web site I am developing in Angular4 (my first attempt in Angular) and have come across an issue I just can seem to figure out. Simplified down my scenario is:
I have a component (account-list) which using a html5 select/option control which is populated from a rest api and displays a list of accounts.
I have a second component which displays the details of the account (account-detail) and take accountId as an input parameter.
When an account is selected in the account-list component I want the account-detail to auto update to the newly selected account. I know that my account-list component is working fine and that when I select an account the selectAccountId variable in the ts is being updated.
However I just can't seem to get the update to the selectAccountId variable to trigger an update to the account-detail component. I know this component works fine on it own as a default account is displayed and the id of this default is set in the ngOnInit method of the account-list component.
The relevant html5 in the account-list component:
<select id="Id" #Id="ngModel" class="hideLabel form-control" [(ngModel)]="selectedAccountId" name="Id">
<option [ngValue]="account.Id" *ngFor="let account of accounts">
{{account.Name}}
</option>
</select>
<!-- this div just to prove that selectedAccountId changes when a new account is selected, which it does -->
<div *ngIf="selectedAccountId">
{{selectedAccountId}}
</div>
<!-- this is the line which does not seem to work, changes to selectedAccountId are not triggering the component to refresh -->
<account-detail *ngIf="selectedAccountId" [account]="selectedAccountId"></account-detail>
The ts code of the account-list component:
export class AccountListComponent implements OnInit {
selectedAccountId: number;
title: string;
accounts: AccountHeader[];
errorMessage: string;
constructor(private accountService: AccountService, private router: Router) { }
ngOnInit() {
var s = this.accountService.getLatest();
s.subscribe(
accounts => {
this.accounts = accounts; this.selectedAccountId = this.accounts[0].Id;
},
error => this.errorMessage = <any>error
);
}
}
The ts code of the account-detail component:
export class AccountDetailComponent {
#Input("account") selectedAccountId: number;
account: Account;
selectedLicense: License;
constructor(
private authService: AuthService,
private accountService: AccountService,
private router: Router,
private activatedRoute: ActivatedRoute) {
}
ngOnInit() {
if (this.selectedAccountId) {
this.accountService.get(this.selectedAccountId).subscribe(
account => this.account = account
);
}
else {
this.router.navigate([""]);
}
}
}
In all honesty I've lost track of the things I've tried to make this work, most of the blogs, guides etc I've read talk about how to bind the other way and I have all that working just fine. But I can't find how to get the binding to trigger an update of the account-detail component which should be accomplished by this line:
<!-- this is the line which does not seem to work, changes to selectedAccountId are not triggering the component to refresh -->
<account-detail *ngIf="selectedAccountId" [account]="selectedAccountId"></account-detail>
I've been following a book in which this was working fine, but originally this was working in AngularJS and then migrated to Angular2 (then 4) and somewhere along the way this has stopped working.
Any help would be much appreciated, thanks!
So in your account-detail component you are using the ngOnInit method to fetch some data depending on your selected account id. ngOnInit is a lifecycle hook that is called once when a component is created. When you change the selected id, angular is not recreating the component and the method does not fire.
What you need is a method that fire when you change the variable. There are several approaches you can use, check out this comprehensive post for more information.
Here is a simple example using a property setter to trigger a method call when the input is changed:
export class AccountDetailComponent {
selectedAccount: Account;
private _selectedAccountId: number;
#Input("account") set selectedAccountId(value: number) {
this._selectedAccountId = value;
this.getAccount();
}
getAccount() {
if (this._selectedAccountId) {
this.accountService.get(this._selectedAccountId).subscribe(
account => this.selectedAccount = account
);
}
}
}