diff --git a/_goldens/test/_files/dart2js/dart2js_golden.golden b/_goldens/test/_files/dart2js/dart2js_golden.golden index df37106012..0aa600a036 100644 --- a/_goldens/test/_files/dart2js/dart2js_golden.golden +++ b/_goldens/test/_files/dart2js/dart2js_golden.golden @@ -2714,7 +2714,7 @@ _.$ti = t2; }, _LinkedHashSetCell: function _LinkedHashSetCell(t0) { - this._collection$_element = t0; + this._element = t0; this._collection$_previous = this._collection$_next = null; }, _LinkedHashSetIterator: function _LinkedHashSetIterator(t0, t1) { @@ -3411,11 +3411,6 @@ }, _StyleSheetList: function _StyleSheetList() { }, - _AttributeMap: function _AttributeMap() { - }, - _ElementAttributeMap: function _ElementAttributeMap(t0) { - this._element = t0; - }, _EventStreamSubscription: function _EventStreamSubscription(t0, t1, t2, t3, t4) { var _ = this; _._pauseCount = t0; @@ -3742,18 +3737,6 @@ this.parent = t0; }, DomSanitizationServiceImpl: function DomSanitizationServiceImpl() { - }, - updateClassBinding: function(element, className, isAdd) { - if (isAdd) - element.classList.add(className); - else - element.classList.remove(className); - }, - updateClassBindingNonHtml: function(element, className, isAdd) { - if (isAdd) - element.classList.add(className); - else - element.classList.remove(className); } }, K = {NgIf: function NgIf(t0, t1, t2) { @@ -3981,7 +3964,30 @@ }, JsTestabilityRegistry: function JsTestabilityRegistry() { }}, T = {BrowserExceptionHandler: function BrowserExceptionHandler() { - }}, + }, + updateClassBinding: function(element, className, isAdd) { + if (isAdd) + element.classList.add(className); + else + element.classList.remove(className); + }, + updateClassBindingNonHtml: function(element, className, isAdd) { + if (isAdd) + element.classList.add(className); + else + element.classList.remove(className); + }, + updateAttribute: function(element, attribute, value) { + if (value == null) + element.removeAttribute.apply(element, [attribute]); + else + T.setAttribute(element, attribute, value); + $.domRootRendererIsDirty = true; + }, + setAttribute: function(element, attribute, value) { + element.setAttribute(attribute, value); + } + }, N = { EventManager$: function(_plugins, zone) { var t1 = new N.EventManager(zone, _plugins, P.LinkedHashMap_LinkedHashMap$_empty(P.String, N.EventManagerPlugin)); @@ -6781,7 +6787,7 @@ return -1; $length = bucket.length; for (i = 0; i < $length; ++i) - if (J.$eq$(bucket[i]._collection$_element, element)) + if (J.$eq$(bucket[i]._element, element)) return i; return -1; } @@ -6796,7 +6802,7 @@ return -1; $length = bucket.length; for (i = 0; i < $length; ++i) { - t1 = bucket[i]._collection$_element; + t1 = bucket[i]._element; if (t1 == null ? element == null : t1 === element) return i; } @@ -6818,7 +6824,7 @@ this._collection$_current = null; return false; } else { - this._collection$_current = t1._collection$_element; + this._collection$_current = t1._element; this._collection$_cell = t1._collection$_next; return true; } @@ -8438,47 +8444,6 @@ return [W.StyleSheet]; } }; - W._AttributeMap.prototype = { - forEach$1: function(_, f) { - var t1, t2, t3, _i, key; - for (t1 = this.get$keys(this), t2 = t1.length, t3 = this._element, _i = 0; _i < t1.length; t1.length === t2 || (0, H.throwConcurrentModificationError)(t1), ++_i) { - key = t1[_i]; - f.call$2(key, t3.getAttribute(key)); - } - }, - get$keys: function(_) { - var attributes, keys, len, i, attr; - attributes = this._element.attributes; - keys = H.setRuntimeTypeInfo([], [P.String]); - for (len = attributes.length, i = 0; i < len; ++i) { - attr = attributes[i]; - if (attr.namespaceURI == null) - keys.push(attr.name); - } - return keys; - }, - $asMapMixin: function() { - return [P.String, P.String]; - }, - $asMap: function() { - return [P.String, P.String]; - } - }; - W._ElementAttributeMap.prototype = { - $index: function(_, key) { - return this._element.getAttribute(key); - }, - remove$1: function(_, key) { - var t1, value; - t1 = this._element; - value = t1.getAttribute(key); - t1.removeAttribute(key); - return value; - }, - get$length: function(_) { - return this.get$keys(this).length; - } - }; W._EventStreamSubscription.prototype = {}; W._EventStreamSubscription_closure.prototype = { call$1: function(e) { @@ -10159,15 +10124,6 @@ if (t1 != null) hostElement.classList.add(t1); return hostElement; - }, - setAttr$3: function(renderElement, attributeName, attributeValue) { - if (attributeValue != null) - renderElement.setAttribute(attributeName, attributeValue); - else { - renderElement.toString; - new W._ElementAttributeMap(renderElement).remove$1(0, attributeName); - } - $.domRootRendererIsDirty = true; } }; X.View.prototype = { @@ -10989,13 +10945,13 @@ currVal_2 = J.get$title$z(t1.ctx); t2 = t1._expr_2; if (t2 != currVal_2) { - t1.setAttr$3(t1.rootEl, "title", currVal_2); + T.updateAttribute(t1.rootEl, "title", currVal_2); t1._expr_2 = currVal_2; } currVal_3 = t1.ctx.get$isFancy(); t2 = t1._expr_3; if (t2 != currVal_3) { - R.updateClassBindingNonHtml(t1.rootEl, "fancy", currVal_3); + T.updateClassBindingNonHtml(t1.rootEl, "fancy", currVal_3); t1._expr_3 = currVal_3; } this._compView_0.detectChanges$0(); @@ -11512,13 +11468,13 @@ t1 = self.defeatDart2JsOptimizations("title"); t2 = this._expr_0; if (t2 != t1) { - this.setAttr$3(this._el_0, "title", t1); + T.updateAttribute(this._el_0, "title", t1); this._expr_0 = t1; } t1 = self.defeatDart2JsOptimizations("fancy"); t2 = this._expr_1; if (t2 != t1) { - R.updateClassBinding(this._el_0, "fancy", t1); + T.updateClassBinding(this._el_0, "fancy", t1); this._expr_1 = t1; } }, @@ -11629,7 +11585,7 @@ _inheritMany(P.Error, [H.NullError, H.JsNoSuchMethodError, H.UnknownJsTypeError, H.RuntimeError, P.NullThrownError, P.ArgumentError, P.NoSuchMethodError, P.UnsupportedError, P.UnimplementedError, P.StateError, P.ConcurrentModificationError, P.CyclicInitializationError]); _inheritMany(H.TearOffClosure, [H.StaticClosure, H.BoundClosure]); _inherit(P.MapBase, P.MapMixin); - _inheritMany(P.MapBase, [H.JsLinkedHashMap, P._HashMap, W._AttributeMap]); + _inheritMany(P.MapBase, [H.JsLinkedHashMap, P._HashMap]); _inherit(H.NativeTypedArray, H.NativeTypedData); _inheritMany(H.NativeTypedArray, [H._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin, H._NativeTypedArrayOfInt_NativeTypedArray_ListMixin]); _inherit(H._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin, H._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin); @@ -11705,7 +11661,6 @@ _inherit(W._SpeechRecognitionResultList, W.__SpeechRecognitionResultList_Interceptor_ListMixin_ImmutableListMixin); _inherit(W.__StyleSheetList_Interceptor_ListMixin_ImmutableListMixin, W.__StyleSheetList_Interceptor_ListMixin); _inherit(W._StyleSheetList, W.__StyleSheetList_Interceptor_ListMixin_ImmutableListMixin); - _inherit(W._ElementAttributeMap, W._AttributeMap); _inherit(W._EventStreamSubscription, P.StreamSubscription); _inherit(P._StructuredCloneDart2Js, P._StructuredClone); _inherit(P._AcceptStructuredCloneDart2Js, P._AcceptStructuredClone); @@ -11987,10 +11942,10 @@ $.Device__cachedCssPrefix = null; $._platformInjectorCache = null; $.ChangeDetectionHost__current = null; - $.domRootRendererIsDirty = false; $.appViewUtils = null; $.AppViewUtils__nextCompTypeId = 0; $.sharedStylesHost = null; + $.domRootRendererIsDirty = false; $.ViewRootComponent0__renderType = null; $.ViewUsesDefaultChangeDetectionAndInputs0__renderType = null; $.ViewDefaultChangeDetectionAndInputs0__renderType = null; diff --git a/_goldens/test/_files/dart2js/dart2js_golden.template.golden b/_goldens/test/_files/dart2js/dart2js_golden.template.golden index 2cd8863102..06c7e0faa1 100644 --- a/_goldens/test/_files/dart2js/dart2js_golden.template.golden +++ b/_goldens/test/_files/dart2js/dart2js_golden.template.golden @@ -1232,7 +1232,7 @@ class ViewUsesDomBindings0 extends AppView { final _ctx = ctx; final currVal_0 = _ctx.title; if (import6.checkBinding(_expr_0, currVal_0)) { - setAttr(_el_0, 'title', currVal_0); + import16.updateAttribute(_el_0, 'title', currVal_0); _expr_0 = currVal_0; } final currVal_1 = _ctx.isFancy; @@ -1245,7 +1245,7 @@ class ViewUsesDomBindings0 extends AppView { void detectHostChanges(bool firstCheck) { final currVal_2 = ctx.title; if (import6.checkBinding(_expr_2, currVal_2)) { - setAttr(rootEl, 'title', currVal_2); + import16.updateAttribute(rootEl, 'title', currVal_2); _expr_2 = currVal_2; } final currVal_3 = ctx.isFancy; diff --git a/_goldens/test/_files/di/inlined_providers.template.golden b/_goldens/test/_files/di/inlined_providers.template.golden index 34d604ef65..1baa30f8c0 100644 --- a/_goldens/test/_files/di/inlined_providers.template.golden +++ b/_goldens/test/_files/di/inlined_providers.template.golden @@ -15,7 +15,8 @@ import 'dart:html' as import5; import 'package:angular/src/core/linker/app_view_utils.dart' as import6; import 'package:angular/src/runtime.dart' as import7; import 'package:angular/angular.dart'; -import 'package:angular/src/di/errors.dart' as import9; +import 'package:angular/src/runtime/dom_helpers.dart' as import9; +import 'package:angular/src/di/errors.dart' as import10; final List styles$ParentComponent = const []; @@ -168,14 +169,14 @@ class ViewParentComponent0 extends AppView { _compView_5 = ViewChildComponentProvidingA$B$C0(this, 5); final _el_5 = _compView_5.rootEl; parentRenderNode.append(_el_5); - createAttr(_el_5, 'directive-providing-a2-d2', ''); + import9.setAttribute(_el_5, 'directive-providing-a2-d2', ''); _ChildComponentProvidingA$B$C_5_5 = import1.ChildComponentProvidingA$B$C(); _DirectiveProviding$A2$D2_5_6 = import1.DirectiveProviding$A2$D2(); _compView_5.create(_ChildComponentProvidingA$B$C_5_5, []); _compView_6 = ViewChildComponentWithNgContentProviding$D0(this, 6); final _el_6 = _compView_6.rootEl; parentRenderNode.append(_el_6); - createAttr(_el_6, 'directive-providing-a2-d2', ''); + import9.setAttribute(_el_6, 'directive-providing-a2-d2', ''); _ChildComponentWithNgContentProviding$D_6_5 = import1.ChildComponentWithNgContentProviding$D(); _DirectiveProviding$A2$D2_6_6 = import1.DirectiveProviding$A2$D2(); _D_6_7 = import1.D2(); @@ -507,7 +508,7 @@ class _ViewChildComponentInjecting$DHost0 extends AppView styles$TestFooComponent = ['div._ngcontent-%ID%{font-size:10px}']; @@ -38,9 +38,9 @@ class ViewTestFooComponent0 extends AppView { final import2.HtmlElement parentRenderNode = initViewRoot(_rootEl); var doc = import2.document; _el_0 = createDivAndAppend(doc, parentRenderNode); - createAttr(_el_0, 'directive-with-output', ''); - createAttr(_el_0, 'role', import1.ChildDirective.hostRole); - createAttr(_el_0, 'some-child-directive', ''); + import9.setAttribute(_el_0, 'directive-with-output', ''); + import9.setAttribute(_el_0, 'role', import1.ChildDirective.hostRole); + import9.setAttribute(_el_0, 'some-child-directive', ''); addShimC(_el_0); _ChildDirective_0_5 = ChildDirectiveNgCd(import1.ChildDirective(_el_0, ElementRef(_el_0))); _DirectiveWithOutput_0_6 = import1.DirectiveWithOutput(); @@ -123,11 +123,11 @@ class ViewDirectiveContainerTest0 extends AppView viewFactory_DirectiveContainerTestHost0( return _ViewDirectiveContainerTestHost0(parentView, parentIndex); } -class ChildDirectiveNgCd extends import10.DirectiveChangeDetector { +class ChildDirectiveNgCd extends import11.DirectiveChangeDetector { final import1.ChildDirective instance; var _expr_0; var _expr_1; @@ -197,18 +197,18 @@ class ChildDirectiveNgCd extends import10.DirectiveChangeDetector { } final currVal_1 = instance.disabledStr; if (import6.checkBinding(_expr_1, currVal_1)) { - setAttr(el, 'aria-disabled', currVal_1); + import9.updateAttribute(el, 'aria-disabled', currVal_1); _expr_1 = currVal_1; } final currVal_2 = instance.disabled; if (import6.checkBinding(_expr_2, currVal_2)) { - import11.updateClassBindingNonHtml(el, 'is-disabled', currVal_2); + import9.updateClassBindingNonHtml(el, 'is-disabled', currVal_2); _expr_2 = currVal_2; } } } -class FastDirectiveNgCd extends import10.DirectiveChangeDetector { +class FastDirectiveNgCd extends import11.DirectiveChangeDetector { final import1.FastDirective instance; var _expr_0; FastDirectiveNgCd(this.instance, AppView v, import2.Element e) { @@ -220,7 +220,7 @@ class FastDirectiveNgCd extends import10.DirectiveChangeDetector { void detectHostChanges(AppView view, import2.Element el) { final currVal_0 = instance.msg; if (import6.checkBinding(_expr_0, currVal_0)) { - setAttr(el, 'data-msg', currVal_0); + import9.updateAttribute(el, 'data-msg', currVal_0); _expr_0 = currVal_0; } } diff --git a/_goldens/test/_files/directives/functional_directives.template.golden b/_goldens/test/_files/directives/functional_directives.template.golden index aac97eb159..11a69ddeb1 100644 --- a/_goldens/test/_files/directives/functional_directives.template.golden +++ b/_goldens/test/_files/directives/functional_directives.template.golden @@ -15,6 +15,7 @@ import 'dart:html' as import5; import 'package:angular/src/core/linker/app_view_utils.dart' as import6; import 'package:angular/src/runtime.dart' as import7; import 'package:angular/angular.dart'; +import 'package:angular/src/runtime/dom_helpers.dart' as import9; final List styles$AppComponent = const []; @@ -31,7 +32,7 @@ class ViewAppComponent0 extends AppView { final import5.HtmlElement parentRenderNode = initViewRoot(_rootEl); var doc = import5.document; final _el_0 = createDivAndAppend(doc, parentRenderNode); - createAttr(_el_0, 'generateText', ''); + import9.setAttribute(_el_0, 'generateText', ''); final _el_1 = createDivAndAppend(doc, _el_0); import1.generateTextDirective(_el_0, parentView.injectorGet(import1.TextService, viewData.parentIndex)); init(const [], null); diff --git a/_goldens/test/_files/directives/generics.template.golden b/_goldens/test/_files/directives/generics.template.golden index 6690e6b9e7..abb4aea775 100644 --- a/_goldens/test/_files/directives/generics.template.golden +++ b/_goldens/test/_files/directives/generics.template.golden @@ -19,7 +19,8 @@ import 'dart:core'; import 'package:angular/src/core/linker/view_container.dart'; import 'package:angular/src/common/directives/ng_for.dart' as import11; import 'package:angular/src/core/linker/template_ref.dart'; -import 'package:angular/src/core/change_detection/directive_change_detector.dart' as import13; +import 'package:angular/src/runtime/dom_helpers.dart' as import13; +import 'package:angular/src/core/change_detection/directive_change_detector.dart' as import14; final List styles$UntypedComp = const []; @@ -1635,7 +1636,7 @@ class ViewUsesGenericChangeDetector0 extends AppView viewFactory_UsesGenericChangeDetector return _ViewUsesGenericChangeDetectorHost0(parentView, parentIndex); } -class GenericDirectiveNgCd extends import13.DirectiveChangeDetector { +class GenericDirectiveNgCd extends import14.DirectiveChangeDetector { final import1.GenericDirective instance; var _expr_0; GenericDirectiveNgCd(this.instance); void detectHostChanges(AppView view, import5.Element el) { final currVal_0 = instance.input; if (import6.checkBinding(_expr_0, currVal_0)) { - setAttr(el, 'a', currVal_0?.toString()); + import13.updateAttribute(el, 'a', currVal_0?.toString()); _expr_0 = currVal_0; } } diff --git a/_goldens/test/_files/empty_properties.template.golden b/_goldens/test/_files/empty_properties.template.golden index 4c94742da0..e93caee4e2 100644 --- a/_goldens/test/_files/empty_properties.template.golden +++ b/_goldens/test/_files/empty_properties.template.golden @@ -15,6 +15,7 @@ import 'dart:html' as import5; import 'package:angular/src/core/linker/app_view_utils.dart' as import6; import 'package:angular/src/runtime.dart' as import7; import 'package:angular/angular.dart'; +import 'package:angular/src/runtime/dom_helpers.dart' as import9; final List styles$EmptyPropertiesComponent = const []; @@ -40,7 +41,7 @@ class ViewEmptyPropertiesComponent0 extends AppView { static RenderComponentType _renderType; ViewHostComponent0(AppView parentView, int parentIndex) : super(import3.ViewType.component, {}, parentView, parentIndex, ChangeDetectionStrategy.CheckAlways) { rootEl = import5.document.createElement('host'); - createAttr(rootEl, 'has-shiny', (import1.HostComponent.hasShinyAttribute ? '' : null)); + import9.setAttribute(rootEl, 'has-shiny', (import1.HostComponent.hasShinyAttribute ? '' : null)); _renderType ??= import6.appViewUtils.createRenderType((import7.isDevMode ? 'asset:_goldens/test/_files/host.dart' : null), ViewEncapsulation.None, styles$HostComponent); setupComponentType(_renderType); } @@ -191,7 +191,7 @@ class ViewHostComponent0 extends AppView { } final currVal_0 = import1.HostComponent.hasTerrible; if (import6.checkBinding(_expr_0, currVal_0)) { - setAttr(rootEl, 'has-terrible', (currVal_0 ? '' : null)); + import9.updateAttribute(rootEl, 'has-terrible', (currVal_0 ? '' : null)); _expr_0 = currVal_0; } final currVal_1 = ctx.title; @@ -201,12 +201,12 @@ class ViewHostComponent0 extends AppView { } final currVal_2 = ctx.title; if (import6.checkBinding(_expr_2, currVal_2)) { - setAttr(rootEl, 'aria-title', currVal_2); + import9.updateAttribute(rootEl, 'aria-title', currVal_2); _expr_2 = currVal_2; } final currVal_3 = ctx.isDisabled; if (import6.checkBinding(_expr_3, currVal_3)) { - setAttr(rootEl, 'aria-disabled', (currVal_3 ? '' : null)); + import9.updateAttribute(rootEl, 'aria-disabled', (currVal_3 ? '' : null)); _expr_3 = currVal_3; } final currVal_4 = ctx.isDisabled; diff --git a/_goldens/test/_files/i18n/attribute.template.golden b/_goldens/test/_files/i18n/attribute.template.golden index b3839a43af..d419accf67 100644 --- a/_goldens/test/_files/i18n/attribute.template.golden +++ b/_goldens/test/_files/i18n/attribute.template.golden @@ -16,6 +16,7 @@ import 'dart:html' as import6; import 'package:angular/src/core/linker/app_view_utils.dart' as import7; import 'package:angular/src/runtime.dart' as import8; import 'package:angular/angular.dart'; +import 'package:angular/src/runtime/dom_helpers.dart' as import10; final List styles$I18nAttributeComponent = const []; @@ -33,7 +34,7 @@ class ViewI18nAttributeComponent0 extends AppView { void detectHostChanges(bool firstCheck) { final currVal_0 = ctx.title; if (import6.checkBinding(_expr_0, currVal_0)) { - setAttr(rootEl, 'title', currVal_0); + import9.updateAttribute(rootEl, 'title', currVal_0); _expr_0 = currVal_0; } } @@ -110,7 +110,7 @@ class ViewHasTemplateAttributes0 extends AppView final _ctx = ctx; final currVal_0 = _ctx.title; if (import6.checkBinding(_expr_0, currVal_0)) { - setAttr(_el_0, 'title', currVal_0); + import9.updateAttribute(_el_0, 'title', currVal_0); _expr_0 = currVal_0; } } diff --git a/angular/lib/experimental.dart b/angular/lib/experimental.dart index 4047c80ea1..d288fd7ed3 100644 --- a/angular/lib/experimental.dart +++ b/angular/lib/experimental.dart @@ -13,9 +13,9 @@ import 'package:angular/src/runtime.dart'; import 'package:meta/meta.dart'; import 'src/bootstrap/run.dart' show appInjector; -import 'src/core/linker/app_view.dart' as app_view; import 'src/di/injector/injector.dart'; import 'src/runtime.dart'; +import 'src/runtime/dom_helpers.dart'; export 'src/bootstrap/modules.dart' show bootstrapLegacyModule; export 'src/common/directives/ng_for_identity.dart' show NgForIdentity; @@ -56,12 +56,12 @@ Injector rootLegacyInjector(InjectorFactory userInjector) { /// /// **WARNING**: This API is not considered part of the stable API. @experimental -bool isDomRenderDirty() => app_view.domRootRendererIsDirty; +bool isDomRenderDirty() => domRootRendererIsDirty; /// Resets the state of [isDomRenderDirty] to `false`. /// /// **WARNING**: This API is not considered part of the stable API. @experimental void resetDomRenderDirty() { - app_view.domRootRendererIsDirty = false; + domRootRendererIsDirty = false; } diff --git a/angular/lib/src/compiler/identifiers.dart b/angular/lib/src/compiler/identifiers.dart index c32bb47968..12d9b5a4e3 100644 --- a/angular/lib/src/compiler/identifiers.dart +++ b/angular/lib/src/compiler/identifiers.dart @@ -28,6 +28,10 @@ class DomHelpers { static final updateClassBinding = _of('updateClassBinding'); static final updateClassBindingNonHtml = _of('updateClassBindingNonHtml'); + + static final updateAttribute = _of('updateAttribute'); + static final updateAttributeNS = _of('updateAttributeNS'); + static final setAttribute = _of('setAttribute'); } class Identifiers { diff --git a/angular/lib/src/compiler/view_compiler/property_binder.dart b/angular/lib/src/compiler/view_compiler/property_binder.dart index 24e4542303..dc80a0aa5f 100644 --- a/angular/lib/src/compiler/view_compiler/property_binder.dart +++ b/angular/lib/src/compiler/view_compiler/property_binder.dart @@ -292,10 +292,12 @@ void bindAndWriteToRenderer( attrName, renderValue, ); - updateStmts.add(o.InvokeMemberMethodExpr( - attrNs == null ? 'setAttr' : 'setAttrNS', - params, - ).toStmt()); + + final updateAttribute = o.importExpr(attrNs == null + ? DomHelpers.updateAttribute + : DomHelpers.updateAttributeNS); + + updateStmts.add(updateAttribute.callFn(params).toStmt()); } break; case PropertyBindingType.cssClass: diff --git a/angular/lib/src/compiler/view_compiler/view_compiler_utils.dart b/angular/lib/src/compiler/view_compiler/view_compiler_utils.dart index 133e34c223..0c0081e42b 100644 --- a/angular/lib/src/compiler/view_compiler/view_compiler_utils.dart +++ b/angular/lib/src/compiler/view_compiler/view_compiler_utils.dart @@ -430,11 +430,16 @@ o.Statement createSetAttributeStatement(String astNodeName, break; } } - var params = - createSetAttributeParams(renderNode, attrNs, attrName, attrValue); - return o.InvokeMemberMethodExpr( - attrNs == null ? "createAttr" : "setAttrNS", params) - .toStmt(); + final params = createSetAttributeParams( + renderNode, + attrNs, + attrName, + attrValue, + ); + final function = o.importExpr( + attrNs == null ? DomHelpers.setAttribute : DomHelpers.updateAttributeNS, + ); + return function.callFn(params).toStmt(); } Map _toSortedMap(Map data) { diff --git a/angular/lib/src/core/linker/app_view.dart b/angular/lib/src/core/linker/app_view.dart index 1161b447e2..7e74f1627d 100644 --- a/angular/lib/src/core/linker/app_view.dart +++ b/angular/lib/src/core/linker/app_view.dart @@ -11,6 +11,7 @@ import 'package:angular/src/di/injector/injector.dart' show throwIfNotFound, Injector; import 'package:angular/src/core/render/api.dart'; import 'package:angular/src/runtime.dart'; +import 'package:angular/src/runtime/dom_helpers.dart'; import 'package:meta/meta.dart'; import 'package:meta/dart2js.dart' as dart2js; @@ -34,13 +35,6 @@ final _viewContainerAnchor = Comment(); Comment createViewContainerAnchor() => unsafeCast(_viewContainerAnchor.clone(false)); -/// Set to `true` when Angular modified the DOM. -/// -/// May be used in order to optimize polling techniques that attempt to only -/// process events after a significant change detection cycle (i.e. one that -/// modified the DOM versus a no-op). -bool domRootRendererIsDirty = false; - const _UndefinedInjectorResult = Object(); /// Shared app view members used to reduce polymorphic calls and @@ -456,31 +450,6 @@ abstract class AppView extends View { return hostElement; } - void setAttr( - Element renderElement, String attributeName, String attributeValue) { - if (attributeValue != null) { - renderElement.setAttribute(attributeName, attributeValue); - } else { - renderElement.attributes.remove(attributeName); - } - domRootRendererIsDirty = true; - } - - void createAttr( - Element renderElement, String attributeName, String attributeValue) { - renderElement.setAttribute(attributeName, attributeValue); - } - - void setAttrNS(Element renderElement, String attrNS, String attributeName, - String attributeValue) { - if (attributeValue != null) { - renderElement.setAttributeNS(attrNS, attributeName, attributeValue); - } else { - renderElement.getNamespacedAttributes(attrNS).remove(attributeName); - } - domRootRendererIsDirty = true; - } - /// Adds content shim class to HtmlElement. void addShimC(HtmlElement element) { String contentClass = componentType.contentAttr; diff --git a/angular/lib/src/runtime/dom_helpers.dart b/angular/lib/src/runtime/dom_helpers.dart index 6557eb0e14..006002580a 100644 --- a/angular/lib/src/runtime/dom_helpers.dart +++ b/angular/lib/src/runtime/dom_helpers.dart @@ -1,10 +1,38 @@ /// This library is considered separate from rest of `runtime.dart`, as it /// imports `dart:html` and `runtime.dart` is currently used on libraries /// that expect to only run on the command-line VM. +library angular.src.runtime.dom_helpers; + import 'dart:html'; +import 'package:js/js_util.dart' as js; import 'package:meta/dart2js.dart' as dart2js; +// Adds additional (missing) methods to `dart:html`'s [Element]. +// +// TODO(https://github.com/dart-lang/sdk/issues/35655): Remove. + +/// https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute +void _removeAttribute(Element e, String attribute) { + js.callMethod(e, 'removeAttribute', [attribute]); +} + +/// https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttributeNS +void _removeAttributeNS(Element e, String namespace, String attribute) { + js.callMethod(e, 'removeAttributeNS', [attribute]); +} + +/// Set to `true` when Angular modified the DOM. +/// +/// May be used in order to optimize polling techniques that attempt to only +/// process events after a significant change detection cycle (i.e. one that +/// modified the DOM versus a no-op). +/// +/// **NOTE**: What sets this to `true` (versus ignores it entirely) is currently +/// not consistent (it skips some methods that knowingly update the DOM). See +/// b/122842549. +var domRootRendererIsDirty = false; + /// Either adds or removes [className] to [element] based on [isAdd]. /// /// For example, the following template binding: @@ -42,3 +70,50 @@ void updateClassBindingNonHtml(Element element, String className, bool isAdd) { element.classes.remove(className); } } + +/// Updates [attribute] on [element] to reflect [value]. +/// +/// If [value] is `null`, this implicitly _removes_ [attribute] from [element]. +@dart2js.noInline +void updateAttribute( + Element element, + String attribute, + String value, +) { + if (value == null) { + _removeAttribute(element, attribute); + } else { + setAttribute(element, attribute, value); + } + domRootRendererIsDirty = true; +} + +/// Similar to [updateAttribute], but supports name-spaced attributes. +@dart2js.noInline +void updateAttributeNS( + Element element, + String namespace, + String attribute, + String value, +) { + if (value == null) { + _removeAttributeNS(element, namespace, attribute); + } else { + element.setAttributeNS(namespace, attribute, value); + } + domRootRendererIsDirty = true; +} + +/// Similar to [updateAttribute], but strictly for setting the initial [value]. +/// +/// This is meant as a slight optimization when initially building elements +/// from the template, as it does not check to see if [value] is `null` (and +/// the attribute should be removed) nor does it set [domRootRendererIsDirty]. +@dart2js.noInline +void setAttribute( + Element element, + String attribute, [ + String value = '', +]) { + element.setAttribute(attribute, value); +}