Right now, I have dead simple use case of the inheritance, but for the life of me, I cannot get it to work.
The use case
My use case is thus:
I have BaseComponent that has some LoaderComponent. It has methods showLoader() and hideLoader() for showing it and hiding it, respectively.
Every loadable component, or even every component, extends this BaseComponent. Then, when there is network request, or we need to wait for a lot of stuff to render, we can just showLoader(), and then hideLoader() when the work is done.
Implementation
My implementation of this is pretty straightforward, but for some obscure reason, it's not working.
base.component.ts
//...
export class BaseComponent implements AfterViewInit {
#ViewChild(LoaderComponent) loader: LoaderComponent;
constructor() {}
ngAfterViewInit() {
// caveman debugging, I know. Even worse is that I found myself placing breakpoint on this line of caveman debugging. SAD!
console.log(this.loader);
}
showLoader() {
this.loader.show();
}
hideLoader() {
this.loader.hide();
}
}
base.component.html
I use transclusion here, of course.
<ng-content> </ng-content>
<app-loader #loader></app-loader>
contact.component.ts
//...
#AutoUnsubscribe
export class ContactComponent extends BaseComponent implements OnInit {
constructor(
private senderService: SenderService,
private messageSentService: MessageSentService,
private router: Router
) {
super();
// setup logic here...
}
ngOnInit() {}
sendEmail() // or whatever...
{
this.showLoader();
// send the email
// do the request
this.emailSender = this.senderService.send(this.emailMessage);
this.emailSender.subscribe((res) => {
this.hideLoader();
// handling the request here....
}
//...
}
contact.component.html
<app-base>
<!-- the contact page -->
</app-base>
When I fire this up, what I see, in the developer console, is :
.
When I place my breakpoint on the caveman debugging in BaseComponent.prototype.ngAfterViewInit(), it hit twice. On the first instance, I get this.constructor.name === "BaseComponent". However, on the second one, is the derived class : this.constructor.name === "ContactComponent".
The state of the decorated view child field loader isn't being passed down!!
How do I fix this, and without resorting to some bullshit design like making the derived class has-a base class?
Problem
From What I understand ur question is you are not only using just component inheritance but template composition as well
contact.component.html
<app-base>
<!-- the contact page -->
</app-base>
Explanation
Thing is You can not use Parent Template while inherit from it. a component has only one template.
in contact.component.html <app-base> means an instance of base-component as part of ur template.
so ur child component template is composed of base not inherit from it .
bcs Child Component template has not loader it it so its null.
Either use compose or inheritance
Simple Solution
while using component inheritance you have to copy all ur base template to child template
contact.component.html
<app-loader></app-loader>
Advance Solution
We can achieve almost what u want with composite pattern
start with just an interface
export interface ILoader{
showLoading ():void
hideLoading ():void
}
create a app-loader wrapper that implement ILoader
#Component({
selector: 'app-loader-wrapper',
template: `<ng-content> </ng-content>
<app-loader></app-loader>`,
styleUrls: ['./loader-wrapper.component.css']
})
export class LoaderWrapperComponent implements ILoader {
#ViewChild(Loader) loader: Loader
showLoading (){
this.loader.showLoading()
}
hideLoading (){
this.loader.hideLoading()
}
}
Create a baseComponent Class it will be base of all ur component which need loading note it has no template
class BaseComponent implements ILoader{
#ViewChild(LoaderWrapperComponent) loader: LoaderWrapperComponent
showLoading (){
this.loader.showLoading();
}
hideLoading (){
this.loader.hideLoading();
}
}
now create components and inherit from it as use composition in template
#Component({
selector: 'my-app',
template: `<app-loader-wrapper>
// rest of ur component
</app-loader-wrapper>`,
styleUrls: [ './app.component.css' ]
})
export class AppComponent extends BaseComponent {
}
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!
A component colled with passing value with Input():
<app-available-modal [good]="'test'"></app-available-modal>
The component looks like:
#Component({
selector: 'app-available-modal',
templateUrl: './available-modal.component.html',
styleUrls: ['./available-modal.component.scss']
})
export class AvailableModalComponent implements OnInit {
#Input() good: TyreGoodModelTemp;
constructor() {
console.log(this.good);
}
ngOnInit(): void {
console.log(this.good);
}
}
I expect "test" and "test" output in the console.
And the console.log from ngOnInit() prints "test". But console.log from constructor() prints "undefined".
Why does it happen and how do I handle it?
In Angular the constructor function of a component class is being used for service injection only. Everything that is related to rendering and #Input resolving has to be handled in Angular's lifecycle hooks like ngOnInit and ngOnChanges
Don't try to access #Input()s in the constructor, do it in the ngOnInit life-cycle hook. The constructor has almost nothing to do with the Angular application life-cycle.
For more information : #8015757
I'm passing a function as parameter from parent to child component. When click event is occurred, function of parent component trigger, but all the property of parent component is undefined. For example,
Parent Component
export class AppComponent implements OnInit {
constructor( private notificationService: NotificationService ) {}
unreadNotification(): Observable<any> {
// here this.notificationService is undefined
console.log( this.notificationService );
}
}
Parent html
<notification-menu [unread]= "unreadNotification"></notification-menu>
child Component
export class NotificationMenuComponent implements OnInit {
#Input() updateUnread: Function;
}
child html
<button type="button" class="icon-button" (click)="updateUnread()">
</button>
Now when I click on notification button, unreadNotification is triggered, but value of this.notificationService in console.log is undefined.
How can I solve this?
You should use #Input() to pass values from parent to child and #Output() to pass values from child to parent.
Child HTML:
<button type="button" class="icon-button" (click)="update()">
</button>
Child Component:
export class NotificationMenuComponent implements OnInit {
#Output() updateUnread = new EventEmitter<string>();
update() {
this.updateUnread.emit("I am working man!");
}
}
Parent HTML:
<notification-menu (updateUnread)= "unreadNotification($event)"></notification-menu>
Parent Component:
export class AppComponent implements OnInit {
constructor( private notificationService: NotificationService ) {}
unreadNotification(dataFromChild: string) {
console.log(dataFromChild);
}
}
The answer from #nimeresam is good advice - using an #Output is an idomatic way to achieve this.
It's worth noting though, that the reason that your original solution doesn't work is due to the way that javascript handles the this context.
Writing (click)="updateUnread()" is equivalent to saying this.updateUnread() with this being NotificationMenuComponent - as notificationService does not exist on NotificationMenuComponent you get the undefined error.
To have the context of the parent component used, you would need to bind the context to the updateUnread function before passing it into the child component.
This can be achieved either by converting the function to be an arrow functionn, or using Function.bind
See:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
It's normally a good idea to enable the typescript option for --noImplicitThis to help catch these errors (though unsure if it will detect it in this case)
You can use arrow function so that you can use parent component's information. You can try as like as given below.
updateUnreadNotification = () => {
// by using arrow function you can get notificationService information
console.log( this.notificationService );
}
Hope your problem will be solve by this.
<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
Question
How to create child components inside a parent component and display them in the view afterwards using Angular2? How to make sure the injectables are injected correctly into the child components?
Example
import {Component, View, bootstrap} from 'angular2/angular2';
import {ChildComponent} from './ChildComponent';
#Component({
selector: 'parent'
})
#View({
template: `
<div>
<h1>the children:</h1>
<!-- ??? three child views shall be inserted here ??? -->
</div>`,
directives: [ChildComponent]
})
class ParentComponent {
children: ChildComponent[];
constructor() {
// when creating the children, their constructors
// shall still be called with the injectables.
// E.g. constructor(childName:string, additionalInjectable:SomeInjectable)
children.push(new ChildComponent("Child A"));
children.push(new ChildComponent("Child B"));
children.push(new ChildComponent("Child C"));
// How to create the components correctly?
}
}
bootstrap(ParentComponent);
Edit
I found the DynamicComponentLoader in the API docs preview. But I get the following error when following the example: There is no dynamic component directive at element 0
This is generally not the approach I would take. Instead I would rely on databinding against an array that will render out more child components as objects are added to the backing array. Essentially child components wrapped in an ng-for
I have an example here that is similar in that it renders a dynamic list of children. Not 100% the same, but seems like the concept is still the same:
http://www.syntaxsuccess.com/viewarticle/recursive-treeview-in-angular-2.0
Warning: DynamicComponentLoader has been deprecated in RC.4
In Angular 2.0, loadIntoLocation method of DynamicComponentLoader serve this purpose of creating parent-child relationship. By using this approach you can dynamically create relationship between two components.
Here is the sample code in which paper is my parent and bulletin is my child component.
paper.component.ts
import {Component,DynamicComponentLoader,ElementRef,Inject,OnInit} from 'angular2/core';
import { BulletinComponent } from './bulletin.component';
#Component({
selector: 'paper',
templateUrl: 'app/views/paper.html'
}
})
export class PaperComponent {
constructor(private dynamicComponentLoader:DynamicComponentLoader, private elementRef: ElementRef) {
}
ngOnInit(){
this.dynamicComponentLoader.loadIntoLocation(BulletinComponent, this.elementRef,'child');
}
}
bulletin.component.ts
import {Component} from 'angular2/core';
#Component({
selector: 'bulletin',
template: '<div>Hi!</div>'
}
})
export class BulletinComponent {}
paper.html
<div>
<div #child></div>
</div>
Few things you needs to be take care of are mentioned in this answer
You should use ComponentFactoryResolver and ViewElementRef to add component at runtime.Let's have a look at below code.
let factory = this.componentFactoryResolver.resolveComponentFactory(SpreadSheetComponent);
let res = this.viewContainerRef.createComponent(factory);
Put the above code inside your ngOnInit function and replace "SpreadSheetComponent" by your component name.
Hope this will work.
Programmatically add components to DOM in Angular 2/4 app
We need to use ngAfterContentInit() lifecycle method from AfterContentInit. It is called after the directive content has been fully initialized.
In the parent-component.html, add the a div like this:
<div #container> </div>
The parent-component.ts file looks like this:
class ParentComponent implements AfterContentInit {
#ViewChild("container", { read: ViewContainerRef }) divContainer
constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
ngAfterContentInit() {
let childComponentFactory = this.componentFactoryResolver.resolveComponentFactory(childComponent);
this.divContainer.createComponent(childComponentFactory);
let childComponentRef = this.divContainer.createComponent(childComponentFactory);
childComponentRef.instance.someInputValue = "Assigned value";
}
}
Inside src\app\app.module.ts, add the following entry to the #NgModule() method parameters:
entryComponents:[
childComponent
],
Notice that we're not accessing the div#container using the #ViewChild("container") divContainer approach. We need it's reference instead of the nativeElement. We will access it as ViewContainerRef:
#ViewChild("container", {read: ViewContainerRef}) divContainer
The ViewContainerRef has a method called createComponent() which requires a component factory to be passed as a parameter. For the same, we need to inject a ComponentFactoryResolver. It has a method which basically loads a component.
The right approach depends on the situation you're trying to solve.
If the number of children is unknown then NgFor is the right approach.
If it is fixed, as you mentioned, 3 children, you can use the DynamicComponentLoader to load them manually.
The benefits of manual loading is better control over the elements and a reference to them within the Parent (which can also be gained using templating...)
If you need to populate the children with data, this can also be done via injection, the Parent is injected with a data object populating the children in place...
Again, a lot of options.
I have used 'DynamicComponentLoader' in my modal example, https://github.com/shlomiassaf/angular2-modal