How to share HTML between Angular 2 components

583 views
Skip to first unread message

halvorsen

unread,
Jul 21, 2017, 1:52:15 AM7/21/17
to Angular and AngularJS discussion

I want to share some HTML (layout) between some of my components but not all.

The app.compomonent:

<div class="classA">
   
<router-outlet></router-outlet>
</div>

Some of my components, but not all, share some HTML:

Component X

<div class="classB">
   
<h2>{{Subtitle}}</h2>
</div>
<div class="classC">
    X_SPECIFIC_HTML
</div>

Component Y

<div class="classB">
   
<h2>{{Subtitle}}</h2>
</div>
<div class="classC">
    Y_SPECIFIC_HTML
</div>

I want to define the shared HTML (note the data binding) with a place holder for the actual component HTML:

<div class="classB">
   
<h2>{{Subtitle}}</h2>
</div>
<div class="classC">
    INSERT_COMPONENT_HTML_HERE
</div>

So my components can be defined like this:

Component X

X_SPECIFIC_HTML

Component Y

Y_SPECIFIC_HTML

Current routes:

const appRoutes: Routes = [
 
{ path: 'x', component: XComponent },
 
{ path: 'y', component: YComponent }
];


Is this possible?

Sander Elias

unread,
Jul 21, 2017, 2:03:26 AM7/21/17
to Angular and AngularJS discussion
Hi Halvorsen,

Yes that is possible, The easiest way is adding another components that uses content projection.

something like this:
import { Component } from '@angular/core';

@Component({
selector: 'holder',
template: `
<div class="classB">
<h2>{{Subtitle}}</h2>
</div>
<div class="classC">
<ng-content></ng-content>
</div>
`
})
export class MyHolderComponent {}

@Component({
selector: 'howToUse',
template: `
<holder>
Y_SPECIFIC_HTML
</holder>
`
})
export class MyHowToUseComponent {}

But there are more way's to resolve your issue.

Regards
Sander

halvorsen

unread,
Jul 21, 2017, 3:22:38 AM7/21/17
to Angular and AngularJS discussion
Hi Sander,

Thanks, exactly what I was looking for. The only problem is that data binding does not work I have tried to the simplest possible solution I could think of, but maybe I need to use a BehaviorSubject and observable/subscribe?

page-layout.component

<div style="background-color: red;">
<h1>{{subtitle}}</h1>
<ng-content></ng-content>
</div>

import { Component } from '@angular/core';

@Component({
selector: 'page-layout',
templateUrl: './page-layout.component.html',
styleUrls: ['./page-layout.component.css']
})

export class PageLayoutComponent {
subtitle = 'PageLayoutComponent';
}

work-orders.component

<page-layout>
...
</page-layout>

import { Component, OnInit } from '@angular/core';

import { PageLayoutComponent } from '../../components/page-layout/page-layout.component';

@Component({
selector: 'work-orders',
templateUrl: './work-orders.component.html',
styleUrls: ['./work-orders.component.css']
})

export class WorkOrdersComponent extends PageLayoutComponent implements OnInit {
ngOnInit(): void {
this.subtitle = 'WorkOrdersComponent';
}

constructor() {
super();
}
}

Regards,
halvorsen
Message has been deleted
Message has been deleted
Message has been deleted

halvorsen

unread,
Jul 21, 2017, 7:01:50 PM7/21/17
to Angular and AngularJS discussion
My solution using "content projection" and data binding through a BehaviorSubject.

page-layout.component (the shared HTML)

    <div style="background-color: red;">
     
<h2>subtitle: {{subtitle}}</h2>
     
<ng-content></ng-content>
   
</div>


    import { Component, OnInit } from '@angular/core';
    import { LayoutService } from '../../services/layout.service';

    @Component({
      selector: 'page-layout',
      templateUrl: './page-layout.component.html',
      styleUrls: ['./page-layout.component.css']
    })
    export class PageLayoutComponent implements OnInit {
      subtitle = '';
      constructor(private LayoutService: LayoutService) {
      }
      ngOnInit() {
        this.LayoutService.observable.subscribe(x => {
          if (console) {
            console.log('PageLayoutComponent, subscribe: ' + JSON.stringify(x));
          }
          this.subtitle = x.subtitle;
        });
      }
    }

assembly-list.component (a component using the shared HTML)

    <page-layout>
     
<p>ToDo</p>
   
</page-layout>


    import { Component, OnInit } from '@angular/core';
    import { LayoutService } from '../../services/layout.service';
    @Component({
      selector: 'assembly-list',
      templateUrl: './assembly-list.component.html',
      styleUrls: ['./assembly-list.component.css']
    })
    export class AssemblyListComponent implements OnInit {
      constructor(private LayoutService: LayoutService) {
      }
      ngOnInit() {
        this.LayoutService.emitTitle('AssemblyListComponent1', 'AssemblyListComponent2');
      }
    }

layout-service (the data binding for the shared HTML)
   
    import { Component, Injectable } from '@angular/core';
   
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
   
export interface ILayoutServiceData {
      title
: string;
      subtitle
: string;
   
}
   
@Injectable()
   
export class LayoutService {
     
private behaviorSubject: BehaviorSubject<ILayoutServiceData> = new BehaviorSubject({title: null, subtitle: null});
      observable
= this.behaviorSubject.asObservable();
      emitTitle
(title: string, subtitle: string) {
       
if (console) {
          console
.log(`LayoutService.emitTitle(\'${title}\', \'${subtitle}\'`);
       
}
       
const data: ILayoutServiceData = {
          title
: title,
          subtitle
: subtitle
       
};
       
this.behaviorSubject.next(data);
     
}
   
}


Sander Elias

unread,
Jul 22, 2017, 1:44:41 AM7/22/17
to Angular and AngularJS discussion
Hi Halvorsen,

That is one way to go. I think that for a single string, it is a bit off an overkill and I would use an eventemmiter. There is a nice sample here in the docs

Regards
Sander

halvorsen

unread,
Jul 22, 2017, 4:34:10 AM7/22/17
to Angular and AngularJS discussion
Hi Sander,

Thanks, it is my first time with Angular, so I still have a lot to learn, and when not having the right search terms it can be a little bit hard to find the answer you are looking.

It is overkill but the only thing I could come up with. I currently use it to communicate with the app.component (layout title) and the page-layout.component (subtitle), but still it feels a bit overkill for something as simple as this. I'll have a look at the EventEmitter.

Thanks again.

Regards,
halvorsen

Sander Elias

unread,
Jul 22, 2017, 5:30:47 AM7/22/17
to Angular and AngularJS discussion
Hi Halvorsen,

Well, it is an impressive first try. Most starters shy away from observables. I think they are awesome, and they will make your life a lot easier in the long run. So getting familiar with them is a good thing. They provide a powerful abstraction! In our line of business there are always multiple solutions to the same issue. There isn't really a strong good or wrong. Some solutions are more efficient, and even that is changing all the time. I completely forgot where I was going to with this line of explaining :-D

Usually, the less code you need, the easier it is to maintain. That is why I recommended  the event emitter solution.

Regards
Sander

halvorsen

unread,
Jul 22, 2017, 6:29:59 AM7/22/17
to Angular and AngularJS discussion
Hi Sander,

I'm having a real hard time making the EventEmitter work for my case.

The documentation example is straight forward as you insert the child component as a tag in the parent component's markup.

<my-voter ... (onVoted)="onVoted($event)">
</my-voter>

In my case, the child tag is the ng-content tag in my page-layout.component, but it does of course not work.

<div style="background-color: red;">
 
<h2>page-layout.subtitle: {{subtitle}}</h2>
 
<ng-content (onSetTitle)="onSetTitle($event)"></ng-content>
</div>

Then I thought the page-layout tag in the assembly-list.component must be the "real" parent but this doesn't work either.I get a Member 'onSetTitle' is no callable which makes sense.

<page-layout (onSetTitle)="onSetTitle($event)">
   
<p>ToDo</p>
</page-layout>

I have found a similar problem at the link below, but I don't fully get the solution (can't make it work):
https://stackoverflow.com/questions/43227652/angular-2-ng-content-receive-event-from-child-component

The link above links to a Plunker which looks similar to what I currently got:
http://plnkr.co/edit/Kt7jpEwzKC8ljvKGLbKt?p=preview

Any suggestions?

I thought Angular should speed of my development. I have now spent several hours on a simple problem that could have been solved with a single line of jQuery :-)

Sander Elias

unread,
Jul 22, 2017, 6:32:59 AM7/22/17
to Angular and AngularJS discussion
Hi Halvorsen,

I don't have the time right now, bit I will whip up an example for you.

Regards
Sander

halvorsen

unread,
Jul 22, 2017, 6:40:26 AM7/22/17
to Angular and AngularJS discussion
Hi Sander,

I appreciate that very much, thanks :-)

Regards,
halvorsen

Sander Elias

unread,
Jul 22, 2017, 11:55:27 PM7/22/17
to Angular and AngularJS discussion
Hi Halvorsen,

Here is a sample that shows a couple of way's it can be done. Oh, the eventEmitter is still possible, but you don't need it at all :)

Regards
Sander

halvorsen

unread,
Jul 23, 2017, 4:53:16 AM7/23/17
to Angular and AngularJS discussion
Thank you :-)

Regards,
halvorsen
Reply all
Reply to author
Forward
0 new messages