16

Cannot get SSR to work when using Angular Universal with pages that use dynamic content. All other pages work and the dynamic pages return with HTML but do not include the dynamic data.

Method Called:

ngOnInit() {
  const div = this.renderer.createElement('div');
  const texttest = this.renderer.createText('this works');

  this.renderer.appendChild(div, texttest);
  this.renderer.appendChild(this.content.nativeElement, div);

  this.createLinkForCanonicalURL();

  // The HTML never shows, the method works fine.
  this._contentfulService.getBlogPosts().then(posts => {
    this.blogPosts = _.orderBy(posts, ['sys.createdAt'], ['desc']);
  });
}

Service Method

getBlogPosts(query ? : object): Promise<Entry<any>[]> {
  return this.cdaClient.getEntries(Object.assign({
      content_type: CONFIG.contentTypeIds.blogPosts
    }, {
      'fields.private': false
    }))
    .then(res => res.items);
}

Template:

This never shows in the source.

<li class="blog-post" *ngFor="let post of blogPosts">
  <h1>{{post.fields.title}}</h1>
</li>

Have tried the starter kits and they do not work calling the same method. Also tried render2 to inject and a resolver service to test fetching data before the load.

Nothing seems to work?

Any help would be appreciated!

EDIT

This is my server.ts file

// These are important and needed before anything else
import 'zone.js/dist/zone-node';
import 'reflect-metadata';

import { enableProdMode } from '@angular/core';
import * as express from 'express';
import { join } from 'path';

// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();

// Express server
const app = express();

const PORT = process.env.PORT || 8080;
const DIST_FOLDER = join(process.cwd(), 'dist');

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = 
require('./dist/server/main');

// Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';

app.engine('html', ngExpressEngine({
  bootstrap: AppServerModuleNgFactory,
  providers: [
    provideModuleMap(LAZY_MODULE_MAP)
 ]
}));

app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));

// TODO: implement data requests securely
app.get('/api/*', (req, res) => {
  res.status(404).send('data requests are not supported');
});

// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));

// All regular routes use the Universal engine
app.get('*', (req, res) => {
  res.render('index', { req });
});

// Start up the Node server
app.listen(PORT, () => {
  console.log(`Node server listening on http://localhost:${PORT}`);
});

and my app server module

@NgModule({
bootstrap: [AppComponent],

imports: [
    BrowserModule.withServerTransition({ appId: 'app-root' }),

    AppModule,
    ModuleMapLoaderModule,
    ServerModule,
    NoopAnimationsModule,
    ServerTransferStateModule, // comment
]
})
export class AppServerModule { }
nisarg parekh
  • 413
  • 4
  • 23
Smokey Dawson
  • 8,827
  • 19
  • 77
  • 152
  • First of all, does it work when you launch you application using ng serve ? Have you tried the getBlobPosts method on other page, are you sure it's returning data ? you can add console.log to see what happen on SSR – xrobert35 Sep 18 '18 at 07:21
  • Which version of angular are you using ? – xrobert35 Sep 18 '18 at 07:31
  • The method 100% works, I’m using angular 6 – Smokey Dawson Sep 18 '18 at 07:32
  • can you reproduce the issue on stackblitz? – Shadab Faiz Sep 19 '18 at 04:09
  • For Angular Universal to run properly it needs to be built and served differently than a normal project, so a stackblitz wont show the issue.. – Smokey Dawson Sep 19 '18 at 04:10
  • The data are displaied correctly but are only resolve in front right ? It's just the server that don't wait the data to be resolved before send the html page ? – xrobert35 Sep 20 '18 at 06:17
  • @xrobert35 yes pretty much – Smokey Dawson Sep 20 '18 at 06:40
  • Which engine are you using ? ( ngExpressEngine ? ) can you add some code about this. and also your "server root module" – xrobert35 Sep 20 '18 at 06:41
  • Yes I'm using ngExpressEngine, I will update my question – Smokey Dawson Sep 20 '18 at 06:47
  • please see my updated question – Smokey Dawson Sep 20 '18 at 06:49
  • nothing seem to be wrong here, and your AppServerModule ? – xrobert35 Sep 20 '18 at 06:50
  • @xrobert35 please see edited question – Smokey Dawson Sep 20 '18 at 06:52
  • ok and to finish, your main server ? – xrobert35 Sep 20 '18 at 06:53
  • Futhermore when you say that "the method work fine" this._contentfulService.getBlogPosts(); how can you know ? did you try to add a console.log in the callback with the result to see on the server console that everything is fine ? – xrobert35 Sep 20 '18 at 07:18
  • I get the list of the blog posts, displayed on the blog page, but if I inspect the source code, the html for each post is not there – Smokey Dawson Sep 20 '18 at 07:24
  • can you show your main ? (that load the AppServerModule) – xrobert35 Sep 20 '18 at 12:31
  • Dumb question, but I assumed that you checked that you get no errors **server side** (i.e. in your angular universal console/file logs) during rendering? And is `blogPosts` correctly initialised to an empty array ? – David Sep 22 '18 at 06:23
  • @David the method and everything works fine, the issue is that when I view the page source I don't see the rendered HTML for each blog post, it just has the `` now normally this wouldnt be an issue, but due to site crawlers needing the html for SEO, It's a problem. The blog posts display fine on the app and are properly initialised to an empty array – Smokey Dawson Sep 23 '18 at 23:12
  • 1
    Yeah I did understand the problem, but this is often caused because of a server side error during SSR (not talking about the API, but the nodejs process doing SSR). If everything is fine server side, I'm not sure what could be causing the issue. – David Sep 24 '18 at 05:58

4 Answers4

5

You need use TransferHttpCacheModule for wait HTTP call to API or use another implementation that work with TransferState like ngx-transfer-http .

My sample with wait 3-second delay: https://github.com/Angular-RU/angular-universal-starter/blob/master/src/app/transfer-back/transfer-back.component.ts

gorniv
  • 180
  • 2
  • 8
  • If the module containing `TransferHttpCacheModule` is not yet installed in the app, install it with `npm install @nguniversal/common@x.x.x --save` (replace x.x.x with version compatible with your angular app) – Paramvir Singh Karwal Oct 15 '19 at 18:48
  • @gorniv i use ngx-transfer-http but on view source the dynamic data is not showing – Mohammed Feb 11 '23 at 09:55
  • Gorniv you're a lifesaver! Your library works like a dream. It's so frustrating that Angular Universal team still can't seem to fix their own TransferHttpCacheModule yet – Andrew Howard Apr 20 '23 at 18:27
1

It seems that angular universal doesn't wait for async calls to render.

I had the same problem and finally made it work using ZoneMacroTaskWrapper as mentioned in this issue.

This is the service I made.

Alvaro
  • 95
  • 1
  • 8
1

In 2022 you would want to implement a Resolver (Resolve interface from @angular/router) in combination with TransferState from @angular/platform-browser.

Get the data by calling your API in your resolver. Add the resolver in your app-routing.module.ts to the route. The resolver then is automatically called by Angular's routing service. Best is that when you are storing the API data in TransferState during the SSR process, your user's browser will get the data from TransferState rather than doing the API call a second time.

full example here: GitHub: Angular University Universal Course

Example code...

Resolver implementation

@Injectable()
export class CourseResolver implements Resolve<Course> {

    constructor(
        private coursesService: CoursesService,
        @Inject(PLATFORM_ID) private platformId,
        private transferState:TransferState
    ) {

    }

    resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Course> {

        const courseId = route.params['id'];

        const COURSE_KEY = makeStateKey<Course>('course-' + courseId);

        if (this.transferState.hasKey(COURSE_KEY)) {

            const course = this.transferState.get<Course>(COURSE_KEY, null);

            this.transferState.remove(COURSE_KEY);

            return of(course);
        }
        else {
            return this.coursesService.findCourseById(courseId)
                .pipe(
                    first(),
                    tap(course => {

                        if (isPlatformServer(this.platformId)) {
                            this.transferState.set(COURSE_KEY, course);
                        }

                    })
                );

        }


    }

app-routing.module.ts

const routes: Routes = [
    {
        path: "",
        component: HomeComponent

    },
    {
        path: 'courses/:id',
        component: CourseComponent,
        resolve: {
            course: CourseResolver
        }
    },
    {
        path: "**",
        redirectTo: '/'
    }
];

Getting the data in your component

...
    constructor(private route: ActivatedRoute,
                private coursesService: CoursesService,
                private title: Title,
                private meta: Meta) {

    }


    ngOnInit() {

        this.course = this.route.snapshot.data['course'];
...
}
...
Humppakäräjät
  • 1,156
  • 2
  • 13
  • 17
  • i implemented like you described in this post,but the rendered dynamic data not available in viewsource, but it's present in snapshot – Mohammed Feb 11 '23 at 12:31
  • Here it's working fine (just tested). My data shows up in view-source. The above code is copy pasted from the linked GitHub example. You probably should check that out? – Humppakäräjät Feb 12 '23 at 14:05
  • 1
    i were using npm run dev:ssr command , the api endpoint needed a authentication header also, everything was correct backend receiving authorisation correctly.but the data is not present in view source, then i ran the commmand npm run build:ssr && npm run serve:ssr then it worked properly now i can see the data in view source although i don't know how it solves the issue – Mohammed Feb 13 '23 at 18:03
0

Try to edit your code as follows:

async ngOnInit() {
  const div = this.renderer.createElement('div');
  const texttest = this.renderer.createText('this works');

  this.renderer.appendChild(div, texttest);
  this.renderer.appendChild(this.content.nativeElement, div);

  this.createLinkForCanonicalURL();

  const posts = await this._contentfulService.getBlogPosts();
  this.blogPosts = _.orderBy(posts, ['sys.createdAt'], ['desc'])

 }
Alex Cheremisin
  • 238
  • 2
  • 10