-
Notifications
You must be signed in to change notification settings - Fork 26.5k
feat(platform browser): introduce Meta service #12322
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
Conversation
CI fails with
I don't know what I'm missing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
General question, is there any benefit to setting meta tags in the browser (instead of just pre-render)? Like do Chrome extensions like Pocket look at the current meta tags of content?
* @param contentValue new content value | ||
* @returns {void} | ||
*/ | ||
setContent(metaName: string, contentValue: string): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reasoning for not implicitly creating the tag if it doesn't exist? For example, for something like open graph tags, the existence of certain types of tags depends on the page's content. I.e. the og:video
tag should not be present if there's no video.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some meta tags have a "property" and no "name" such as Facebook:
I know this is not web standard but is this something that should be implemented in like a PropertyMetaTag or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Almost any respectable application will use property tags as well, even if they are not standard.
If this doesn't get implemented natively, then we'll have to do our own service anyway, which is just duplicating code/effort.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@FrozenPandaz I think you're agreeing with my original comment? My comment was positing that the existence of some tags, such as open graph tags, depends on content, so the meta service should manage creating/removing tags instead of just updating attributes of existing tags.
One of my other review comments addresses changing the API to support arbitrary attribute names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was just commenting on the same code..
What I meant is that Metas should not only have a name
property but also a... property
property.
I do agree with your comment though. There is no one set of metadata for every single app. I believe we should start with a blank slate and have the developer set them 1 by 1.
It should create it if its not there already, edit it if it is there, and it should have the ability to remove it.. I wonder how that will work with SPAs.. we might want to have a removeAll method as well.
* @param metaName meta tag name | ||
* @returns {HTMLElement|null} | ||
*/ | ||
getMeta(metaName: string): HTMLElement|null { return getDOM().query(`meta[name=${metaName}]`); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather the get/set methods be more generic than include the getMeta
method and return the element. setContent
could accept an optional third argument attributeName='content'
.
Or is there another reason to have the getMeta
method other than setting arbitrary attributes?
getContent(metaName: string): string|null { | ||
const meta: HTMLElement = this.getMeta(metaName); | ||
return meta ? meta.getAttribute(CONTENT_ATTR) : null; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A remove
method seems appropriate since this service is a map-like interface, as long as the setContent
method would implicitly create elements.
By the way the TS failures are because of returning union types, which is not compatible with TS 1.8. |
@jeffbcross It's needed to change metadata for sharing content after each routing navigation. This is for second+ views. |
expect(actual).toEqual(META_CONTENT); | ||
}); | ||
|
||
it('should return meta element', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Repeated description
Does this work with server-side rendering and Web Worker when it reads from the DOM? |
@laco0416 thanks, I'm looking for documentation about common extensions like pocket to verify that they actually read the meta tags inside the browser, rather than re-requesting the content. Can you provide any links to confirm that popular extensions actually look at the browser state? |
@DzmitryShylovich do you plan to update this PR with requested changes? |
@DzmitryShylovich @jeffbcross |
@jeffbcross updated |
@DzmitryShylovich can you please check, some of the tests didnt pass? |
* @param tags | ||
* @returns {HTMLMetaElement[]} | ||
*/ | ||
addTag(...tags: MetaDefinition[]): HTMLMetaElement[] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we make this addTags
and pass an Array always ?
(In the currrent impl isArray
is useless)
* @returns {HTMLMetaElement} | ||
*/ | ||
getTag(selector: string): HTMLMetaElement { | ||
if (selector == null || selector === '') return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!selector)
* @returns {HTMLMetaElement} | ||
*/ | ||
updateTag(selector: string, tag: MetaDefinition): HTMLMetaElement { | ||
let meta: HTMLMetaElement = this.getTag(selector); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const
* | ||
* @param selectorOrElem selector or meta element | ||
*/ | ||
removeTag(selectorOrElem: string|HTMLMetaElement): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removeTagBySelector & removeTagElement
updated |
* @param tags | ||
* @returns {HTMLMetaElement[]} | ||
*/ | ||
addTags(...tags: Array<MetaDefinition|MetaDefinition[]>): HTMLMetaElement[] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need [][]
here ? (over addTags(tags: MD[])
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a convenience so we can add meta tags like
addTags(tag1, tag2, tag3)
or
addTags([tag1, tag2, tag3])
Otherwise we will get such request #7432 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make it addTag
and nobody can argue !
*/ | ||
removeTagElement(meta: HTMLMetaElement): void { | ||
if (meta) { | ||
this._removeMetaElement(meta); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about inlining methods when called once ony ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree.
*/ | ||
getTag(selector: string): HTMLMetaElement { | ||
if (!selector) return null; | ||
return this._dom.query(`meta[${selector}]`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if multiple matching els ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do u think we should use DomAdapter.querySelectorAll
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would tend to say probably (then updateTag makes little sense)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So what should we do? I think update
is the most wanted method here.
often useful to change meta values as well.
from original issue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example in vuejs they use id
to uniquely identify a tag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updateAll ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this really a problem? http://stackoverflow.com/a/26623223
Looks like it's not possible to have multiple meta tags with the same name/property.
btw not sure how it will work if we dynamically will add tags with the same name...
Just tested in chrome. we can add the same tag over and over again and then query them all as an array :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does the spec says ?
Anyway there should be tests (executed BrowserStack and SauceLabs)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spec doesn't say anything about multiple tags with the same name https://www.w3.org/TR/html5/document-metadata.html#the-meta-element
* | ||
* ### Example | ||
* | ||
* ```ts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm very much a bug fan of inline docs but also I think too much docs is bad, can you reduce the # of exs here ?
I'll add new commits so it will be easier to review. Will squash them later. |
result.push(list[i]); | ||
} | ||
} | ||
return result; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return list ? [].slice.call(list) : [];
getDOM().getElementsByTagName(doc, 'head')[0].appendChild(defaultMeta); | ||
}); | ||
|
||
afterEach(() => getDOM().getElementsByTagName(doc, 'head')[0].removeChild(defaultMeta)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clean up between tests so they won't affect each other.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently I use native document to create elements so I need to clean up them between test. Probably I should use fakeDoc = getDOM().createHtmlDocument();
instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I meant remove
over rempveChild
- cleaning up is great !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it :)
return el; | ||
} | ||
|
||
private tryParseSelector(tag: MetaDefinition): string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why try
here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we are trying our best to create a selector from a tag.
For example, a tag can be {content: 'my content'}
(without name
or property
). In this case we cannot construct selector and I think we should throw an error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try
(the word) makes me think try
(the keyword)
parseSelector()
returning null would be ok
parseSelectorOrThrow()
throwing would also be ok
*/ | ||
@Injectable() | ||
export class Meta { | ||
private _dom: DomAdapter = getDOM(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inject the DOM ? (factory)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it possible to inject dom adapter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can inject whatever when using a factory fn to define the binding. Makes sense ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do u want me to create a factory provider {provide: DomAdapter: useFactory: getDOM}
? :)
ie doesn't support |
|
getDOM().getElementsByTagName(doc, 'head')[0].appendChild(defaultMeta); | ||
}); | ||
|
||
afterEach(() => defaultMeta.remove()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getDom().remove(defaultMeta);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... or use your own service :)
@vicb so what about dom adapter injection? I don't fully understand your point. |
// ...
{provide: Meta, useFactory: getMeta}
// ...
function getMeta(): Meta {
return new Meta(getDom());
} |
updated (seems circleci is dead) |
Thanks ! |
Do we know if this is slated for 2.4? @vicb |
@MarkPieszak I believe there is no 2.4 release planned (see 2.3 blog post) so this would be in the first beta for 4.0 (See @IgorMinar's talk on the subject). |
Family over past few days, I saw something about that, but thought it was an April Fools joke (in December) haha. So March then, great! I was just wondering since this makes life easier for Universal users, think we talked about that the other week. |
@MarkPieszak yep, a big motivator of this API was for pre-rendering. Final will be in March, but betas will start being released soon :). |
@jeffbcross great! Messaged you on the slack, not sure if you still use that one |
@DzmitryShylovich is there any docs on how to use this? Also which version of angular this is supported? |
not yet. you can find usage examples in the tests
beta 4.0 |
@DzmitryShylovich @jeffbcross was looking at the new API for meta -- is it possible to use it for link rel= tags as well? Or should we stick with the old pattern along the lines below. If the second, do you know what where the old getDOM moved to?...this import broke for me
|
Check out here for a temporary solution until a DocumentService is implemented within Core |
@MarkPieszak I checked the Actualy I am planning to use such a similar Meanwhile, many thanks to @DzmitryShylovich for Meta/Title services. This way, the meta utility avoids direct manipulation of DOM and just depends on DomAdapter (in Angular 2) or on Meta/Title services (in Angular 4) - and works well with server-side rendering as well as client-side rendering. |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
Fixes #11115