Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: feat(addon-docs): Angular inline rendering support #11339

Closed
wants to merge 1 commit into from

Conversation

alc-1
Copy link

@alc-1 alc-1 commented Jun 27, 2020

Issue: #8153

🚀 This is a first attempt to allow inlining Angular stories in addon-docs.
⚠️ While this is not working yet, it might give birth to discussions permitting to reach that goal.

What I did

What I miss

  • Currently this isn't working.
  • Shall we put the angular-to-react logic inside addon-docs? Or should we put it in an external project like for vue-to-react?
    • This question also relates to dependency management in addon-docs

How to test

Go into the angular-cli storybook, then take any story and display the DocPage.
⚠️ Currently, this results in the following error :
Error: A platform with a different configuration has been created. Please destroy it first. zone-error.js:97

  • Is this testable with Jest or Chromatic screenshots?
    • Yes, if we can apply Chromatic screenshots to addon-docs (is it??)
  • Does this need a new example in the kitchen sink apps?
    • Yes, to showcase both possible cases : inline vs iframe.
  • Does this need an update to the documentation?
    • Yes, Angular inlineStories will probably be disabled by default so it'll be up to the user to toggle this flag.

If your answer is yes to any of these, please make sure to include it in your PR.

@shilman
Copy link
Member

shilman commented Jun 27, 2020

Awesome start!!

Re: dependency management. I’m planning on adjusting the vue dependencies such that vue-to-React is a dependency of @storybook/vue and an optional peer dependency of addon-docs. Therefore someone who installs docs in a non-vue project doesn’t get any of the vue stuff. We should do something similar for angular!

@alc-1
Copy link
Author

alc-1 commented Jun 27, 2020

Awesome start!!

Thank you! 😄

Re: dependency management. I’m planning on adjusting the vue dependencies such that vue-to-React is a dependency of @storybook/vue and an optional peer dependency of addon-docs. Therefore someone who installs docs in a non-vue project doesn’t get any of the vue stuff. We should do something similar for angular!

Sounds like a smart move, thanks for the input!
Even if I allow myself to keep it dirty while doing these tests, it'd be wise to put any related dependencies under @storybook/angular at some point, for sure.

❓ Otherwise, some questions about addon-docs logic, since I dunno how it works :

  • Basically, any inlinedStory seems to be rendered via a call to docs.prepareForInline
  • ... And it seems to be used in Story.tsx while constructing a Story component.
  • ... But then, when do we instantiate these Story components?

💡 The whole point is that I suspect we'll need to have some kind of BeforeAll or AfterAll mechanism to instantiate Angular stories in a statefull way, since we'll need to bootstrap a single Angular app that'll be used to construct every component (not proven yet, but it's one potential way).

@shilman
Copy link
Member

shilman commented Jun 28, 2020

Agree with the idea of keeping it dirty during prototyping, then moving any angular-specific dependencies out later.

Re: beforeAll / afterAll, we should be able to add that for angular. There are two relevant story parameters: docs.page and docs.container. The angular framework preset can override docs.container with a React component that wraps the existing DocsContainer and also uses React lifecycle hooks like useEffect to do extra work before and after the render.

WDYT? Something like:

import React, { useEffect } from 'react';
import { DocsContainer, DocsContainerProps } from '../blocks';

const beforeRender = () => {
  console.log('before');
}

const afterRender = () => {
  console.log('after');
}

const AngularDocsContainer: FC<DocsContainerProps> = (props) => {
  useEffect(() => {
    beforeRender();
    return afterRender;
  });
  return <DocsContainer {...props}>;
}

"@angular/platform-browser-dynamic": "8.2.14",
"reflect-metadata": "0.1.13",
"zone.js": "^0.10.2",
"@angular/core": "^9.1.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using the same version for all @angular-namespaced packages. I noticed @angular/core is 9, and the rest are 8

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, will do soon!

node: HTMLElement,
AppComponentInstance: any
): Promise<NgModuleRef<any>> => {
node.appendChild(document.createElement(SELECTOR));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider allowing users to pass in their own value for SELECTOR. When defining angular components, the decorator accepts a selector value. Users may wish to use their own selector value.

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

@Component({
  selector: 'app-hero-list', // <-- current code assumes this value === 'app-root' 
  templateUrl: './hero-list.component.html',
})
export class HeroListComponent {
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that's a good point.
I started integrating your solution directly in Storybook, hoping for some magic to happen.. 🧙
Still permits to talk about how & where to add this logic, but not good enough for testing.

I ended up making a React side-project and successfully applied your solution there.
Currently looking into injecting metadata, then I'll get into selectors.
I'll let you know once I have news about that, gonna focus on this task for the days to come. 😉

schemas: [CUSTOM_ELEMENTS_SCHEMA],
})(class {});
return platformBrowserDynamic().bootstrapModule(AppModule);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible users may need to pass in multiple components, in scenarios where a parent Angular component renders a child Angular component. In that case, declarations would need to accept an array of components.

(IIRC) Only the root component should be listed in the bootstrap array.

Example: Suppose I need to render three components, A, B, and C, and A is the root component.

AppModule should look like this:

  const AppModule = NgModule({
    declarations: [A, B, C],
    imports: [BrowserModule],
    bootstrap: [A],
    schemas: [CUSTOM_ELEMENTS_SCHEMA],
  })(class {});

It could be beneficial to update the bootstrapAngularApp contract to be more flexible. Example:

const bootstrapAngularApp = (
  node: HTMLElement,
-  AppComponentInstance: any
+  AppComponentInstance: any,
+  moduleOverrides?: object = {}
): Promise<NgModuleRef<any>> => {
  node.appendChild(document.createElement(SELECTOR));
  const AppModule = NgModule({
    declarations: [AppComponentInstance],
    imports: [BrowserModule],
    bootstrap: [AppComponentInstance],
    schemas: [CUSTOM_ELEMENTS_SCHEMA],
+   ...moduleOverrides
  })(class {});

This allows users to completely configure the object passed to the NgModule function.

My original comment (#3889 (comment)) only scratched the surface of what users could possibly need, and my code there represented my individual use case at the time. I ended up needing more, like adding more items to the imports array.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So glad to see you there @petermikitsh! Was hoping to get some insights from you to achieve this task. 😄
Thanks for coming and for reviewing!

It could be beneficial to update the bootstrapAngularApp contract to be more flexible.

You'r absolutely right! After thinking about it, we simply need to pass anything declared in the story's moduleMetada : All declarations, imports & providers are already there. I'll have to dig into where & how it is used, so that I adapt this logic.

My original comment (#3889 (comment)) only scratched the surface of what users could possibly need

It's already a lot, you know. Even if it's not the complete solution, it's enough to give a direction.. and honestly, without it, I wouldn't dare trying to resolve this.

@alc-1
Copy link
Author

alc-1 commented Jun 28, 2020

@shilman Thanks for the hints on implementing a BeforeAll / AfterAll (example greatly appreciated!)

Sounds good to me, I'll look further into it when/if the implementation needs to go that way. 😄

@stale
Copy link

stale bot commented Jul 19, 2020

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

@manuelmeister
Copy link
Contributor

How can I help with this?

@manuelmeister
Copy link
Contributor

@shilman I think this can be closed by #13525, correct?

@shilman
Copy link
Member

shilman commented Feb 3, 2021

Indeed!! Thanks

@shilman shilman closed this Feb 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants