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

feat(elements): implement @angular/elements #19469

Closed
wants to merge 8 commits into from

Conversation

gkalpak
Copy link
Member

@gkalpak gkalpak commented Sep 28, 2017

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

[ ] Bugfix
[ ] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Documentation content changes
[ ] angular.io application / infrastructure changes
[ ] Other... Please describe:

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

[ ] Yes
[ ] No

Other information

@gkalpak gkalpak added state: WIP target: major This PR is targeted for the next major release feature Issue that requests a new feature labels Sep 28, 2017
@gkalpak gkalpak force-pushed the feat-custom-elements branch 3 times, most recently from 1e27a28 to 3e825a1 Compare September 30, 2017 08:34
@mary-poppins
Copy link

You can preview 3e825a1 at https://pr19469-3e825a1.ngbuilds.io/.

Copy link
Contributor

@mhevery mhevery left a comment

Choose a reason for hiding this comment

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

Few comments. @robwormald will have more feedback for you.

Also I think we decided to cal it just @angular/elements so you will need to do search and replace.

// (i.e. not transpiled to an ES5 constructor function).
// TODO(gkalpak): Document that if you are using ES5 sources you need to include a polyfill
// (e.g. https://github.com/webcomponents/webcomponentsjs/blob/master/custom-elements-es5-adapter.js).
class NgElementConstructorImpl extends NgElementBaseImpl<T> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we have FooImpl? Why not just Foo. There is but one implementation.

See: https://github.com/angular/angular/blob/master/docs/NAMING.md

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure what you mean. There are interfaces called NgElementConstructor and NgElementBase, so I suffixed the implementations with Impl (note that all Impl-suffixed things are not public API).

Open to suggestions for a different naming scheme 😃

* TODO(gkalpak): Add docs.
* @experimental
*/
export interface NgElementBase<T> extends HTMLElement {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this interface adds value. Just go directly to implementation skip interface and remove -Impl

Copy link
Member Author

Choose a reason for hiding this comment

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

The implementation has public members (properties, methods, constructor) that I don't want to document.

Copy link
Member Author

Choose a reason for hiding this comment

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

BTW, I was thinking about renaming these as follows:

// Before:
NgElement = NgElementBase & {[prop in keyof P]: P[prop]}
NgElementBase { /* the actual interface without custom props */ }

// After:
NgElementWithProps = NgElement & {[prop in keyof P]: P[prop]}
NgElement { /* the actual interface without custom props */ }

Would it be clearer/less clattered?

disconnected,
}

interface NgElementBaseImplConnected<T> extends NgElementBaseImpl<T> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Interface by definition can't be Impl

import {NgElementBaseImpl, NgElementInput, NgElementOutput} from '../src/ng-element';
import {setScheduler} from '../src/utils';

export function main() {
Copy link
Contributor

Choose a reason for hiding this comment

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

main used to be for Dart. It is no longer needed, you can skip it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hm...not so sure about that 😉

@gkalpak gkalpak force-pushed the feat-custom-elements branch from 3e825a1 to 27efe36 Compare October 3, 2017 21:34
@mary-poppins
Copy link

You can preview 27efe36 at https://pr19469-27efe36.ngbuilds.io/.

@@ -0,0 +1,22 @@
{
"name": "@angular/custom-elements",
Copy link
Contributor

Choose a reason for hiding this comment

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

think we're going with @angular/elements here


// NOTE: This is a (slightly improved) version of what is used in ngUpgrade's
// `DowngradeComponentAdapter`.
// TODO(gkalpak): Investigate if it makes sense to share the code.
Copy link
Contributor

Choose a reason for hiding this comment

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

do we want to support this case at all? it seems like it should be unsupported, since we're not going to be able to get any of the @ContentChild/Children stuff to work here?

<slot> handles this natively, what's the benefit of doing this over relying on platform behavior?

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess ContentChild(ren) don't work with projected content. What else doesn't work?

I don't feel strongly about supporting it. In my head, the idea is to have something work as similar as possible when inside and outside an Angular app (so I can re-use components are "native" Angular components or custom elements).

readonly selector: string;
readonly observedAttributes: string[];

upgrade(host: HTMLElement): NgElement<T, P>;
Copy link
Contributor

Choose a reason for hiding this comment

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

is this your method name? Worried about a potential conflict with a future native upgrade thing which has been discussed

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah. I thought it makes sense to call it something that describes what it is actually doing (in CustomElements terms). In the design docs it is called attach() (which is more arbitrary).

I wasn't aware of any discussions about a native upgrade thing. Are there any resources you can point me to?

export function createNgElementConstructor<T, P>(appRef: ApplicationRef, injector: Injector, componentFactory: ComponentFactory<T>): NgElementConstructorInternal<T, P> {
const inputs = componentFactory.inputs.map(({propName, templateName}) => ({
propName,
attrName: camelToKebabCase(templateName),
Copy link
Contributor

Choose a reason for hiding this comment

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

This is kinda sketchy - seems like we should avoid these heuristics where possible?

Copy link
Member Author

Choose a reason for hiding this comment

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

The problem this is trying to address is that most of the time attribute names are the same as property names (unless explicitly overriden), which in turn are usually camelCased. CamelCased attributes are fine when something is compiled with Angular's compiler, but we can't have them in native HTML.

So, we either need to lowercase them or kebabcase them. (@mhevery said kebabcasing is fine.)

// (e.g. https://github.com/webcomponents/webcomponentsjs/blob/master/custom-elements-es5-adapter.js).
class NgElementConstructorImpl extends NgElementBaseImpl<T> {
static readonly selector = componentFactory.selector.toLowerCase();
static readonly observedAttributes = inputs.map(input => input.attrName);
Copy link
Contributor

Choose a reason for hiding this comment

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

this seems to conflate attributes and @inputs ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure I understand the question, but probably yes 😁
According to the design, updating an attribute will propagate to the corresponding property (not the other way around though) and attributes will be considered for initializing properties (when we instantiate the component).

// TODO(gkalpak): Document that if you are using ES5 sources you need to include a polyfill
// (e.g. https://github.com/webcomponents/webcomponentsjs/blob/master/custom-elements-es5-adapter.js).
class NgElementConstructorImpl extends NgElementBaseImpl<T> {
static readonly selector = componentFactory.selector.toLowerCase();
Copy link
Contributor

Choose a reason for hiding this comment

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

should be static is = componentFactory.selector.toLowerCase(); - matches with Polymer nicely?

Why are we lowercasing the selector? We should not allow the following usages:

  • foo-Bar - (case sensitive)
  • foobar - violates the CE spec
  • [foo-bar] - doesn't make sense in CE land

(tldr, we should require people to always use dash-case selectors, because the spec requires it :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Happy to rename selector to is (although I think selector is more clear 😁).
The lowercasing is just to be consistent with the casing, so that we can use it to match against nodeNames later without having to lower/uppercase it every time.

I have also thought about "validating" the selector and throwing if it is not a dash-case selector. In practice, I don't think it is possible to instantiate an element that hasn't been added to the customElements registry, which means that customElements.define(selector, ...) will throw anyway.

But maybe it will be possible to bypass the customElements registry in the future, so 👍 for having a explicit check for the selector.

static readonly selector = componentFactory.selector.toLowerCase();
static readonly observedAttributes = inputs.map(input => input.attrName);
static readonly onConnected = new EventEmitter<NgElement<T, P>>();
static readonly onDisconnected = new EventEmitter<NgElement<T, P>>();
Copy link
Contributor

Choose a reason for hiding this comment

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

what are these for? can we not just use the lifecycle hooks themselves?

Copy link
Member Author

Choose a reason for hiding this comment

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

These emit values when an NgElement of this type is connected/disconnected.
How would we use the lifecycle hooks for that?

@gkalpak gkalpak force-pushed the feat-custom-elements branch 2 times, most recently from 443e5d7 to ea73465 Compare October 4, 2017 13:18
@gkalpak gkalpak changed the title WIP - feat(custom-elements): implement custom-elements WIP - feat(elements): implement @angular/elements Oct 4, 2017
@gkalpak gkalpak force-pushed the feat-custom-elements branch 5 times, most recently from 945fff2 to 663e924 Compare October 11, 2017 22:50
@gkalpak gkalpak force-pushed the feat-custom-elements branch 4 times, most recently from a9a81a1 to 6a7a490 Compare October 16, 2017 22:08
@mary-poppins
Copy link

You can preview 6a7a490 at https://pr19469-6a7a490.ngbuilds.io/.

karma-js.conf.js Outdated
@@ -34,6 +34,9 @@ module.exports = function(config) {
'node_modules/zone.js/dist/async-test.js',
'node_modules/zone.js/dist/fake-async-test.js',

// Include polyfills necessary for testing the `custom-elements` package.
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't it be elements package now?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch 😃

@gkalpak gkalpak force-pushed the feat-custom-elements branch 4 times, most recently from 6189089 to caaa98e Compare October 17, 2017 11:20
@mary-poppins
Copy link

You can preview ead31ec at https://pr19469-ead31ec.ngbuilds.io/.

@gkalpak gkalpak force-pushed the feat-custom-elements branch from ead31ec to 21bfaf2 Compare November 2, 2017 09:49
@mary-poppins
Copy link

You can preview 21bfaf2 at https://pr19469-21bfaf2.ngbuilds.io/.

@gkalpak gkalpak added target: patch This PR is targeted for the next patch release and removed target: major This PR is targeted for the next major release labels Nov 2, 2017
@vicb
Copy link
Contributor

vicb commented Nov 2, 2017

@gkalpak is it expected to have a feat on a patch branch (5.0.x) ?

@gkalpak
Copy link
Member Author

gkalpak commented Nov 2, 2017

Hm...probably not. I had it marked as master-only before the release, but since it didn't get merged, I changed to master & patch so that it lands on the branches that it would have landed if it were merged earlier. Didn't think about the fix-vs-feat distinction.
(I've had too much AngularJS, I guess, where patch releases are treated as minor releases (since there can be no major version bumps) 😁)

Thx for catching this 👍

@gkalpak gkalpak added target: major This PR is targeted for the next major release and removed target: patch This PR is targeted for the next patch release state: blocked labels Nov 2, 2017
@vicb
Copy link
Contributor

vicb commented Nov 2, 2017

Thanks for you PR!
Target labels are confusing.

@vicb vicb closed this in dcf8840 Nov 2, 2017
@gkalpak
Copy link
Member Author

gkalpak commented Nov 3, 2017

🎉

@gkalpak gkalpak deleted the feat-custom-elements branch November 3, 2017 09:33
IgorMinar added a commit to IgorMinar/angular that referenced this pull request Nov 3, 2017
This PR was merged without API docs and general rollout plan.

We can't release this as is in 5.1 without a plan for documentation, cli integration, etc.
IgorMinar added a commit to IgorMinar/angular that referenced this pull request Nov 3, 2017
This PR was merged without API docs and general rollout plan.

We can't release this as is in 5.1 without a plan for documentation, cli integration, etc.
IgorMinar added a commit that referenced this pull request Nov 3, 2017
This PR was merged without API docs and general rollout plan.

We can't release this as is in 5.1 without a plan for documentation, cli integration, etc.
vicb pushed a commit that referenced this pull request Nov 3, 2017
This PR was merged without API docs and general rollout plan.

We can't release this as is in 5.1 without a plan for documentation, cli integration, etc.
@MarkPieszak
Copy link
Member

Was this merged in elsewhere ? @gkalpak

@IgorMinar
Copy link
Contributor

@jponeill
Copy link

jponeill commented Dec 6, 2017

Should this be reopened or is there some other way to see progress on this feature? (I checked the lab branch and it doesn't look like anything has changed since the backed out merge.)

@eggp
Copy link

eggp commented Dec 7, 2017

it is a live feature ? it will be in the future ? :) last commit in: https://github.com/angular/angular/tree/labs/elements oct.12 :(

@gkalpak
Copy link
Member Author

gkalpak commented Dec 8, 2017

Not sure what you mean by "live feature". It is not out of labs yet (so not part of the actual release) if that is what you mean. Things under labs are experimental and not guaranteed to make it into core (at least that's my understanding), but I am pretty confident that "Angular Components as Custom Elements" will definitely make it into core - one way or another.

In the meantime, we are investigating some internal core changes that will also improve ngElements (e.g. make it possible to reduce the bundle size of bundled components, etc). And we might soon try out ngElements on specific parts of angular.io. Docs are also on the roadmap not too far down the road 😃

Stay tuned! And feel free to play around with labs/elements and give us feedback :heart:
(Some useful resources for getting started can be found in #20859.)/sub>

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 12, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes feature Issue that requests a new feature target: major This PR is targeted for the next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.